1. 核心概念
aop是类似与oop的思想,oop通过继承实现代码复用,对于无逻辑上的父子关系的类就不能用了。因此有了面向切面编程(aop),做法就是将没有父子关系的类的相同代码抽取出来作为一个切面,实现代码复用(请注意:interceptor也是aop思想的一种实现,aspect也是aop思想的一种实现;aop是一种思想,不能把aop等同于aspect)。使用aop主要是理解一下几个概念:
Aspect:切面,由一系列切点、增强和引入组成的模块对象,可定义优先级,从而影响增强和引入的执行顺序。
Join point:接入点,程序执行期的一个点,例如方法执行、类初始化、异常处理。 在Spring AOP中,接入点始终表示方法。
Advice:增强,切面在特定接入点的执行动作,包括 “around,” “before” ,"after","afterReturning","afterThrowing"等多种类型。包含Spring在内的许多AOP框架,通常会使用拦截器(Interceptor)来实现增强,围绕着接入点维护着一个拦截器链。
Pointcut:切点,就是一个表达式,可以表示出多个连接点(即多个方法),增强将会与切点表达式产生关联,并运行在任何切点匹配到的接入点上。通过切点表达式匹配接入点是AOP的核心,Spring默认使用AspectJ的切点表达式。
Introduction:引入,为某个type声明额外的方法和字段。Spring AOP允许你引入任何接口以及它的默认实现到被增强对象上。
Target object:目标对象,被一个或多个切面增强的对象。也叫作被增强对象。既然Spring AOP使用运行时代理(runtime proxies),那么目标对象就总是代理对象。
AOP proxy:AOP代理,为了实现切面功能一个对象会被AOP框架创建出来。在Spring框架中AOP代理的默认方式是:有接口,就使用基于接口的JDK动态代理,否则使用基于类的CGLIB动态代理。但是我们可以通过设置proxy-target-class="true",完全使用CGLIB动态代理。
Weaving:织入,将一个或多个切面与类或对象链接在一起创建一个被增强对象。织入能发生在编译时 (compile time )(使用AspectJ编译器),加载时(load time),或运行时(runtime) 。Spring AOP默认就是运行时织入,可以通过枚举AdviceMode来设置。
2. Pointcut
上面说的概念中,最常用的就是Pointcut与Advice。Pointcut决定了怎样拦截方法和拦截什么样的方法,实际上就是通过表达式找到一个接入点;Advice与Pointcut联系密切他将决定如何处理这个切点所表示的接入点;因此熟练掌握这两个的用法就能应对大多数需求了。
@Aspect //说明这个类是一个切面类 @Component public class Aop { @Pointcut("within(com.sq.service..*)") //切点,within和括号的表达式的意义:service包及其子包下的所有类的所有方法都是连接点 public void cutA(){} @Before("cutA()") //使用Before来处理这个切点 public void beforeFunc(){ System.out.println("aop"); } }
@Pointcut括号里使用within(xxx),则括号里的表达式指的是若干个包、类;
@Pointcut括号里使用this(xxx)、target(xxx)、Bean(xxx),括号里的表达式指的是某个对象;他们之间有些许区别:
@Pointcut("this(com.sq.service.ServiceA)") public void cutA(){} //this表示匹配ServiceA类的对象的所有方法,换做target则表示匹配所有实现了ServiceA接口的所有类的对象的方法(ServiceA得是个接口),
换做Bean则表示只限在被spring管起来的对象中寻找匹配者
@Pointcut括号里使用args(xxx),则只要方法的参数与括号里的表达式匹配,这个方法就会被视为连接点;
@Pointcut括号里使用@args(xxx)、@within(xxx)、@annotation(xxx),则括号里的表达式是匹配注解。如:
@Pointcut("@within(com.sq.annotation.Aa)") public void cutA(){} //说明只要某个类上面打了@Aa注解,则这个类的所有方法就是连接点;
换做@annotation(com.sq.annotation.Aa),则表示这个@Aa注解只能打到方法上,所有打了@Aa注解的方法将会被视为连接点;
换做@args(com.sq.annotation.Aa),则表示如果方法的参数中打了@Aa注解的,方法将会被视为连接点
@Pointcut括号里使用execution(xxx),就是直接匹配方法。如execution(public * com.sq.*Service(..)),表示匹配public的,返回值不限,com.sq包下的以Service为结尾的名称的类里的方法,方法参数不限。
包后面跟 .. 表示本包及其所有子包, .. 表示零个或多个,* 表示一个或多个。匹配表达式还可以使用 && || !等运算符。
3. Advice
增强的类型决定了增强的代码织入的时机,@Before表示表示织入到方法执行之前、@After表示方法执行之后(无论是否成功执行完毕)、@AfterReturning表示织入到方法成功执行完返回结果后、@AfterThrowing则是方法抛出异常后织入、@Around则可以表示上述所有时机。下面是一些示例:
@AfterReturning,可以在增强方法中使用原方法的返回值:
@AfterReturning(value = "cutA()",returning = "result") public void adviceFunc(Object result){ System.out.println("这是方法执行完后返回的结果" + result); }
@Around:拿到上下文,可以将一个方法的所有时间点一次处理:
@Around( "cutA()") public Object aroundFunc(PorceedingJionPoint jionPoint){ //PorceedingJionPoin 拿到方法的信息 ........ //执行方法前 可以做点什么相当于 @Before try{ Object result = jionPoint.proceed(jionPoint.getArgs()); //执行正真的原方法 ......... //执行完毕返回后可以做点什么 相当于 @AfterReturning } catch(Throwable t) { ...... //方法发生异常后可以做点什么 相当于 @AfterThrowing }finally { ...... //方法执行完后可以做点什么 相当于 @After } }
可以看到@Around在增强方法里执行原方法,可以做其他注解能做的所有事。只是写起来有点麻烦。
4. 实现原理
aop的aspect实现可以在编译时、装载时、运行时完成代码的织入,在Spring中aspect代码织入发生在运行时。
aop的实现原理是通过动态代理,动态代理在之前总结过。对于JDK动态代理和Cglib动态代理,aop的aspect是如何选择实现方式的呢(aop的另一种实现interceptor是用cglib实现)。一般是能用JDK就用JDK代理,如果某个类没有实现接口,则会使用Cglib动态代理。当然也可以通过设置,无论什么情况都一律使用Cglib动态代理,在启动类的上面打个注解 :@EnableAspectAutoProxy(proxyTargetClass = true),即可。