AOP术语
- 连接点(Joinpoint)
程序执行的某个特定位置:类开始初始化前,类初始化后,类某个方法调用前,调用后,方法抛出异常后等等。连接点由两个信息确定:第一是用方法表示的程序执行点,第二十咏相对点表示的方位。如在Foo.bar()方法执行前的连接点,执行点是Foo.bar(),方位为该方法执行前。需要知道在系统的哪些执行点上进行织入操作,这些将要在其之上进行织入操作的系统执行点就是Joinpoint。
- 切点(Pointcut)
Pointcut概念代表的是Joinpoint的表述方式。将横切逻辑织入当前系统的过程中,需要参照Pointcut规定的Joinpoint信息,才可以知道应该往系统的哪些Joinpoint上织入横切逻辑。
也就是特定连接点。连接点相当于数据库中的记录,切点相当于查询条件。一个切点可以匹配多个连接点。
- 增强(Advice)
增强是织入到目标类连接点上的一段程序代码。增强还有一个执行点的方位。例如BeforeAdvice表示方法调用前的位置。只有结合切点和增强两者一起才能确定特定的连接点并实施增强逻辑。
- 目标对象(target)
增强逻辑的织入目标类。
- 引介(Introduction)
是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过引介功能,可以动态为该类添加接口实现逻辑,让业务类成为这个接口的实现类。
- 织入(Weaving)
织入市将增强添加到目标类具体连接点上的过程。
- 代理(Proxy)
一个类被AOP织入增强后,就产出一个结果类,它是融合了原类和增强逻辑的代理类。
- 切面(Aspect)
切面由切点和增强组成。
AOP的工作重心在于如何增强应用于目标对象的连接点上,通过切点和增强定位到连接点上,在增强中编写面向切面的代码。
Pointcut接口
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); Pointcut TRUE
创建Advice类
- 增强类型
- 前置增强(Before Advice):因为Spring只支持方法级的增强,所以MethodBeforeAdvice是目前可用的前置增强。
- 后置增强(After Advice):可以细分为三类:
- AfterReturningAdvice,在目标方法执行后实施增强。
- After throwing Advice: 只有执行过程中抛出异常才会执行。
- After finally advice: 不管Joinpoint处执行流程是正常终了还是抛出异常都会执行,就好像finally块一样。
- 环绕增强(Around Advice):MethodInterceptor,表示在目标方法执行前后实施增强。
- 异常抛出增强:ThrowsAdvice,表示在目标方法抛出异常后实施增强。
- 引介增强:IntroductionInterceptor,表示在目标类中添加一些新的方法和属性。
- 前置增强 (Before Advice)
示例:
Waiter.java
package com.ivy.aop.advice; public interface Waiter { void greetTo(String name); void serveTo(String name); }
NaiveWaiter.java
package com.ivy.aop.advice; public class NaiveWaiter implements Waiter{ @Override public void greetTo(String name) { System.out.println("greet to " + name + "..."); } @Override public void serveTo(String name) { System.out.println("serve to " + name + "..."); } }
GreetingBeforeAdvice.java
package com.ivy.aop.advice; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class GreetingBeforeAdvice implements MethodBeforeAdvice{ @Override public void before(Method method, Object[] args, Object obj) throws Throwable { String clientName = (String)args[0]; System.out.println("How are you! Mr." + clientName); } }
TestBeforeAdvice.java
package com.ivy.aop.advice; import org.springframework.aop.BeforeAdvice; import org.springframework.aop.framework.ProxyFactory; public class TestBeforeAdvice { public static void main(String[] args) { // TODO Auto-generated method stub Waiter target = new NaiveWaiter(); BeforeAdvice advice = new GreetingBeforeAdvice(); // Spring提供的代理工厂 ProxyFactory pf = new ProxyFactory(); // 设置代理目标 pf.setTarget(target); // 为代理目标添加增强 pf.addAdvice(advice); // 生成代理实例 Waiter proxy = (Waiter)pf.getProxy(); proxy.greetTo("John"); proxy.serveTo("Tom"); } }
运行结果:
log4j:WARN No appenders could be found for logger (org.springframework.aop.framework.Cglib2AopProxy). log4j:WARN Please initialize the log4j system properly. How are you! Mr.John greet to John... How are you! Mr.Tom serve to Tom...
剖析ProxyFactory
ProxyFactory内部就是使用JDK或者CGLib代理的技术,将增强应用到目标类中。如果通过ProxyFactory的setInterfaces(Class[] interfaces)指定针对接口进行代理,ProxyFactory就是使用JDK代理,如果是针对类的代理,则使用CGLib,此外,还可以通过ProxyFactory的setOptimize(true)方法,让ProxyFactory启动优化代理方式,这样针对接口的代理也会使用CGLib.值得注意的一点是,使用CGLib,必须引入CGLib类库。
ProxyFactory通过addAdvice(Advice)添加一个增强,用户可以使用该方法添加多个增强,多个增强形成一个增强链,它们的调用顺序和添加顺序一致。可以通过addAdvice(int, Advice)将增强添加到增强链的具体位置(第一个位置是0)。
在Spring中配置
beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="greetingAdvice" class="com.ivy.aop.advice.GreetingBeforeAdvice"/> <bean id="target" class="com.ivy.aop.advice.NaiveWaiter"/> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.ivy.aop.advice.Waiter" p:interceptorNames="greetingAdvice" p:target-ref="target"/> </beans>
解释:
target: 代理的目标对象
proxyInterfaces: 代理所要实现的接口,可以是多个接口。
interceptorNames: 需要织入目标对象的Bean列表。这些Bean必须是实现了MethodInterceptor或Advice,配置中的顺序对应调用的顺序。
singleton: 返回的代理是否是单例。
optimize: 当设置为true时,强制使用CGLib.
proxyTargetClass: 是否对类进行代理(而不是对接口进行代理),设置为true时,使用CGLib代理。
使用CGLib后的beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="greetingBefore" class="com.ivy.aop.advice.GreetingBeforeAdvice"/> <bean id="target" class="com.ivy.aop.advice.NaiveWaiter"/> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingBefore" p:target-ref="target" p:proxyTargetClass="true"/> </beans>
将proxyTargetClass设置为true后,无须再设置批roxyInterfaces属性,即使设置也会被ProxyFactoryBean忽略。
测试代码:
String configPathString="com/ivy/aop/beforeadvice/beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPathString); Waiter waiter = (Waiter)ctx.getBean("waiter"); waiter.greetTo("John");
运行结果:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. How are you! Mr.John greet to John...
- 后置增强 (After Advice)
只有方法正常返回的情况下,AfterReturningAdvice才会执行。另外,虽然Spring的AfterReturningAdvice可以访问到方法的返回值,但不可以更改返回值。不过Around Advice可以做到。
GreetingAfterAdvice.java
package com.ivy.aop.advice; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class GreetingAfterAdvice implements AfterReturningAdvice{ @Override public void afterReturning(Object returnObject, Method method, Object[] args, Object obj) throws Throwable { System.out.println("Please enjoy yourself"); } }
将greetingAfterAdvice添加到配置文件中
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="greetingBefore" class="com.ivy.aop.advice.GreetingBeforeAdvice"/> <bean id="greetingAfter" class="com.ivy.aop.advice.GreetingAfterAdvice"/> <bean id="target" class="com.ivy.aop.advice.NaiveWaiter"/> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingBefore,greetingAfter" p:target-ref="target" p:proxyTargetClass="true"/> </beans>
interceptorNames是String[]类型,它接受增强Bean的名称,而非增强Bean的实例。
运行测试代码,结果如下:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
How are you! Mr.John
greet to John...
Please enjoy yourself
- 环绕增强(Around Advice)
Around Advice几乎可以替代上边所有的Advice
package com.ivy.aop.advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class GreetingInterceptor implements MethodInterceptor{ @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object[] args = invocation.getArguments(); String clientName = (String)args[0]; System.out.println("How are you! Mr." + clientName); Object obj = invocation.proceed(); System.out.println("Please enjoy yourself!"); return obj; } }
MethodInvocation不但封装目标方法及其入参数组,还封装了目标方法所在的实例对象,通过getArguments可以获取目标方法的入参数组,通过proceed()反射调用目标实例相应的方法。
使用环绕增强替换前置和后置增强
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- <bean id="greetingBefore" class="com.ivy.aop.advice.GreetingBeforeAdvice"/> <bean id="greetingAfter" class="com.ivy.aop.advice.GreetingAfterAdvice"/> --> <bean id="greetingInterceptor" class="com.ivy.aop.advice.GreetingInterceptor"/> <bean id="target" class="com.ivy.aop.advice.NaiveWaiter"/> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingInterceptor" p:target-ref="target" p:proxyTargetClass="true"/> </beans>
- 异常抛出增强(Throws Advice)
抛出异常的NaiveWaiter.java
package com.ivy.aop.advice; public class NaiveWaiter implements Waiter{ @Override public void greetTo(String name) { System.out.println("greet to " + name + "..."); throw new RuntimeException("runtime exception"); } @Override public void serveTo(String name) { System.out.println("serve to " + name + "..."); } }
GreetingThrowsAdvice.java
package com.ivy.aop.advice; import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class GreetingThrowsAdvice implements ThrowsAdvice{ public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable { System.out.println("------------"); System.out.println("method:" + method.getName()); System.out.println("exception:" + ex.getMessage()); System.out.println("Successfully rollback"); } }
ThrowsAdvice接口没有定义任何方法,只是个标识接口,在运行期Spring使用反射的机制自行判断,我们必须采用以下签名形式定义异常抛出的增强方法:
void afterThrowing(Method method, Object[] args, Object target, Throwable);
前三个参数可选,最后一个参数必须是Throwable或者其子类。如afterThrowing(SQLException e), afterThrowing(RuntimeException e), afterThrowing(Method method, Object[] args, Object target, RuntimeException e) 都是合法的。可以在同一个异常抛出增强中定义多个afterThrowing(), 当目标类方法抛出异常时,Spring会自动选用最匹配的增强方法。例如afterThrowing(SQLException e), afterThrowing(RuntimeException e), 当目标方法抛出一个SQLException时,将调用afterThrowing(SQLException e)进行增强,在类的继承树上,两个类的距离越近,这两个类的相似度越高,目标方法抛出异常后,优先选取拥有异常入参和抛出的异常相似度最高的afterThrowing()方法。
异常抛出增强配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- <bean id="greetingBefore" class="com.ivy.aop.advice.GreetingBeforeAdvice"/> <bean id="greetingAfter" class="com.ivy.aop.advice.GreetingAfterAdvice"/> <bean id="greetingInterceptor" class="com.ivy.aop.advice.GreetingInterceptor"/> --> <bean id="greetingThrows" class="com.ivy.aop.advice.GreetingThrowsAdvice"/> <bean id="target" class="com.ivy.aop.advice.NaiveWaiter"/> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingThrows" p:target-ref="target" p:proxyTargetClass="true"/> </beans>
运行测试代码,结果如下:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
greet to John...
------------
method:greetTo
exception:runtime exception
Successfully rollback
- 引介增强(Introduction)
Introduction不会在目标类所有对象实例之间共享,而是会为不同的实例对象保存它们各自的状态以及相关逻辑。它可以在不改动目标类定义的情况下,为目标类添加新的属性及行为。
引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,不是方法级别的。Spring定义了引介增强接口IntroductionInterceptor,该接口没有定义任何的方法,Spring为该接口提供了DelegatingIntroductionInterceptor实现类,一般情况下,我们通过扩展该实现类定义自己的引介增强类。
Monitorable接口
package com.ivy.aop; public interface Monitorable { void setMonitorActive(boolean active); }
ControllablePerformanceMonitor.java
package com.ivy.aop; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.DelegatingIntroductionInterceptor; public class ControllablePerformanceMonitor extends DelegatingIntroductionInterceptor implements Monitorable{ private ThreadLocal<Boolean> monitorStatusMap = new ThreadLocal<>(); @Override public void setMonitorActive(boolean active) { monitorStatusMap.set(active); } public Object invoke(MethodInvocation mi) throws Throwable { Object obj = null; if (monitorStatusMap.get()!=null && monitorStatusMap.get()) { PerformanceMonitor.begin(mi.getClass().getName() + "." + mi.getMethod().getName()); obj = super.invoke(mi); PerformanceMonitor.end(); } else { obj = super.invoke(mi); } return obj; } }
ControllablePerformanceMonitor在扩展DelegatingIntroductionInterceptor的同时,还必须实现Monitorable接口,提供接口方法的实现。
配置信息:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="pmonitor" class="com.ivy.aop.ControllablePerformanceMonitor"/> <bean id="target" class="com.ivy.aop.ForumServiceImpl"/> <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean" p:interfaces="com.ivy.aop.Monitorable" p:target-ref="target" p:interceptorNames="pmonitor" p:proxyTargetClass="true"/> </beans>
引介增强的配置与一般配置有较大的区别:首先,需要制定引介增强所实现的接口,即Monitorable接口;其次,由于只能通过为目标类创建子类的方式生成引介增强的代理(不能使用JDK生成代理),所以必须将proxyTargetClass设置为true。
测试代码:
package com.ivy.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestForumServiceByXml { public static void main(String[] args) { String configPath = "com/ivy/aop/beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); ForumService forumService = (ForumService)ctx.getBean("forumService"); forumService.removeForum(10); forumService.removeTopic(1022); Monitorable monitorable = (Monitorable)forumService; monitorable.setMonitorActive(true); forumService.removeForum(10); forumService.removeTopic(1022); } }
运行结果:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. remove forum record : 10 remove topic record : 1022 begin monitor... remove forum record : 10 end monitor... org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeForum cost 40ms. begin monitor... remove topic record : 1022 end monitor... org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeTopic cost 20ms.
- 创建切面Aspect
在介绍增强时,我们可能注意到:增强被织入到目标类的所有方法中,假设我们希望有选择地织入到目标类某些特定的方法中,就需要使用切点进行目标连接点的定位了。增强提供了连接点方位信息:如织入到方法前面/后面等,而切点进一步描述织入到哪些类的哪些方法上。 Spring通过Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。
切面类型
Spring使用Advisor接口表示切面概念,一个切面同时包含横切代码和连接点信息。切面分为三类:一般切面/切点切面和引介切面。
- Advisor:一般切面,仅包含一个Advice。因为Advice包含了横切代码和连接点的信息,所以Advice本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面台宽泛,所以一般不会直接使用。
- PointcutAdvisor:代表具有切点的切面,包含Advice和Pointcut两个类,这样,就可以通过类/方法名以及方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面。
- IntroductionAdvisor: 代表引介切面。是对引介增强的特殊的切面,它应用于类层面上,所以引介切点使用ClassFilter进行定义。
注意:Advisor代表Spring中的Aspect,但是与正常的Aspect不同,Advisor通常只持有一个Pointcut和一个Advice。而理论上,Aspect定义中可以有多个Pointcut和多个Advice,所以,可以认为Advisor是一种特殊的Aspect。
静态普通方法名匹配切面
Waiter.java
package com.ivy.aop.advisor; public class Waiiter { public void greetTo(String name) { System.out.println("waiter greet to " + name + "..."); } public void serveTo(String name) { System.out.println("waiter serves to " + name + "..."); } }
Seller.java
package com.ivy.aop.advisor; public class Seller { public void greetTo(String name) { System.out.println("seller greet to " + name + "..."); } }
定义增强 BeforeAdvice.java
package com.ivy.aop.advisor; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class BeforeAdvice implements MethodBeforeAdvice{ public void before(Method method, Object[] args, Object obj) throws Throwable { System.out.println(obj.getClass().getName() + "." + method.getName()); String clientName = (String)args[0]; System.out.println("How are you! Mrs." + clientName); } }
定义切面 GreetingAdvisor.java
package com.ivy.aop.advisor; import java.lang.reflect.Method; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor{ @Override public boolean matches(Method method, Class<?> clazz) { // TODO Auto-generated method stub return "greetTo".equals(method.getName()); } public ClassFilter getClassFilter() { return new ClassFilter() { @Override public boolean matches(Class<?> clazz) { // TODO Auto-generated method stub return Waiiter.class.isAssignableFrom(clazz); } }; } }
因为StaticMethodMatcherPointcutAdvisor抽象类唯一需要定义的是matches()方法,在默认情况下,该切面匹配所有的类。这样,通过覆盖getClassFilter()方法,让它仅匹配Waiter类及其子类。
配置文件如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="waiterTarget" class="com.ivy.aop.advisor.Waiiter"/> <bean id="sellerTarget" class="com.ivy.aop.advisor.Seller"/> <bean id="beforeAdvice" class="com.ivy.aop.advisor.BeforeAdvice"/> <bean id="greetingAdvisor" class="com.ivy.aop.advisor.GreetingAdvisor" p:advice-ref="beforeAdvice"/> <bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true"/> <bean id="waiter" parent="parent" p:target-ref="waiterTarget"/> <bean id="seller" parent="parent" p:target-ref="sellerTarget"/> </beans>
将beforeAdvice增强装配到greetingAdvisor的切面中。StaticMethodMatcherPointcutAdvisor除了advice-ref属性外,还可以定义另外两个属性:
classFilter: 类匹配过滤器,这个例子中我们用编码的方式设定了classFilter.
order: 切面织入时的顺序。
测试代码:
package com.ivy.aop.advisor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestAdvisor { public static void main(String[] args) { // TODO Auto-generated method stub String configPath = "com/ivy/aop/advisor/beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiiter waiter = (Waiiter)ctx.getBean("waiter"); Seller seller = (Seller)ctx.getBean("seller"); waiter.greetTo("John"); waiter.serveTo("John"); seller.greetTo("John"); } }
运行结果:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. com.ivy.aop.advisor.Waiiter.greetTo How are you! Mrs.John waiter greet to John... waiter serves to John... seller greet to John...
静态正则表达式方法匹配切面
RegexpMethodPointcutAdvisor是正则表达式方法匹配的切面实现类,该类已经功能齐备,一般情况下,无需扩展该类。
配置xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="waiterTarget" class="com.ivy.aop.advisor.Waiiter"/> <bean id="sellerTarget" class="com.ivy.aop.advisor.Seller"/> <bean id="beforeAdvice" class="com.ivy.aop.advisor.BeforeAdvice"/> <bean id="greetingAdvisor" class="com.ivy.aop.advisor.GreetingAdvisor" p:advice-ref="beforeAdvice"/> <bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true"/> <bean id="waiter" parent="parent" p:target-ref="waiterTarget"/> <bean id="seller" parent="parent" p:target-ref="sellerTarget"/> <bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" p:advice-ref="beforeAdvice"> <property name="patterns"> <list> <value>.*greet.*</value> </list> </property> </bean> <bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="regexpAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true"/> </beans>
RegexpMethodPointcutAdvisor 除了例子中使用的patterns和advice-ref外,还有两个属性:
pattern: 如果只有一个匹配模式串,可以使用该属性进行配置,patterns用于定义多个匹配模式串,这些匹配模式串之间是“或”的关系。
order: 切面在织入时对应的顺序。
测试代码:
String configPath = "com/ivy/aop/advisor/beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiiter waiter1 = (Waiiter)ctx.getBean("waiter1"); waiter1.greetTo("John"); waiter1.serveTo("John");
运行结果:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. com.ivy.aop.advisor.Waiiter.greetTo How are you! Mrs.John waiter greet to John... waiter serves to John...
动态切面
就是静态切面加上对动态参数的匹配。原先提供了DynamicMethodMatcherPointcutAdvisor抽象类,但是它和其他类有重叠,因此该类已经过期。不过可以使用DefaultPointcutAdvisor和DynamicMethodMatcherPointcut来完成相同的功能。
DynamicMethodMatcherPointcut是一个抽象类,其子类就是一个动态切点,该抽象类默认匹配所有的类和方法,因此需要通过扩展该类编写符合要求的动态切点。
GreetingDynamicPointcut.java
package com.ivy.aop.advisor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.DynamicMethodMatcherPointcut; public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut{ private static List<String> specialClientList = new ArrayList<>(); static { specialClientList.add("John"); specialClientList.add("Tom"); } // 对类进行静态切点检查 public ClassFilter getClassFilter() { return new ClassFilter() { @Override public boolean matches(Class<?> clazz) { System.out.println("getClassFilter staitc check for : " + clazz.getName()); return Waiiter.class.isAssignableFrom(clazz); } }; } //对方法进行静态切点检查 public boolean matches(Method method, Class<?> clazz) { System.out.println("matches(Method, clazz) static check for : " + clazz.getName() + "." + method.getName()); return "greetTo".equals(method.getName()); } //对方法进行动态切点检查 @Override public boolean matches(Method method, Class<?> clazz, Object[] args) { System.out.println("matches(Method, class, args) dynamic check for : " + clazz.getName() + "." + method.getName()); String clientName = (String)args[0]; return specialClientList.contains(clientName); } }
Spring采用这样的机制: 在创建代理时对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则在运行时就不再进行动态检查了;如果静态切点检查是匹配的,在运行时才进行动态切点检查。
配置文件
<bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" p:advice-ref="beforeAdvice"> <property name="pointcut"> <bean class="com.ivy.aop.advisor.GreetingDynamicPointcut"></bean> </property> </bean> <bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="dynamicAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true"/>
测试代码:
String configPath = "com/ivy/aop/advisor/beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiiter waiter1 = (Waiiter)ctx.getBean("waiter2"); waiter1.greetTo("John"); waiter1.serveTo("John"); waiter1.greetTo("Peter"); waiter1.serveTo("Peter");
运行结果:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. getClassFilter staitc check for : com.ivy.aop.advisor.Waiiter matches(Method, clazz) static check for : com.ivy.aop.advisor.Waiiter.greetTo getClassFilter staitc check for : com.ivy.aop.advisor.Waiiter matches(Method, clazz) static check for : com.ivy.aop.advisor.Waiiter.serveTo getClassFilter staitc check for : com.ivy.aop.advisor.Waiiter matches(Method, clazz) static check for : com.ivy.aop.advisor.Waiiter.clone getClassFilter staitc check for : com.ivy.aop.advisor.Waiiter matches(Method, clazz) static check for : com.ivy.aop.advisor.Waiiter.toString getClassFilter staitc check for : com.ivy.aop.advisor.Waiiter matches(Method, clazz) static check for : com.ivy.aop.advisor.Waiiter.greetTo matches(Method, class, args) dynamic check for : com.ivy.aop.advisor.Waiiter.greetTo com.ivy.aop.advisor.Waiiter.greetTo How are you! Mrs.John waiter greet to John... getClassFilter staitc check for : com.ivy.aop.advisor.Waiiter matches(Method, clazz) static check for : com.ivy.aop.advisor.Waiiter.serveTo waiter serves to John... matches(Method, class, args) dynamic check for : com.ivy.aop.advisor.Waiiter.greetTo waiter greet to Peter... waiter serves to Peter...
Spring会在创建代理织入切面时,对目标类中所有方法进行静态切点检查;在生成织入切面的代理对象后,第一次调用代理类的每个方法都回进行一次静态检查,如果本次检查能将该方法派出,以后对该方法的调用就不再执行动态切点检查了。所以,在定义动态切点时,切勿忘记同时覆盖getClassFilter()和matches(Method method, Class clazz),通过静态切点检查排除掉大部分方法。
流程切面
Spring的流程切面由DefaultPointcutAdvisor和ControlFlowPointcut实现。流程切点代表某个方法直接或间接发起调用的其他方法。
示例:
假设有个WaiterDelegate代理Waiter所有的方法:
package com.ivy.aop.advisor; public class WaiterDelegate { private Waiiter waiiter; public void service(String clientName) { waiiter.greetTo(clientName); waiiter.serveTo(clientName); } public void setWaiiter(Waiiter waiter) { this.waiiter = waiter; } }
如果希望所有由WaiterDelegate#service()方法发起调用的其他方法(greetTo和serveTo)都织入GreetingBeforeAdvice增强,就必须使用流程切面完成目标了。
配置文件:
<bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut"> <constructor-arg type="java.lang.Class" value="com.ivy.aop.advisor.WiterDelegate"/> <constructor-arg type="java.lang.String" value="service"/> </bean> <bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" p:pointcut-ref="controlFlowPointcut" p:advice-ref="beforeAdvice"/> <bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="controlFlowAdvisor" p:target-ref="waiterTarget" p:proxyTargetClass="true"/>
测试代码:
String configPath = "com/ivy/aop/advisor/beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiiter waiter = (Waiiter)ctx.getBean("waiter3"); WaiterDelegate wd = new WaiterDelegate(); wd.setWaiiter(waiter); waiter.greetTo("John"); waiter.serveTo("John"); wd.service("John");
运行结果:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. waiter greet to John... waiter serves to John... com.ivy.aop.advisor.Waiiter.greetTo How are you! Mrs.John waiter greet to John... com.ivy.aop.advisor.Waiiter.serveTo How are you! Mrs.John waiter serves to John...
直接通过waiter调用serveTo和greetTo,增强不会起作用。通过WaiterDelegate#service()调用Waiter的serveTo和greetTo就会发现这两个方法都织入了增强。
复合切点切面
假设我们希望由WaiterDelegate#service()发起调用且被调用的方法是Waiter#greetTo()时才织入增强,这个切点就变成复合切点,因为它由两个单独的切点共同确定,第一个切点是上例中的流程切点,而另外一个切点是greetTo的方法名切点。Spring提供的ComposablePointcut把两个切点组合起来,通过切点的复合运算表示。ComposablePointcut本身也是一个切点,有如下构造函数:
ComposablePointcut(): 构造一个匹配所有类所有方法的复合切点;
ComposablePointcut(ClassFilter classFilter): 构造一个匹配特定类所有方法的复合切点。
ComposablePointcut(MethodMatcher methodMatcher): 构造一个匹配所有类特定方法的复合切点。
ComposablePointcut(ClassFilter classFilter, MethodMatcher methodMatcher): 构造一个匹配特定类特定方法的复合切点。
ComposablePointcut提供了三个交集运算的方法:
ComposablePointcut intersection(ClassFilter filter): 将复合切点和一个ClassFilter对象进行交集运算。
ComposablePointcut intersection(MethodMatcher mm): 将复合切点和一个MethodMatcher对象进行交集运算。
ComposablePointcut intersection(Pointcut other): 将复合切点和一个切点对象进行交集运算。
ComposablePointcut提供了两个并集运算:
ComposablePointcut union(ClassFilter filter): 将复合切点和一个ClassFilter对象进行并集运算。
ComposablePointcut union(MethodMatcher mm): 将复合切点和一个MethodMatcher对象进行并集运算。
如果需要对两个切点进行并集运算,可以使用Pointcuts工具类,它由两个非常好用的静态方法:
Pointcut intersection(Pointcut a, Pointcut b): 对两个切点进行交集运算,返回一个结果切点,该切点就是ComposablePointcut对象的实例。
Pointcut union(Pointcut a, Pointcut b): 对两个切点进行并集运算,返回一个结果切点,该切点就是ComposablePointcut对象的实例。
自动创建代理
BeanNameAutoProxyCreator
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" p:beanNames="*Target" p:interceptorNames="beforeAdvice" p:optimize="true"/>
测试代码:
String configPath = "com/ivy/aop/advisor/beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiiter waiter = (Waiiter)ctx.getBean("waiterTarget"); Seller seller = (Seller)ctx.getBean("sellerTarget"); waiter.greetTo("John"); waiter.serveTo("John"); seller.greetTo("Peter");
运行结果:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. com.ivy.aop.advisor.Waiiter.greetTo How are you! Mrs.John waiter greet to John... com.ivy.aop.advisor.Waiiter.serveTo How are you! Mrs.John waiter serves to John... com.ivy.aop.advisor.Seller.greetTo How are you! Mrs.Peter seller greet to Peter...
Ordered的作用
Spring在处理同一Joinpoint处的多个Advisor的时候,实际上会按照指定的顺序和优先级来执行,如果不明确指定各个Advisor的执行顺序,那么Spring会按照它们的声名顺序来应用它们,最先声名的顺序号最小但优先级最大。由于各个Advisor实现类都实现了Ordered接口,所以我们直接在配置时指定顺序号即可:
<bean id="pemissionAuthAdvisor" class="...PermissionAuthAdvisor"> <property name="order" value="1" /> ... </bean> <bean id="exceptionBarrierAdvisor" class="...ExceptionBarrierAdvisor"> <property name="order" value="0" /> ... </bean>
这样exceptionBarrierAdvisor就会先执行。