AOP的重要概念
1、切面 : 切点(Pointcut) + Advice【 在哪里 、加什么 】
2、Advice: 在 切点 选中的 连接点 "加入" 的 代码 就是 Advice【确定 加什么 】
3、切点( Pointcut ) : 用来 筛选 连接点 的条件就是切点, 类似于sql语句,where条件就可以理解为切点【 确定 在哪里加 ,在那些方法里面加入代码】
4、连接点( Join Point ) : 执行点 + 方位信息 所有被拦截的方法被加入了其他的方法,通过代理的方法将想要加入的想要的代码,加入的代码和原有的代码形成了连接点。一个方法有5个连接点,四个方法就有20个
5、方位信息
6、执行点:一个可以执行的方法在执行时就是一个执行点
使用AOP的方式
1、使用 XML 文件中的 aop 命名空间:
...................
proxy-target-class 指示 采用 那种方式 创建 代理对象
proxy-target-class="false" 不采用 cglib 方式,而是采用 JDK 动态代理方式 ,若没有实现接口会通过 cglib 方式
proxy-target-class="true" 采用 cglib 方式(当一个类没有实现任何接口时,必须采用这种方式)动态产生字节码
2、使用 注解 声明
使用 aop:advisor 标签来声明切面
1、这种方式是基于 XML 文件中的 aop 命名空间,并通过Spring的API实现AOP。
2、主要步骤
package ecut.aop.xml;
public class Monkey {
private String name ;
public void eat( String food ) {
System.out.println( this.name + " 吃 " + food );
}
public void run(){
System.out.println( this.name + " 在跑步 " );
}
public void sleep(){
System.out.println( this.name + " 在睡觉 " );
}
public void fly(){
System.out.println( this.name + " 很牛逼,可以腾云驾雾 " );
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
MonkeyBeforeAdvice:
package ecut.aop.xml;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
// org.springframework.aop.BeforeAdvice 接口 继承了 org.aopalliance.aop.Advice
// org.springframework.aop.MethodBeforeAdvice 接口 继承了 BeforeAdvice
public class MonkeyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before( Method method , Object[] args , Object target) throws Throwable {
System.out.println( "MonkeyBeforeAdvice 提示 : 方法[ " + method.getName() + " ]将要执行了" );
}
}
org.springframework.aop.BeforeAdvice 接口 继承了 org.aopalliance.aop.Advice,org.springframework.aop.MethodBeforeAdvice 接口 继承了 BeforeAdvice,实现MethodBeforeAdvice的接口并重写before方法。
MonkeyAroundAdvice:package ecut.aop.xml;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
// org.aopalliance.intercept.Interceptor 继承了 org.aopalliance.aop.Advice
// org.aopalliance.intercept.MethodInterceptor 继承 org.aopalliance.intercept.Interceptor
public class MonkeyAroundAdvice implements MethodInterceptor{
@Override
public Object invoke( MethodInvocation invocation ) throws Throwable {
System.out.println( "around advice : before" );
Object result = invocation.proceed(); // 让被 "拦截" 的方法执行
System.out.println( "around advice : after" );
/*
Method m = invocation.getMethod(); //获取被连接的 方法 对应的 Method 对象
Object[] args = invocation.getArguments(); // 获得 即将被执行的 方法的参数列表
Object target = invocation.getThis(); // 获取 代理目标 ( 被别的对象所代理的那个对象 )
// System.out.println( target );
System.out.println( "around advice : before" );
Object result = m.invoke( target , args ); // 调用 target 对象的 m 对应的方法,并传入参数 args
System.out.println( "around advice : after" );
*/
return result ;
}
}
org.aopalliance.intercept.Interceptor 继承了 org.aopalliance.aop.Advice,org.aopalliance.intercept.MethodInterceptor 继承 org.aopalliance.intercept.Interceptor实现MethodInterceptor的接口并重写invoke方法。这个Advice需要继续向后传递结果,因此需要将结果返回。
MonkeyAfterAdvice:
package ecut.aop.xml;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class MonkeyAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning( Object returnValue , Method method , Object[] args , Object target )
throws Throwable {
System.out.println( "方法[ " + method.getName() + " ]执行后返回了: " + returnValue );
}
}
需要实现AfterReturningAdvice 接口重写afterReturning方法。
需要在配置文件中声明切点和Advice,然后aop:advisor声明切面Advice,声明切点需要通过expression属相来指定切点表达execution(修饰符 返回类型 包名.类名.方法名(参数列表) 异常类型)。
package ecut.aop.xml;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAdvisor {
public static void main(String[] args) {
String configLocations = "classpath:ecut/**/xml/advisor.xml" ;
AbstractApplicationContext container = new ClassPathXmlApplicationContext( configLocations );
Monkey m = container.getBean( "wk" , Monkey.class );
//获取的是代理类的类型,不再是Monkey
System.out.println( m.getClass() );
System.out.println("~~~~~~~~~~~~~~~~~~~~~~");
m.eat( "苹果" );
System.out.println("~~~~~~~~~~~~~~~~~~~~~~");
m.run();
System.out.println("~~~~~~~~~~~~~~~~~~~~~~");
String name = m.getName() ;
//和filter,interceptor 不一样,需继续传递才会往下执行,而after,before拦截下来之后会继续往下执行,无需继续传递
System.out.println( "name: " + name );
container.close();
}
}
通过m.getClass可以获得代理类的类型。测试代码运行结果如下:
class ecut.aop.xml.Monkey$$EnhancerBySpringCGLIB$$44ed5ead
~~~~~~~~~~~~~~~~~~~~~~
MonkeyBeforeAdvice 提示 : 方法[ eat ]将要执行了
around advice : before
孙悟空 吃 苹果
方法[ eat ]执行后返回了: null
around advice : after
~~~~~~~~~~~~~~~~~~~~~~
MonkeyBeforeAdvice 提示 : 方法[ run ]将要执行了
around advice : before
孙悟空 在跑步
方法[ run ]执行后返回了: null
around advice : after
~~~~~~~~~~~~~~~~~~~~~~
MonkeyBeforeAdvice 提示 : 方法[ getName ]将要执行了
around advice : before
方法[ getName ]执行后返回了: 孙悟空
around advice : after
name: 孙悟空
由结果可以看出,Advice按照配置文件中引用的顺序输出了。默认执行顺序为before 一定在 after之前执行,若为方位信息一致,谁在前谁先执行。
3、织入顺序:
MonkeyAnotherAdvice :
package ecut.aop.xml;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class MonkeyAnotherAdvice implements MethodBeforeAdvice {
@Override
public void before( Method method , Object[] args , Object target) throws Throwable {
System.out.println( "MonkeyAnotherAdvice 提示 : 方法[ " + method.getName() + " ]将要执行了" );
}
配置文件:
数字越小越先执行,order属性所指定的数字可以不按照顺序,只要数字之前有大小之分就可以决定两个Advice的执行顺序
测试类:
package ecut.aop.xml;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAdvisorOrder {
public static void main(String[] args) {
String configLocations = "classpath:ecut/**/xml/advisor-order.xml" ;
AbstractApplicationContext container = new ClassPathXmlApplicationContext( configLocations );
Monkey m = container.getBean( "wk" , Monkey.class );
System.out.println( m.getClass() );
m.eat( "苹果" );
container.close();
}
}
运行结果如下:
class ecut.aop.xml.Monkey$$EnhancerBySpringCGLIB$$b4a506ad
MonkeyAnotherAdvice 提示 : 方法[ eat ]将要执行了
MonkeyBeforeAdvice 提示 : 方法[ eat ]将要执行了
孙悟空 吃 苹果
织入顺序由order决定,若没有指定order属性会由配置文件中引用顺序来执行即MonkeyBeforeAdvice 先执行
使用 aop:aspect 标签来声明一组切面
1、这种方式是基于 XML 文件中的 aop 命名空间,并通过自定义类来实现AOP。
2、主要步骤
package ecut.aop.xml;
public class Panda {
private String name ;
public void eat( String food ) {
System.out.println( this.name + " 吃 " + food );
}
public void run(){
System.out.println( this.name + " 在跑步 " );
}
public void sleep(){
System.out.println( this.name + " 在睡觉 " );
}
public void fly(){
System.out.println( this.name + " 很牛逼,可以腾云驾雾 " );
}
public int div( int a , int b ) {
return a / b ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package ecut.aop.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
public class PandaAdvices {
/**
* 不要静态的,所有Advices都是实例化之后去植入这个方法
* 通过 JoinPoint 类型的对象 可以访问连接点 信息
* 如果不需要访问连接点信息,那么 可以不写 这个参数
* @param point
*/
public void before( JoinPoint point ) {
//方法签名方法签名int ecut.aop.xml.Panda.div(int,int)
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "PandaAdvices 提示你 : 方法[ " + name + " ]将要执行了" );
}
// ProceedingJoinPoint 是 JoinPoint 接口的子接口 ,可以通过 ProceedingJoinPoint访问连接点 信息,也可以通过ProceedingJoinPoint使方法执行
public Object around( ProceedingJoinPoint point ) throws Throwable {
System.out.println( "around : before" );
Object result = point.proceed();
System.out.println( "around : after" );
return result ;//得将值返回,不然没法往后传值了
}
public void afterReturn( JoinPoint point , Object result ) {
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "方法[ " + name + " ]执行后返回: " + result );
}
public void afterThrow( JoinPoint point , Throwable ex ) {
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "方法[ " + name + " ]执行时发生异常: " + ex );
}
public void after( JoinPoint point ) {
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "方法[ " + name + " ]执行结束" );
}
}
连接点JoinPoint 是执行点+方位信息, 通过 JoinPoint 类型的对象 可以访问连接点信息 ,如果不需要访问连接点信息,那么 可以不写 这个参数,afterThrow和afterReturn中的参数名称要和配置文件中的名称保持一致。ProceedingJoinPoint 是 JoinPoint 接口的子接口 ,可以通过 ProceedingJoinPoint访问连接点 信息,也可以通过ProceedingJoinPoint使方法执行。
通过
package ecut.aop.xml;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAspect {
public static void main(String[] args) {
String configLocations = "classpath:ecut/**/xml/aspect.xml" ;
AbstractApplicationContext container = new ClassPathXmlApplicationContext( configLocations );
Panda m = container.getBean( "panda" , Panda.class );
System.out.println(m.getClass());
//表面上调用的panda的eat,方法实际上m只是一个代理对象
m.eat( "面条" );
System.out.println( "~~~~~~~~~~~~~~~~~" );
String name = m.getName() ;
System.out.println( "name : " + name );
System.out.println( "~~~~~~~~~~~~~~~~~" );
int r = m.div( 100 , 0 );
System.out.println( r );
container.close();
}
}
运行结果如下:
class ecut.aop.xml.Panda$$EnhancerBySpringCGLIB$$dac36ee3
PandaAdvices 提示你 : 方法[ eat ]将要执行了
around : before
阿宝 吃 面条
around : after
方法[ eat ]执行后返回: null
方法[ eat ]执行结束
~~~~~~~~~~~~~~~~~
PandaAdvices 提示你 : 方法[ getName ]将要执行了
around : before
around : after
方法[ getName ]执行后返回: 阿宝
方法[ getName ]执行结束
name : 阿宝
~~~~~~~~~~~~~~~~~
PandaAdvices 提示你 : 方法[ div ]将要执行了
around : before
方法[ div ]执行时发生异常: java.lang.ArithmeticException: / by zero
方法[ div ]执行结束
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ecut.aop.xml.Panda.div(Panda.java:24)
3、织入顺序
AnimalAdvices:
package ecut.aop.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
public class AnimalAdvices {
public void before1( JoinPoint point ) {
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "AnimalAdvices 的 before1 提示你 : 方法[ " + name + " ]将要执行了" );
}
public void before2( JoinPoint point ) {
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "AnimalAdvices 的 before2 提示你 : 方法[ " + name + " ]将要执行了" );
}
}
配置文件:
测试类:
package ecut.aop.xml;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAspectOrder {
public static void main(String[] args) {
String configLocations = "classpath:ecut/**/xml/aspect-order.xml" ;
AbstractApplicationContext container = new ClassPathXmlApplicationContext( configLocations );
Panda m = container.getBean( "panda" , Panda.class );
m.eat( "面条" );
container.close();
}
}
运行结果如下:
AnimalAdvices 的 before2 提示你 : 方法[ eat ]将要执行了
AnimalAdvices 的 before1 提示你 : 方法[ eat ]将要执行了
PandaAdvices 提示你 : 方法[ eat ]将要执行了
阿宝 吃 面条
使用注解声明切面
1、步骤
package ecut.aop.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
/**
* 控制层 ( Controller ) : 对 表示层 的用户操作 做出响应
* 实现技术可以是: Servlet 、Struts 、Spring MVC
* @Controller("sc") value ="sc" 相当于bean的名称是sc,默认名称是 studentController
*/
@Controller
public class StudentController {
@Autowired
@Qualifier( "studentService" )
private StudentService studentService ;
public String regist( Student s ){
System.out.println( "StudentController # regist ( Student ) " );
studentService.save( s );
return "success" ;
}
public String logout() {
throw new RuntimeException( "抛出异常" );
}
public StudentService getStudentService() {
return studentService;
}
public void setStudentService(StudentService studentService) {
this.studentService = studentService;
}
}
StudentAspect声明一组切面
package ecut.aop.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class StudentAspect {
//@Pointcut( "execution(* ecut..StudentController.*(..))" )
@Pointcut( "execution(* ecut.aop.annotation.StudentController.*(..))" )
private void myPointcut(){
// 用声明方法的形式 来声明 一个切点,切点名称就是 方法名 , 切点表达式在 注解中指定
}
//在 方位信息的注解内部 通过 方法调用的 形式 来引用已经声明的切点
@Before( "myPointcut()" )
public void before( JoinPoint point ) {
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "StudentAspect 的 before 提示你 : 方法[ " + name + " ]将要执行了" );
}
@Around( "myPointcut()" )
public Object around( ProceedingJoinPoint point ) throws Throwable {
System.out.println( "StudentAspect 的 around 提示你 : before" );
Object result = point.proceed();
System.out.println( "StudentAspect 的 around 提示你 : after" );
return result ;
}
//@AfterReturning( value = "myPointcut()" , returning = "result" )
@AfterReturning( pointcut = "myPointcut()" , returning = "result" )
public void afterReturn( JoinPoint point , Object result ) {
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "StudentAspect 的 afterReturn 提示你 : 方法[ " + name + " ]执行后返回: " + result );
}
@AfterThrowing( pointcut = "myPointcut()" , throwing = "ex" )
public void afterThrow( JoinPoint point , Throwable ex ) {
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "StudentAspect 的 afterThrow 提示你 : 方法[ " + name + " ]执行时发生异常: " + ex );
}
@After( "myPointcut()" )
public void after( JoinPoint point ) {
Signature signature = point.getSignature(); // 获得当前的连接点对应的方法的签名
String name = signature.getName() ; // 获得方法的名称
System.out.println( "StudentAspect 的 after 提示你 : 方法[ " + name + " ]执行结束" );
}
}
首先使用Component注解要让spring容器知道有一个类存在,其次要使用Aspect注解声明时一组切面,在类中通过声明方法的形式 来声明 一个切点,切点名称就是方法名, 切点表达式在 注解中指定。在 方位信息的注解内部 通过 方法调用的 形式 来引用已经声明的切点。@AfterThrowing注解中的throwing属性需要和afterThrow方法中的Throwable参数名称保持一致,@AfterReturning注解中的returning属相需要和afterReturn方法中的Object 参数名称保持一致。
配置文件
配置文件中通过<context:component-scan base-package="ecut.aop.annotation" />扫描base-package所指定包及其子包的所有组件,若有注解就自动增加其配置,通过<aop:aspectj-autoproxy proxy-target-class="true" />为 注解 提供 自动产生 代理对象的 支持
package ecut.aop.annotation;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestStudentController {
public static void main(String[] args) {
Student s = new Student();
s.setId( 100 );
s.setName( "张三" );
String configLocations = "classpath:ecut/**/annotation/ioc-aop.xml" ;
AbstractApplicationContext container = new ClassPathXmlApplicationContext( configLocations );
StudentController sc = container.getBean( "studentController" , StudentController.class );
System.out.println( sc );
sc.regist( s );
System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );
sc.logout();
container.close();
}
}
2、相关注解说明
3、织入顺序
转载请于明显处标明出处:
https://www.cnblogs.com/AmyZheng/p/9277832.html