aop是面向切面编程(aspect oriented programing)的简称。aop的出现并非要彻底替代oop,仅是做为oop的有益补充。
aop的应用场合是有限的,通常只适合于那些具备横切逻辑的应用场合。java
性能监测正则表达式
访问控制spring
事务管理数据库
日志记录
...编程
一个类或一段程序代码拥有一些具备边界
性质的特定点,这些代码中的特定点就称为链接点
。好比,app
类开始初始化前,后ide
类中某个方法调用前,后oop
方法抛出异常后
...性能
链接点由两个信息肯定:测试
用方法表示的程序执行点
用相对点表示的方位
如在Test.foo()方法执行前的链接点,执行点为Test.foo(),方位为该方法执行前的位置。
spring使用切点对执行点定位,而方位则在加强类型中定义.
每一个程序类均可能有多个链接点,aop经过切点
定位特定点。类比于数据库查询:链接点至关于数据库中的记录,切点至关于查询条件。
切点和链接点不是一对一关系,一个切点能够匹配多个链接点。
切点只定位到某个方法上,若是但愿定位到具体的链接点上,还须要提供方位信息。
加强是织入到目标类链接点上的一段代码.它除用于描述一段代码外,还拥有另外一个和链接点相关的信息,这即是执行点的方位
。结合执行点方位信息和切点信息,就能够找到特定的链接点了。
spring提供的加强接口都是带方位名的
:BeforeAdvice,AfterReturningAdvice,ThrowsAdvice等。
加强逻辑的织入目标类。
引介是一种特殊的加强,它为类添加一些属性和方法。这样,即便一个业务类原来没有实现某个接口,经过引介,也能够动态的为业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
织入是将加强添加对目标类具体链接点的过程。aop有三种织入方式:
编译期织入,这要求使用特殊的java编译器
类装载期织入,这要求使用特殊的类装载器
动态代理织入,在运行期为目标类添加加强,生成子类
spring使用第3种方式织入,aspectj使用第1,2种方式。
一个类被aop织入加强后,就产生一个结果类,它融合了原来类和加强逻辑的代理类。咱们能够采用调用原来类相同的方式调用代理类。
切面由切点和加强(引介)组成,它既包括了横切逻辑的定义,也包括了链接点的定义。spring aop负责实施切面,它将切面所定义的横切逻辑织入到切面所指定的链接点钟。
场景:高级餐厅的服务员在回答顾客以前都会说'你好!...'.
public class Waiter { public void check(String name){ System.out.println("结帐?"+name); } public void serve(String name){ System.out.println("要点什么?"+name); } }
前置加强
import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class GreetAdvice implements MethodBeforeAdvice{ @Override public void before(Method method, Object[] args, Object obj)throws Throwable { String clientName=args[0].toString(); System.out.println("你好!"+clientName); } }
测试
import org.springframework.aop.BeforeAdvice; import org.springframework.aop.framework.ProxyFactory; public class TestBeforeAdvice { public static void main(String[] args){ Waiter target=new Waiter(); BeforeAdvice advice=new GreetAdvice(); ProxyFactory pf=new ProxyFactory();//spring提供的代理工厂 pf.setTarget(target);//设置代理目标 pf.addAdvice(advice);//添加加强 Waiter proxy=(Waiter)pf.getProxy();//代理实例 proxy.serve("TheViper"); proxy.check("TheViper"); } }
结果
你好!TheViper 来点什么?TheViper 你好!TheViper 结帐?TheViper
ProxyFactory
内部使用JDK代理或CGLib代理,将加强应用到目标类。
还能够将接口设置为代理目标。
... ProxyFactory pf=new ProxyFactory(); pf.setInterfaces(target,getClass().getInterfaces); pf.setTarget(target); ...
application-context.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='greetAdvice' class='com.GreetAdvice'></bean> <bean id='target' class='com.Waiter'></bean> <bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' p:target-ref='target' p:interceptorNames='greetAdvice'/> </beans>
测试
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestBeforeAdvice { public static void main(String[] args){ ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml"); Waiter waiter=(Waiter)ctx.getBean("waiter"); waiter.serve("TheViper"); waiter.check("TheViper"); } }
ProxyFactoryBean
经常使用配置:
target:代理的目标对象
proxyInterfaces:代理所要实现的接口,能够是多个接口。该属性还有一个别名属性interfaces
interceptorNames:须要植入目标对象的Bean列表。这些Bean必须是实现了org.aopalliance.intercept.MethodInterceptor
或org.springframework.aop.Advisor
的Bean,配置中的顺序对应调用的顺序。
singleton:返回的代理是否为单例,默认为单例
optimize:设置为true时,强制使用CGLib代理。对于singleton代理,推荐使用CGLib,对于其余做用域类型的代理,最好使用JDK代理。由于CGLib建立代理速度慢,而建立出的代理对象运行效率较高。JDK代理的表现与之相反
proxyTargetClass:是否对类进行代理(不是对接口进行代理),设置为true时,使用CGLib
从上面spring配置能够看到,ProxyFactoryBean
用的是JDK代理,若是将proxyTargetClass设置为true后,无需再设置proxyInterfaces属性,即便设置了也会被忽略。
场景:服务员和顾客交流后,礼貌的说'please enjoy yourself'.
后置加强
import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class GreetAfterAdvice implements AfterReturningAdvice{ @Override public void afterReturning(Object returnObj,Method method,Object[] args,Object obj)throws Throwable { //returnObj:目标实例方法返回的结果 method:目标类的方法 args:目标实例的方法参数 obj:目标类实例 System.out.println("please enjoy yourself!"); } }
spring配置
... <bean id='greetBeforeAdvice' class='com.GreetAdvice'></bean> <bean id='greetAfterAdvice' class='com.GreetAfterAdvice'></bean> <bean id='target' class='com.Waiter'></bean> <bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' p:target-ref='target' p:interceptorNames='greetBeforeAdvice'/> ...
结果
你好!TheViper 要点什么?TheViper please enjoy yourself! 你好!TheViper 结帐?TheViper please enjoy yourself!
环绕加强容许在目标类方法调用先后织入横切逻辑,它综合实现了前置,后置加强两种的功能。
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=args[0].toString(); System.out.println("你好!"+clientName); Object obj=invocation.proceed();//经过反射调用目标方法 System.out.println("please enjoy yourself!"); return obj; } }
... <bean id='greetingInterceptor' class='com.GreetingInterceptor'></bean> <bean id='target' class='com.Waiter'></bean> <bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' p:target-ref='target' p:interceptorNames='greetingInterceptor,greetAfterAdvice'/> ...
异常抛出加强最适合的场景是事务管理。
import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class TransactionManager implements ThrowsAdvice{ public void afterThrowing(Method method,Object[] args,Object target,Exception ex)throws Throwable{ System.out.println("method:"+method.getName()); System.out.println("抛出异常"+ex.getMessage()); System.out.println("回滚"); } }
引介加强不是在目标方法周围织入加强,而是为目标类建立新的方法和属性
。因此引介加强的链接点是类级别的,非方法级别.
前面织入加强时,都是织入到目标类的全部方法中。这节将会介绍如何让加强提供链接点方位的信息,如织入到方法前面,后面等,而切点进一步描述具体织入哪些类的哪些方法上。
spring经过org.springframework.aop.Pointcut接口
描述切点,Pointcut
由ClassFilter
和MethodMatcher
构成。
经过ClassFilter
定位到某些特定类上,经过MethodMatcher
定位到某些特定方法上。
此外,spring还提供注解切点和表达式切点,二者都使用AspectJ的切点表达式语言。
静态方法切点
动态方法切点
注解切点
表达式切点
流程切点
复合切点
StaticMethodMatcherPointcutAdvisor
表明一个静态方法匹配切面,它经过StaticMethodMatcherPointcut
定义切点,经过类过滤和方法名匹配定义切点。
例子,Seller类也有serve方法
public class Seller { public void serve(String name){ System.out.println("seller说:要点什么?"+name); } }
前置加强(advice)
public class GreetingBeforeAdvice implements MethodBeforeAdvice{ @Override public void before(Method method, Object[] args, Object obj)throws Throwable { String clientName=args[0].toString(); System.out.println("你好!"+clientName); } }
切面(advisor)
import java.lang.reflect.Method; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; public class GreetAdvisor extends StaticMethodMatcherPointcutAdvisor{ @Override public boolean matches(Method method, Class<?> cls) {//切点方法匹配 return "serve".equals(method.getName()); } //切点类匹配规则:Waiter的类或子类 public ClassFilter getClassFilter(){ return new ClassFilter(){ public boolean matches(Class cls){ return Waiter.class.isAssignableFrom(cls); } }; } }
spring配置
... <bean id='greetBeforeAdvice' class='com.GreetingBeforeAdvice'></bean> <bean id='waiterTarget' class='com.Waiter'></bean> <bean id='sellerTarget' class='com.Seller'></bean> <!-- 向切面注入前置加强 --> <bean id='greetingAdvisor' class='com.GreetAdvisor' p:advice-ref='greetBeforeAdvice'></bean> <bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='greetingAdvisor'/> <bean id='waiter' parent='parent' p:target-ref='waiterTarget'></bean><!-- waiter代理 --> <bean id='seller' parent='parent' p:target-ref='sellerTarget'></bean><!-- seller代理 --> ...
<bean id='parent' abstract='true' ...>
表示经过一个父<bean>定义公共的配置信息。
测试
ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml"); Waiter waiter=(Waiter)ctx.getBean("waiter"); Seller seller=(Seller)ctx.getBean("seller"); waiter.serve("TheViper"); waiter.check("TheViper"); seller.serve("TheViper");
结果
你好!TheViper waiter说:要点什么?TheViper waiter说:结帐?TheViper seller说:要点什么?TheViper
能够看到切面只是织入到Waiter.serve()方法调用前的链接点上,而Waiter.check()和Seller.serve()都没有织入切面。
RegexpMethodPointcutAdvisor
是正则表达式方法匹配的切面实现类。该类已是功能齐备的的实现类了,通常状况下,无需扩展该类。
... <bean id='regexpAdvisor' class='org.springframework.aop.support.RegexpMethodPointcutAdvisor' p:advice-ref='greetBeforeAdvice'> <property name="patterns"> <list> <value>.*che.*</value> </list> </property> </bean> <bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='regexpAdvisor'/> ...
这里定义了一个匹配模式.*che.*
,它会匹配check()方法。
... waiter.serve("TheViper"); waiter.check("TheViper"); ...
waiter说:要点什么?TheViper 你好!TheViper waiter说:结帐?TheViper
import org.springframework.aop.ClassFilter; import org.springframework.aop.support.DynamicMethodMatcherPointcut; public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut{ private static List<String> specialCients=new ArrayList<String>(); static{ specialCients.add("Tom");//添加白名单 specialCients.add("TheViper"); } //切点类匹配规则:Waiter的类或子类 public ClassFilter getClassFilter(){//静态匹配 return new ClassFilter(){ public boolean matches(Class cls){ System.out.println("对"+cls.getName()+"类作静态检查"); return Waiter.class.isAssignableFrom(cls); } }; } public boolean matches(Method method, Class<?> cls) {//切点方法静态匹配 System.out.println("对"+cls.getName()+"类的"+method.getName()+"方法作静态检查"); return "serve".equals(method.getName()); } @Override public boolean matches(Method method, Class<?> cls, Object[] args) {//动态匹配 System.out.println("对"+cls.getName()+"类的"+method.getName()+"方法作动态检查"); String clientName=args[0].toString(); return specialCients.contains(clientName); } }
匹配规则:目标类为Waiter或其子类,方法名为serve,动态传入的参数name必须在白名单中存在。
<bean id='dynamicAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor'> <property name="pointcut"> <bean class='com.GreetingDynamicPointcut'/> </property> <property name="advice"> <bean class='com.GreetingBeforeAdvice'/> </property> </bean> <bean id='waiter1' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='dynamicAdvisor' p:target-ref='waiterTarget'/>
Waiter waiter=(Waiter)ctx.getBean("waiter1"); waiter.serve("Peter"); waiter.check("Peter"); waiter.serve("TheViper"); waiter.check("TheViper");
对com.Waiter类作静态检查 对com.Waiter类的serve方法作静态检查 对com.Waiter类作静态检查 对com.Waiter类的check方法作静态检查 对com.Waiter类作静态检查 对com.Waiter类的clone方法作静态检查 对com.Waiter类作静态检查 对com.Waiter类的toString方法作静态检查 //上面是织入前spring对目标类中的全部方法进行的静态切点检查 对com.Waiter类作静态检查 对com.Waiter类的serve方法作静态检查 对com.Waiter类的serve方法作动态检查 waiter说:要点什么?Peter 对com.Waiter类作静态检查 对com.Waiter类的check方法作静态检查 //静态方法检查没经过,不用动态检查了 waiter说:结帐?Peter 对com.Waiter类的serve方法作动态检查 //第二次调用不用执行静态检查 你好!TheViper //动态检查,知足白名单,执行前置加强 waiter说:要点什么?TheViper waiter说:结帐?TheViper
定义动态切点时,切勿忘记同时覆盖getClassFilter()和matches(Method method,Class cls)方法,经过静态切点检查能够排除掉大部分不符合匹配规则的方法。
spring的流程切面由DefaultPointcutAdvisor
和ControlFlowPointcut
实现。流程切点表明由某个方法直接或间接发起调用的其余方法。
定义Waiter的代理
public class WaiterDelegate { private Waiter waiter; public void setWaiter(Waiter waiter) { this.waiter = waiter; } public void service(String name){ waiter.serve(name); waiter.check(name); } }
<bean id='controlFlowPointcut' class='org.springframework.aop.support.ControlFlowPointcut'> <constructor-arg type='java.lang.Class' value='com.WaiterDelegate'/> <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='greetBeforeAdvice'/> <bean id='waiter2' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='controlFlowAdvisor' p:target-ref='waiterTarget'/>
Waiter waiter=(Waiter)ctx.getBean("waiter2"); WaiterDelegate wd=new WaiterDelegate(); wd.setWaiter(waiter); waiter.serve("TheViper"); waiter.check("TheViper"); wd.service("TheViper");
waiter说:要点什么?TheViper waiter说:结帐?TheViper //直接调用,加强不起做用 你好!TheViper waiter说:要点什么?TheViper 你好!TheViper waiter说:结帐?TheViper
spring提供ComposablePointcut
把两个切点组合起来,经过切点的复合运算表示。ComposablePointcut
自己也是一个切点,它实现了Pointcut
接口。
交集运算的方法
并集运算
import java.lang.reflect.Method; import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.ControlFlowPointcut; import org.springframework.aop.support.NameMatchMethodPointcut; public class GreetingComposablePointcut { public Pointcut getIntersectionPointcut(){ ComposablePointcut cp=new ComposablePointcut(); Pointcut pt1=new ControlFlowPointcut(WaiterDelegate.class,"service"); MethodMatcher pt2=new NameMatchMethodPointcut(){ public boolean matches(Method method, Class<?> cls) {// 切点方法静态匹配 return "check".equals(method.getName()); } }; return cp.intersection(pt1).intersection(pt2); } }
<bean id='gcp' class='com.GreetingComposablePointcut'></bean> <bean id='composableAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor' p:pointcut='#{gcp.intersectionPointcut}' p:advice-ref='greetBeforeAdvice'></bean> <bean id='waiter3' class='org.springframework.aop.framework.ProxyFactoryBean' p:interceptorNames='composableAdvisor' p:target-ref='waiterTarget'/>
\ #{gcp.intersectionPointcut}表示引用gcp.getIntersectionPointcut()方法返回的复合切点
Waiter waiter=(Waiter)ctx.getBean("waiter3"); WaiterDelegate wd=new WaiterDelegate(); wd.setWaiter(waiter); waiter.serve("TheViper"); waiter.check("TheViper"); wd.service("TheViper");
waiter说:要点什么?TheViper waiter说:结帐?TheViper //直接调用,加强不起做用 waiter说:要点什么?TheViper 你好!TheViper//匹配check方法 waiter说:结帐?TheViper