AOP编程
AOP即面向切面编程。相对面向对象编程来说,面向对象是从静态角度考虑程序的结构,面向切面则是从动态角度考虑程序的运行过程。
面向切面编程通常是为了代码重用,类或者方法的封装也可以实现代码重用,但各个模块中需要显式调用类或者方法才能使用相应功能,即重用的代码在一定程度上还是会跟具体的业务模块耦合。面向切面编程则使用了不同的重用机制解决了模块耦合的的问题,其底层通过特殊编译或者动态代理的方式,对目标类/业务类进行了功能增强,从而使得编程人员完全不需要修改业务类就可以使用公共的功能类或者方法。面向切面编程最常用的场景就是事务控制,权限控制,日志打印等等。
面向切面编程已经成为面向对象编程的一种补充,目前已经有成熟的框架专门针对面向切面编程,例如AspectJ,是最早也是目前最成熟的框架。
下载AspectJ框架之后,只需要使用简单的AspectJ编程语法以及使用特殊的编译方法,可以实现目标类的重新编译,将需要增强的代码编译进目标代码中,达到增强目标类功能的目的。
AspectJ是一个独立存在的框架,并不强制需要与Spring框架或者与Eclipse工具结合使用,直接使用AspectJ就能进行java的AOP编程。
下面是AOP编程中的一些基本术语:
增强处理(Advice):就是对原有代码进行的增强处理,一般处理类型有 around, before, after等
切面(Aspect):用来组织多个Advice,即在切面(类)中定义增强处理(Advice)
连接点(Joinpoint):在Spring AOP中,连接点总是方法的调用
切入点(Pointcut):...
Spring AOP
Sping AOP使用动态代理来实现,默认使用Java动态代理来创建AOP代理,因为Spring推荐的是面向接口的编程。当然也可以强制使用cglib进行动态代理。
在Spring AOP中,使用的AOP语法与AspectJ的语法一样,使用相同的注解及增强处理表达式,只不过在Spring AOP中,不需要额外的编译器,Spring会使用动态代理进行增强功能的织入。
要使用Spring AOP,需要先导入如下三个jar文件,
aspectjweaver.jar,aspectjrt.jar,aopalliance.jar
Spring AOP 基于注解的 零配置 方式
为了启用Spring对@AspectJ切面配置的支持,并保证Spring容器中的目标Bean被一个或多个切面自动增强,必须在Spring配置文件中配置如下片段,
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans.xsd 8 http://www.springframework.org/schema/aop 9 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 10 http://www.springframework.org/schema/context 11 http://www.springframework.org/schema/context/spring-context.xsd"> 12 13 <!-- 启动@AspectJ支持 --> 14 <aop:aspectj-autoproxy/> 15 </beans>
如果不打算使用Spring Schema配置方式,则需要如下方式启动@AspectJ支持,
1 <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
下面示范Spring AOP的用法,这个例子演示用户权限,打印日志等。
首先是一个XML配置文件,该配置文件启用AspectJ支持,并自动搜索Bean组件及切面类,
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans.xsd 8 http://www.springframework.org/schema/aop 9 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 10 http://www.springframework.org/schema/context 11 http://www.springframework.org/schema/context/spring-context.xsd"> 12 <!-- 启动@AspectJ支持 --> 13 <aop:aspectj-autoproxy/> 14 <!-- 或者以下面这种方式启用 --> 15 <!-- 16 <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" /> 17 --> 18 <!-- 自动搜索bean组件,自动搜索切面类 --> 19 <context:component-scan base-package="spi,asp"> 20 <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect" /> 21 </context:component-scan> 22 23 </beans>
首先在切面类AuthAspect中定义一个Before增强处理
1 package asp; 2 3 import org.aspectj.lang.annotation.Aspect; 4 import org.aspectj.lang.annotation.Before; 5 6 //使用Aspect修饰的类将作为切面类 7 @Aspect 8 public class AuthAspect { 9 //匹配spi包下所有类 10 //所有方法的执行作为切入点 11 @Before("execution(* spi.*.*(..))") 12 public void authority() { 13 System.out.println("模拟执行权限检查"); 14 } 15 }
上面的切面类将被Spring的配置文件识别,有@Aspect注解的类将作为切面类,不会被Spring容器有额外的增强处理。
这个切面类将spi包下的所有类的所有方法都作为切入点,进行了Before类型的增强处理。
下面是spi包下的两个类,
HelloImpl
1 package spi; 2 3 import org.springframework.stereotype.Component; 4 5 @Component 6 public class HelloImpl implements Hello { 7 8 @Override 9 public void foo() { 10 System.out.println("执行Hello组件的foo()方法"); 11 } 12 13 @Override 14 public int addUser(String name, String pass) { 15 System.out.println("执行Hello组件的addUser添加用户:"+name);
return 20; 16 } 17 18 }
WorldImpl
1 package spi; 2 3 import org.springframework.stereotype.Component; 4 5 @Component 6 public class WorldImpl implements World { 7 8 @Override 9 public void bar() { 10 System.out.println("执行World组件的bar()方法"); 11 } 12 13 }
上面两个类将自动被Spring识别为Bean
下面是一个简单的测试类,
1 package spi; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 public class Test { 7 8 public static void test1() { 9 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 10 Hello hello = ctx.getBean("helloImpl", Hello.class); 11 World world = ctx.getBean("worldImpl", World.class); 12 hello.foo(); 13 hello.addUser("张三", "123"); 14 world.bar(); 15 } 16 public static void main(String[] args) { 17 test1(); 18 } 19 }
执行结果,
1 模拟执行权限检查 2 执行Hello组件的foo()方法 3 模拟执行权限检查 4 执行Hello组件的addUser添加用户:张三 5 模拟执行权限检查 6 执行World组件的bar()方法
可以看到,每一个Bean的方法执行前都被加入了增强功能,打印了一段话“模拟执行权限检查”,而我们对HelloImpl和WorldImpl没有任何修改。
定义AfterReturning增强处理
AfterReturning将在目标方法正常执行完之后被织入,它有个重要的属性 returning, returning定义的值将接收方法返回值,同时它又将作为Advice方法的同名形参,例如下面这样,
1 package asp; 2 3 import org.aspectj.lang.annotation.AfterReturning; 4 import org.aspectj.lang.annotation.Aspect; 5 import org.aspectj.lang.annotation.Before; 6 7 @Aspect 8 public class LogAspect { 9 //匹配spi包下所有类 10 //所有方法的执行作为切入点, 11 @AfterReturning(returning="rvt", pointcut="execution(* spi.*.*(..))") 12 //声明rvt指定的类型会限制目标方法必须返回指定类型的值或没有返回值 13 //如果声明为Object,意味着没有限制 14 public void log(Object rvt) { 15 System.out.println("获取目标方法返回值:"+rvt); 16 System.out.println("模拟记录日志功能..."); 17 } 18 }
定义returning的意义在于,在Advice方法(即上面的log(Object rvt)时候,可以为形参指定一个类型,这个切面类只会匹配与这个指定的类型具有相同类型返回值,或者没有返回值的方法!
如果指定类型为Object,则不做限制,所有方法都会被织入增强处理,
先执行测试类,看到输入结果如下,
1 模拟执行权限检查 2 执行Hello组件的foo()方法 3 获取目标方法返回值:null 4 模拟记录日志功能... 5 模拟执行权限检查 6 执行Hello组件的addUser添加用户:张三 7 获取目标方法返回值:20 8 模拟记录日志功能... 9 模拟执行权限检查 10 执行World组件的bar()方法 11 获取目标方法返回值:null 12 模拟记录日志功能...
可以看到所有方法都被织入了增强处理,有返回值的返回了返回值,没有返回值的返回了null
现在如果对上面的切面类修改一下,改成,
1 public void log(int rvt) {
再次执行测试类,
1 模拟执行权限检查 2 执行Hello组件的foo()方法 3 模拟执行权限检查 4 执行Hello组件的addUser添加用户:张三 5 获取目标方法返回值:20 6 模拟记录日志功能... 7 模拟执行权限检查 8 执行World组件的bar()方法
发现只剩下addUser()这个方法被织如了增强处理了,因为只有这个方法返回了int类型,其他方法都没有返回值,
如果再次修改上面的切面类如下,
1 public void log(String rvt) {
再运行测试类,
1 模拟执行权限检查 2 执行Hello组件的foo()方法 3 模拟执行权限检查 4 执行Hello组件的addUser添加用户:张三 5 模拟执行权限检查 6 执行World组件的bar()方法
这回发现完全没有方法被织入增强处理了,因为没有哪个方法是返回String的
定义AfterThrowing增强处理
AfterThrowing增强处理是用来处理方法抛出的未处理的异常的,例如修改上面的addUser()方法,向上一级调用者抛出一个异常:
1 public int addUser(String name, String pass) { 2 System.out.println("执行Hello组件的addUser添加用户:"+name); 3 if (name.length() < 3 || name.length() > 10) { 4 throw new IllegalArgumentException("name参数长度必须大于3,且小于10!"); 5 } 6 return 20; 7 }
然后定义一个切面类,进行AfterThrowing增强处理,对切入点(即上面的addUser()方法)抛出的异常进行增强处理。
1 @Aspect 2 public class RepairAspect { 3 //匹配spi包下的所有类 4 //所有方法都被做为切入点 5 @AfterThrowing(throwing="ex", pointcut="execution(* spi.*.*(..))") 6 public void doRecoveryActions(Throwable ex) { 7 System.out.println("目标方法中抛出异常:"+ex); 8 System.out.println("模拟Advice对异常的修复..."); 9 } 10 }
与AfterReturning非常类似的是,AfterThrowing有一个属性叫throwing, 其值也可以作为增强处理方法(Advice)的同名形参,并且也具有类型限制,即在Advice中为形参指定的类型,也只能匹配能抛出这种异常类型的方法。
我们在上面的切面类中为Advice形参指定的是Throwable类型,即会处理所有异常类型,因此对于上面的addUser()方法进行测试,
1 模拟执行权限检查 2 执行Hello组件的foo()方法 3 获取目标方法返回值:null 4 模拟记录日志功能... 5 模拟执行权限检查 6 执行Hello组件的addUser添加用户:张三 7 目标方法中抛出异常:java.lang.IllegalArgumentException: name参数长度必须大于3,且小于10! 8 模拟Advice对异常的修复... 9 Exception in thread "main" java.lang.IllegalArgumentException: name参数长度必须大于3,且小于10! 10 at spi.HelloImpl.addUser(HelloImpl.java:17) 11 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 12 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:95) 13 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:56) 14 at java.lang.reflect.Method.invoke(Method.java:620) 15 at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) 16 at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) 17 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) 18 at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58) 19 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 20 at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52) 21 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 22 at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52) 23 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 24 at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) 25 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 26 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) 27 at com.sun.proxy.$Proxy9.addUser(Unknown Source) 28 at spi.Test.test1(Test.java:13) 29 at spi.Test.main(Test.java:18)
从上面的结果可以看到,addUser()方法抛出的异常,被切入类的形参接收了,并进行了增强处理。这里有两点需要注意:
1.AfterThrowing只会对形参指定的异常类别进行处理,如果类型不匹配,是不会有任何作用的,例如现在将切面类该一下,
1 public void doRecoveryActions(NullPointerException ex) {
再次执行测试类,
1 模拟执行权限检查 2 执行Hello组件的foo()方法 3 获取目标方法返回值:null 4 模拟记录日志功能... 5 模拟执行权限检查 6 执行Hello组件的addUser添加用户:张三 7 Exception in thread "main" java.lang.IllegalArgumentException: name参数长度必须大于3,且小于10! 8 at spi.HelloImpl.addUser(HelloImpl.java:17) 9 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 10 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:95) 11 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:56) 12 at java.lang.reflect.Method.invoke(Method.java:620) 13 at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) 14 at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) 15 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) 16 at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58) 17 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 18 at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52) 19 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 20 at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52) 21 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 22 at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) 23 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 24 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) 25 at com.sun.proxy.$Proxy9.addUser(Unknown Source) 26 at spi.Test.test1(Test.java:13) 27 at spi.Test.main(Test.java:18)
这回根本就没有切面类对异常的增强处理了,因为之前的Throwable可以匹配任何类型,但是NullPointerException无法匹配 IllegalArgumentException。
2.AfterThrowing跟catch不同,catch可以完全处理异常,不至于让异常抛给JVM;但是AfterThrowing只对匹配的异常进行增强处理,但是不是完全处理,处理完之后,还是会抛给上一层。
例如上面的例子,最终log中还是打印出了详细异常跟踪栈,就是因为AfterThrowing只对异常进行增强处理,而不是完全处理。
如果在main()中捕捉上面的异常,会发现执行如下,
1 public static void main(String[] args) { 2 try { 3 test1(); 4 } catch (Exception e) { 5 System.out.println("捕捉到异常:"+e); 6 } 7 }
执行结果,
1 模拟执行权限检查 2 执行Hello组件的foo()方法 3 获取目标方法返回值:null 4 模拟记录日志功能... 5 模拟执行权限检查 6 执行Hello组件的addUser添加用户:张三 7 目标方法中抛出异常:java.lang.IllegalArgumentException: name参数长度必须大于3,且小于10! 8 模拟Advice对异常的修复... 9 main()捕捉到异常:java.lang.IllegalArgumentException: name参数长度必须大于3,且小于10!
可以看到,main()中的catch不仅捕捉到了异常,而且处理了异常,所以异常没有抛出到JVM中,但是AfterThrowing只是捕捉了异常,然后进行了增强,然后又继续抛给了上一层(main).
定义After增强处理
After与Afterreturning有点类似,但也有区别,Afterreturning只有在目标方法成功完成之后才会被织入,但是After无论目标方法如何结束(包括正常结束和遇到异常)都能织入增强处理。After就相当于finally{}代码块。
下面是After增强的用法,
1 package asp; 2 3 import org.aspectj.lang.annotation.After; 4 import org.aspectj.lang.annotation.Aspect; 5 6 @Aspect 7 public class ReleaseAspect { 8 //匹配spi包下所有类 9 //所有方法的执行作为切入点 10 @After("execution(* spi.*.*(..))") 11 public void release() { 12 System.out.println("模拟方法结束后的释放资源"); 13 } 14 }
执行测试类,
1 模拟执行权限检查 2 执行Hello组件的foo()方法 3 模拟方法结束后的释放资源 4 获取目标方法返回值:null 5 模拟记录日志功能... 6 模拟执行权限检查 7 执行Hello组件的addUser添加用户:张三 8 目标方法中抛出异常:java.lang.IllegalArgumentException: name参数长度必须大于3,且小于10! 9 模拟Advice对异常的修复... 10 模拟方法结束后的释放资源 11 Exception in thread "main" java.lang.IllegalArgumentException: name参数长度必须大于3,且小于10! 12 at spi.HelloImpl.addUser(HelloImpl.java:17) 13 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 14 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:95) 15 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:56) 16 at java.lang.reflect.Method.invoke(Method.java:620) 17 at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) 18 at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) 19 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) 20 at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58) 21 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 22 at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:43) 23 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 24 at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52) 25 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 26 at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52) 27 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 28 at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) 29 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 30 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) 31 at com.sun.proxy.$Proxy10.addUser(Unknown Source) 32 at spi.Test.test1(Test.java:13) 33 at spi.Test.main(Test.java:17)
可以看到上面的例子,对于foo()方法,因为是正常结束的,所以即执行了Afterreturning的增强处理,也执行了After的增强处理,
而对于addUser()方法,因为有异常抛出,因此Afterreturning增强处理并没有得到执行,但After的增强处理依然执行了。
定义Around增强处理
Around增强处理类似于Before增强和AfterReturning增强处理的总和,但是更强大。Around增强处理不仅可以随意控制目标方法的执行时机,还能修改方法的参数和返回值,一个典型的例子如下,
1 @Aspect 2 public class TxAspect { 3 @Around("execution(* spi.*.*(..))") 4 public Object processTx(ProceedingJoinPoint jp) throws Throwable { 5 System.out.println("执行目标方法之前,模式开始事务..."); 6 //获取目标方法原始的调用参数 7 Object[] args = jp.getArgs(); 8 if(args != null && args.length > 1) { 9 //修改目标方法调用参数的第一个参数 10 args[0] = "[增加的前缀]" + args[0]; 11 } 12 //以改变后的参数去执行目标方法,并保存目标方法执行后的返回值 13 Object rvt = jp.proceed(args); 14 System.out.println("执行目标方法之后,模拟结束事务..."); 15 //如果rvt的类型是Integer, 将rvt改为它的平方 16 if(rvt != null && rvt instanceof Integer) { 17 rvt = (Integer)rvt * (Integer)rvt; 18 } 19 return rvt; 20 } 21 }
上面的例子有四个地方需要了解,
- 第一个是切面类的Adfvice方法传入的是一个ProceedingJoinPoint类型的实例jp,这个jp将用来控制目标方法的参数,执行,以及返回值
- 第二个是低7行和第10,通过ProceedingJoinPoint类的getArgs()方法,可以获取目标方法的原始参数,构造成一个数组,在后续可以对参数值进行修改
- 第三个是第13行,可以通过ProceedingJoinPoint实例的proceed(args)方法真正去调用目标类(即动态代理的方式),传入的是修改之后的参数组
- 第四个是第17行,可以修改目标方法的返回值,并将修改后的值返回
执行测试类,结果如下,
1 模拟执行权限检查 2 执行目标方法之前,模式开始事务... 3 执行Hello组件的foo()方法 4 执行目标方法之后,模拟结束事务... 5 模拟方法结束后的释放资源 6 获取目标方法返回值:null 7 模拟记录日志功能... 8 模拟执行权限检查 9 执行目标方法之前,模式开始事务... 10 执行Hello组件的addUser添加用户:[增加的前缀]张三 11 执行目标方法之后,模拟结束事务... 12 模拟方法结束后的释放资源 13 获取目标方法返回值:400 14 模拟记录日志功能... 15 模拟执行权限检查 16 执行目标方法之前,模式开始事务... 17 执行World组件的bar()方法 18 执行目标方法之后,模拟结束事务... 19 模拟方法结束后的释放资源 20 获取目标方法返回值:null 21 模拟记录日志功能...
可以看到第10行,addUser()的参数被修改了;第13行,目标方法的返回值也被修改了。 同时,目标方法的前后增加了模拟的事务控制,即目标方法的执行时机也被切面类控制了。
用 JoinPoint访问目标方法的参数
JoinPoint类的实例可以作为Before, Around, AfterReturning, After增强的Advice方法的参数,用来访问目标方法的参数,
上面Around增强处理例子中的ProceedingJoinPoint就是JoinPoint的子类,不仅能访问参数,还能修改参数。
JoinPoint类提供了一些通用的方法来访问参数,如 getArgs(), getSignature(), getTarget():访问目标方法的对象,getThis() 访问代理对象。
其他几种增强中访问参数的方式大同小异,只是不能修改参数,例如下面AfterReturning增强,
1 @Aspect 2 public class LogAspect { 3 //匹配spi包下所有类 4 //所有方法的执行作为切入点, 5 @AfterReturning(returning="rvt", pointcut="execution(* spi.*.*(..))") 6 //声明rvt指定的类型会限制目标方法必须返回指定类型的值或没有返回值 7 //如果声明为Object,意味着没有限制 8 public void log(JoinPoint jp, Object rvt) { 9 //获取目标方法 10 jp.getSignature().getName(); 11 12 //获取目标参数 13 jp.getArgs(); 14 15 //获取目标方法的对象 16 jp.getTarget(); 17 18 System.out.println("获取目标方法返回值:"+rvt); 19 System.out.println("模拟记录日志功能..."); 20 } 21 }
使用args()表达式访问参数
除了上面的JoinPoint类访问目标方法的参数之外,也可以用args()表达式访问参数,像下面这样,
1 @Aspect 2 public class LogAspect { 3 //匹配spi包下所有类 4 //所有方法的执行作为切入点, 5 @AfterReturning(returning="rvt", pointcut="execution(* spi.*.*(..)) && args(name, age, ..)") 6 //声明rvt指定的类型会限制目标方法必须返回指定类型的值或没有返回值 7 //如果声明为Object,意味着没有限制 8 public void log(Object rvt,String name, int age) {
这种方式要求参数的类型和顺序跟目标方法要一致,否则不能匹配成功。
同种增强处理的顺序
如果两个切面类中,都对同一个连接点(即方法)进行增强,Spring AOP默认是随机顺序的,如果希望控制顺序的话,有两种方式,
- 一是让切面类继承 org.springframework.core.Ordered接口,实现int getOrder()方法,由这个方法的返回值决定顺序(数值越小,优先级越高)
- 二是直接在切面类上面加@Order注解,可以指定一个int类型的参数,数值越小,优先级越高
如果是在同一个切面类中,有两个相同类型的增强处理在同一个连接点被织入的话,程序就无法控制顺序了,只能尽量考虑将多个同类增强处理压缩成一个,或者放入不同的切面类中。
命名切入点
切入点概念
切入点就是一个表达式+参数,用来匹配目标方法的。
例如切面类中的
1 @AfterReturning(returning="rvt", pointcut="execution(* spi.*.*(..))")
就是一个切入点,其中这里的表达式是pointcut="execution(* spi.*.*(..))",参数是returning="rvt"
我们发现,在每一个切入类的每一种增强处理中,例如Before,after,AfterReturning等都适用了上面相同的表达式,
每一个地方这么写会比较麻烦,因此Spring AOP中可以通过定义一个普通的方法来为一个切入点命名,例如下面这样,
1 @Pointcut("execution(* spi.*.*(..))") 2 public void myPointcut() {}
通过@Pointcut注解标注一个普通方法,Pointcut的value将做为表达式,这个普通方法则会作为切入点的名称,方法的返回值必须是void
这个切入点的定义完全可以放在一个独立的切面类中,供所有切面类共用。
1 @Aspect 2 public class MyPointcut { 3 4 @Pointcut("execution(* spi.*.*(..))") 5 public void myPointcut() {} 6 }
因此在其他切面类中,可以直接使用上面的方法名代表切入点的名称了,
1 @Aspect 2 public class LogAspect { 3 //匹配spi包下所有类 4 //所有方法的执行作为切入点, 5 //@AfterReturning(returning="rvt", pointcut="execution(* spi.*.*(..))") 6 @AfterReturning(returning="rvt", pointcut="MyPointcut.myPointcut()")