Spring AOP
1.AOP基本概念及特点
(1)AOP基本概念
- AOP:Aspect Oriented Programming 的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
- 主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等
- 切面和功能垂直
(2)AOP实现方式
- 预编译
- AspectJ
- 运行期动态代理(JDK动态代理、CGLib动态代理)
- SpringAOP
- JbossAOP
(3)AOP相关概念
- 切面(Aspect):一个关注点的模块化,这个关注的可能会横切多个对象
- 连接点(Joinpoint):程序执行过程中的某个特定的点(比如说是某个类中的某个方法执行的开始)
- 通知(Advice):在切面的某个特定的连接点上执行的动作(在方法执行的时候额外执行的切面的动作)
- 切入点(Pointcut):匹配连接点的断言,在AOP中通知和一个切入点表达式关联(在切面中去匹配一个具体的连接点)
- 引入(Introduction):在不修改类代码的前提下,为类添加新的方法和属性(类似于编译器动态修改class文件来为类增加新的属性和方法,引入方式可能是AspectJ,SpringAOP,JbossAOP)
- 目标对象(Target Object):被一个或者多个切面所通知的对象
- AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)(开发时不知道对象存在)
- 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象,分为:编译时织入、类加载时织入、执行时织入(织入将切面和对象关联起来)
(4)Advice的类型
- 前置通知(Before advice):在某连接点(join point)之前执行的通知,但不能阻止连接点前的执行(除非它抛出一个异常)
- 返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知
- 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知
- 后通知(After(finally)advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)
- 环绕通知(Around Advice):包围一个连接点(joint point)的通知
(5)Spring框架中AOP的用途
- 提供了声明式的企业服务,特别是EJB(企业级JavaBean)的替代服务的声明
- 允许用户定制自己的方面,以完成OOP(面向对象编程)与AOP(面向切面编程)的互补使用
(6)Spring的AOP实现
- 纯java实现,无需特殊的编译过程,不需要控制类加载器层次
- 目前只支持方法执行连接点(通知Spring Bean的方法执行)
- 不是为了提供最完整的AOP实现(尽管它非常强大);而是侧重于提供一种AOP实现和Spring IoC容器之间的整合,用于帮助解决企业应用中的常见问题
- Spring AOP不会与AspectJ竞争,不会去提供综合全面的AOP解决方案
(7)有接口和无接口的Spring AOP实现区别
- Spring AOP默认使用标准的Java SE动态代理作为AOP代理,这使得任何接口(或者接口集)都可以被代理—有接口
- Spring AOP中也可以使用CGLIB代理(如果一个业务对象并没有实现一个接口)—无接口
2.配置切面aspect
(1)Schema-based AOP
- Spring所有的切面和通知器都必须放在一个<aop:config>内(可以配置包含多个<aop:config>元素),每一个<aop:config>可以包含pointcut,advisor和aspect元素(它们必须按照这个顺序进行声明)
- <aop:config>风格的配置大量使用了Spring的自动代理机制
- <aop:aspect>
<aop:config> <!--将aBean作为一个切面来声明,切面id是myAspect--> <aop:aspect id="myAspect" ref="aBean"> ... </aop:aspect> </aop:config> <bean id="aBean" class="..."> ... </bean>
3.配置切入点Pointcut
- 在AOP中通知Advice和一个切入点表达式关联
(1)pointcut(都支持)
- execution(public * *(..)) 切入点为执行所有public方法时
- execution(* set*(..)) 切入点为执行所有set开始的方法时
- execution(* com.xyz.service.AccountService.*(..)) 切入点为执行AccountService类中的所有方法时
- execution(* com.xyz.service..(..)) 切入点为执行com.xyz.service包下的所有方法时
- execution(* com.xyz.service...(..)) 切入点为执行com.xyz.service包及其子包下的所有方法时
(2)只支持Spring AOP
- target(com.xyz.service.AccountService) target用于匹配当前目标对象类型的执行方法
- args(java.io.Serializable) args用于匹配当前执行的方法传入的参数为指定类型的执行方法
- @target(org.springframework.transaction.annotation.Transactional)
- @within(org.springframework.transaction.annotation.Transactional)
- @annotation(org.springframework.transaction.annotation.Transactional)
- @args(com.xyz.security.Classified)
- bean(tradeService)
- bean(*Service)
(3)pointcut配置
方式一:匹配所有方法
<aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service..(..))"/>' </aop:config>
方式二:匹配单一方法
<aop:config> <aop:pointcut id="businessService" expression="com.xyz.myapp.SystemArchitecture.businessService()"/> </aop:config>
方法三:全部配置
<aop:config> <aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service..(..))"/> </aop:aspect> </aop:config>
4.Advice应用
(1)Before advice
方式一:
<aop:aspect id="beforeExmaple" ref="aBean"> <aop:before pointcut-ref="dataAccessOperation" method="doAccessCheck"/> </aop:aspect>
方式二:
<aop:aspect id="beforeExmaple" ref="aBean"> <aop:before pointcut="execution(* com.xyz.myapp.dao..(..))" method="doAccessCheck"/> </aop:aspect>
(2)After returning advice
方式一:
<aop:aspect id="afterReturningExmaple" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" method="doAccessCheck"/> </aop:aspect>
方式二:
<aop:aspect id="afterReturningExmaple" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" returning="retVal" method="doAccessCheck"/> </aop:aspect>
(3)After throwing advice
方式一:
<aop:aspect id="afterThrowingExmaple" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" method="doRecoveryActions"/> </aop:aspect>
方式二:使用throwing属性来指定可被传递的异常的参数名称
<aop:aspect id="afterThrowingExmaple" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" throwing="dataAccessEx" method="doRecoveryActions"/> </aop:aspect>
(4)After(finally) advice
<aop:aspect id="afterFinallyExmaple" ref="aBean"> <aop:after pointcut-ref="dataAccessOperation" method="doReleaseLock"/> </aop:aspect>
(5)Around advice
- 通知方法的第一个参数必须是ProceedingJoinPoint类型
<aop:aspect id="aroundExmaple" ref="aBean"> <aop:around pointcut-ref="businessService" method="doBasicProfiling"/> </aop:aspect>
doBasicProfiling方法的第一个参数必须是ProceedingJoinPoint类型
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable{ //start stopwatch Object retVal=pjp.proceed(); //stop stopwatch return retVal; }
(6)Advice parameters(通知中使用参数)
1)相关代码
public interface FooService{ Foo getFoo(String fooName,int age); } public class DefaultFooService implements FooService{ public Foo getFoo(String name,int age){ return new Foo(name,age); } }
public class SimpleProfiler{ public Object profile(ProceedingJointPoint call,String name,int age) throws Throwable{ StopWatch clock=new StopWatch("Profiling for "+name+"and "+age+""); try{ clock.start(call.toShortString()); return call.proceed(); }finally { clock.stop(); System.out.println(clock.prettyPrint()); } } }
2)相关配置
<!--this is the object thar will be proxied by Spring's AOP infrastructure 这是由Spring的AOP基础设施代理的对象--> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!--this is the actual advice itself 这是实际的通知本身--> <bean id="profiler" class="x.y.SimpleProfiler"/> <aop:config> <aop:aspect ref="profiler"> <aop:pointcut id="theExecutionOfSomeFooServiceMethod" expression="execution(* x.y.service.FooService.getFoo(String,int)) and args(name,age)"/> <aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod" method="profile"/> </aop:aspect> </aop:config>
5.Introductions应用
(1)Introductions概念
- 简介运行一个切面声明一个实现指定接口的通知对象,并且提供了一个接口实现类来代表这些对象
- 由<aop:aspect>中<aop:declare-parents>元素声明该元素用于声明所匹配的类型拥有一个新的parent(因此得名)
<aop:aspect id="usageTrackerAspect" ref="usageTracking"> <!--type-matching:匹配类型(也是expression表达式) implement-interface:具体指定的接口 default-impl:接口的实现类--> <aop:declare-parents type-matching="com.xyz.myapp.service.*+" implement-interface="com.xyz.myapp.service.tracking.UsageTracked" default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/> <aop:before pointcut="com.xyz.myapp.SystemArchitecture.businessService() and this(usageTracked)" method="recordUsage"/> </aop:aspect>
public void recordUsage(UsageTracked usageTracked){ usageTracked.incrementUseCount(); }
所以能做下面这种强制的类型转换
UsageTracked usageTracked=(UsageTracked) context.getBean("myService");
(2)Aspect instantiation models
- schema-defined aspects(所有基于配置文件的切面)只支持singleton model(单例模式)
6.Advisors
- advisor就像一个小的自包含的方面,只有一个advice
- 切面自身通过一个bean表示,并且必须实现某个advice接口,同时,advisor也可以很好的利用AspectJ的切入点表达式
- Spring通过配置文件中<aop:advisor>元素支持advisor实际使用中,大多数情况下它会和transactional advice(事务相关的通知)配合使用
- 为了定义一个advisor的优先级以便让advice有序,可以使用order属性来定义advisor的顺序
<aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service..(..))"/> <aop:advisor pointcut-ref="businessService" advice-ref="tx-advice"/> </aop:config> <tx:advice id="tx-advice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
7.AOP API的pointcut、advice概念及应用(工作中很少使用,了解即可)
(1)pointcut
- NameMatchMethodPointcut:根据方法名字进行匹配
- 成员变量:mappedNames,匹配的方法名集合
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut"> <property name="mappedNames"> <list> <!--sa开头的所有方法--> <value>sa*</value> </list> </property> </bean>
(2)advice
1)Before advice
- 一个简单的通知类型
- 只是在进入方法之前被调用,不需要MethodInvocation对象,
- 前置通知可以在连接点执行之前插入自定义行为,但不能改变返回值
public interface MethodBeforeAdvice extends BeforeAdvice{ void before(Method m,Object[] args,Object target) throws Throwable; }
public class CountingBeforeAdvice implements MethodBeforeAdvice{ private int count; public void before(Method m,Object[] args,Object target) throws Throwable{ ++count; } public int getCount() { return count; } }
2)Throws advice
- 如果连接点抛出异常,throws advice在连接点返回后被调用
- 如果throws-advice的方法抛出异常,那么它将覆盖原有异常
- 接口org.springframework.aop.ThrowsAdvice不包含任何方法,仅仅是一个声明,实现类需要实现类似下面的方法:
- void afterThrowing([Method,args,target],ThrowableSubclass); //前三个是可选的,exception是必须的
3)After returning advice
- 后置通知必须实现org.springframework.aop.AfterReturningAdvice接口
public class CountingAfterReturningAdvice implements AfterReturningAdvice{ private int count; public void afterReturning(Object returnValue,Method m,Object[] args,Object target) throws Throwable{ ++count; } public int getCount() { return count; } }
- 可以访问返回值(但不能进行修改)、被调用的方法、方法的参数和目标
- 如果抛出异常,将会抛出拦截器链,替代返回值
4)Interception around advice
- Spring的切入点模型是的切入点可以独立于advice重用,已针对不同的advice可以使用相同的切入点
public interface MethodInterceptor extends Interceptor{ Object invoke(MethodInvocation invocation) throws Throwable; }
public class DebugInterceptor implements MethodInterceptor{ public Object invoke(MethodInvocation invocation) throws Throwable{ System.out.println("Before:invocation=[*"+invocation+"]"); Object rval=invocation.proceed(); System.out.println("Invocation returned"); return rval; } }
5)Introduction advice
- Spring把引入通知作为一种特殊的拦截通知
- 需要IntroductionAdvisor和IntroductionInterceptor
- 仅适用于类,不能喝任何切入点一起使用
public interface IntroductionInterceptor extends MethodInterceptor{ boolean implementsInterface(Class intf); }
public interface IntroductionAdvisor extends Advisor,IntroductionInfo{ ClassFilter getClassFilter(); void validateInterfaces() throws IllegalArgumentException; } public interface IntroductionInfo{ Class[] getInterfaces(); }
- 一个Spring test suite的例子
- 如果调用lock()方法,希望所有的setter方法抛出LockedException异常(如使物体不可变,AOP典型例子)
- 需要一个完成繁重任务的IntroductionInterceptor,这种情况下,可以使用org.springframework.aop.support.DelegatingIntroductionInterceptor
public interface Lockable{ void lock(); void unlock(); boolean locked(); }
IntroductionInterceptor实现类:
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable{ private boolean locked; public void lock(){ this.locked=true; } public void unlock(){ this.locked=false; } public boolean locked(){ return this.locked; } public Object invoke(MethodInvocation invocation) throws Throwable{ if(locked() && invocation.getMethod().getName().indexOf("set")==0){ throw new LockedException(); } return super.invoke(invocation); } }
IntroductionInterceptor实现类:持有独立的LockMixin实例
public class LockMixinAdvisor extends DefaultIntroductionAdvisor{ public LockMixinAdvisor(){ super(new LockMixin(),Lockable.class); } }
(3)Advisor API in Spring
- Advisor是仅包含一个切入点表达式关联的单个通知的方面
- 除了introductions,advisor可以用于任何通知
- org.springframework.aop.support.DefaultPointcutAdvisor是最常用的advisor类,它可以与MethodInterceptor,BeforeAdvice或者ThrowsAdvice一起使用
- 它可以混合在Spring同一个AOP代理的advisor和advice
8.ProxyFactoryBean及相关内容
(1)ProxyFactoryBean相关概念
- 创建Spring AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryFean
- 这可以完全控制切入点和通知(advice)以及他们的顺序
- foo引用的不是ProxyFactoryBean实例本身,而是ProxyFactoryBean实现里getObject()方法创建的对象
- getObject方法将创建一个AOP代理包装一个目标对象
- 使用ProxyFactoryBean或者其他IoC相关类来创建AOP代理的最重要好处是通知和切入点也可以由IoC来管理
- 被代理类没有实现任何接口,使用CGLIB代理,否则JDK代理
- 通过设置proxyTargetClass为true,可强制使用CGLIB(无论是否实现了接口)
- 如果目标类实现了一个(或者多个)接口,那么创建代理的类型将依赖ProxyFactoryBean的配置
- 如果ProxyFactoryBean的proxyInterfaces属性被设置为一个或者多个全限定接口名,基于JDK的代理将被创建
- 如果ProxyFactoryBean的proxyInterfaces属性没有被设置,但是目标类实现了一个(或者更多)接口,那么ProxyFactoryBean将自动检测到这个目标类已经实现了至少一个接口,创建一个基于JDK的代理
(2)Proxying interfaces
<bean id="personTarget" class="com.mycompany.PersonImpl"> <property name="name" value="Tony"/> <property name="age" value="51"/> </bean> <bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty" value="Custom string property value"/> </bean> <bean id="debugInterceptor" class="com.springframework.aop.interceptor.DebugInterceptor"> </bean> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.mycompany.Person"/> <property name="target" ref="personTarget"/> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean>
<bean id="personUser" class="com.mycompany.PersonUser"> <property name="person"><ref bean="person"/></property> </bean>
- 使用匿名内部beabn来隐藏目标和代理之间的区别
<bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty" value="Custom.string.property value"/> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.mycompany.Person"/> <!--Use inner baen,not local reference to target--> <property name="target"> <bean class="com.mycompany.PersonImpl"> <property name="name" value="Tony"/> <property name="age" value="51"/> </bean> </property> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean>
(3)Proxying classes
- 前面的例子中如果没有Person接口,这种情况下Spring会使用CGLIB代理,而不是JDK动态代理
- 如果想,可以强制在任何情况下使用CGLIB,即使有接口
- CGLIB代理的工作原理是在运行时生成目标类的子类,Spring配置这个生成的子类委托方法调用到原来的目标
- 子类是用来实现Decorator模式,织入通知
- CGLIB的代理对用户是透明的,需要注意:
- final方法不能被通知,因为它们不能被覆盖
- 不用把CGLIB添加到classpath中,在Spring3.2中,CGLIB被重新包装并包含在Spring核心的JAR(即基于CGLIB的AOP就像JDK动态代理一样“开箱即用”)
(4)使用global advisors
- 用*做通配,匹配所有拦截器(interceptor)加入通知链
<bean id="proxy" class="org.springframework.aoop.framework.ProxyFactoryBean"> <property name="target" ref="service"/> <property name="interceptorNames"> <list> <value>global*</value> </list> </property> </bean> <bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
(5)简化的proxy定义
- 使用父子bean定义,以及内部bean定义,可能会带来更清洁和更简洁的代理定义(抽象属性标记父bean定义为抽象的这样它不能被实例化)
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
简化后
<bean id="myService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MyServiceImpl"> </bean> </property> </bean>
<bean id="mySpecialServie" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MySpecialServiceImpl"> </bean> </property> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="store*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
(6)使用ProxyFactory
- 使用Spring AOP而不必依赖于Spring IoC
ProxyFactory factory=new ProxyFactory(myBusinessInterfaceImpl); factory.addAdvice(myMethodInterceptor); factory.addAdvisor(myAdvisor); MyBusinessInterface tb=(MyBusinessInterface) factory.getProxy();
- 大多数情况下最佳实践是用IoC容器创建AOP代理
- 虽然可以硬编码方式实现,但是Spring推荐使用配置或注解方式实现
(7)使用"auto-proxy"
- Spring也允许使用"自动代理"的bean定义,它可以自动代理选定的bean,这是建立在Spring的“bean post processor”功能基础上的(在加载bean的时候就可以修改)
- BeanNameAutoProxyCreator
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames" value="jdk*,onlyJdk"/> <property name="interceptorNames"> <list> <value>myInterceptor</value> </list> </property> </bean>
- DefaultAdvisorAutoProxyCreator,当前IoC容器中自动应用,不用显示声明引用advisor的bean定义
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transacitonInterceptor"/> </bean> <bean id="customAdvisor" class="com.mycompany.MyAdvisor"/> <bean id="businessObject1" class="com.mycompany.BusinessObject1"> <!--Properties omitted--> </bean> <bean id="businessObject2" class="com.mycompany.BusinessObject2"/>