在看了很多网上的资料和记录之后,我大概捋了下SpringAOP的各种阶段:
- 基本的advice编程,利用ProxyFactory拿代理类
- 利用spring把ProxyFactory,advice等bean化
- 切面编程,在advisor中编写advice和pointcut
- SpringAOP实现自动代理,也就是说不用闲式地配置ProxyFactory,和从ProxyFactory中拿代理类了,SpringAOP帮你解决了这些东西。
- AspectJ风格的注解式的Aspect编程
先记录一下AOP的几个概念:
AOP:
首先,AOP是Aspect-Originted Programming,即面向切面编程。我觉得可以这样地简单理解,aop就是为了你更清楚的逻辑,让你的业务逻辑代码更清晰,不用去想其他事,像日志啊,权限啊这些和业务逻辑无关的东西。那要怎么用这些又很重要的功能呢?我们不用改变原来的代码,只要在另一个个地方,把这些要附加的功能打包好,然后运行的时候切进你要用这些功能的地方,aop其实大概就是这样。好的那就来看看下面的几个名词:
advice:
翻译成增强、通知,其实就是你要做的东西,你要切到你的代码中的功能。aop框架会把advice模拟成拦截器interceptor。
Join Point:
连接点,就是你可以加入功能,加入advice的地方。Spring只支持在方法或者是抛出异常的地方建立使用通知,也就是说这些地方都可以是joinpoint,像其他aop像aspectJ还可以让你在构造器或属性注入时加入advice。
Pointcut:
刚刚JoinPoint我们说了哪些地方可以用advice,但不是所有地方都要用advice啊,所以就有了这个pointcut,就是加上条件的joincut。在写代码的时候,会用类似正则表达式的方式去选择那些joinpoint来作为pointcut。
Aspect:
切面,这个可是个很重要的概念哦。其实就是advice+pointcut。Spring AOP就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中.
AOP的工作重心在于如何将增强织入目标对象的连接点上, 这里包含两个工作:
- 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上
- 如何在 advice 中编写切面代码.
Introduction:
翻译成引入,就是引入允许我们向现有的类添加新方法或属性,从而无需修改这些现有类的情况下,让他们具有新的行为和状态。Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现). 例如我们可以使用 introduction 来为一个 bean 实现 IsModified 接口, 并以此来简化 caching 的实现.
Target:
目标,要加入新功能,新advice的目标类。就是不知道什么情况然后等等要被我们切入新功能的那个类。
AOP proxy:
一个类被 AOP 织入 advice, 就会产生一个结果类, 它是融合了原类和增强逻辑的代理类. 在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象. 可以说,spring aop就是用动态代理来实现的,我理解的,就是我们要在一个target类中加入新功能嘛,就通过一个代理类来实现,代理类是原来目标类+advice的结合,可以想象成变强了的target类的替身。那么要怎样才能成为这个替身呢?要么就实现一样的接口(jdk动态代理的原理),要么就继承target类称为它的子类(CGLIB的原理)。
Weaving:
织入,将aspect和其他对象连接起来,并创建adviced object的过程。(可以把weaving理解成动词,introduction理解成名词)。根据不同的技术,weaving的方式有三种:编译器织入,要求有特制的编译器;类装载期织入,这需要特殊的类装载期器;动态代理织入,在运行期间为target添加advice的方式。
一、先是非常原始的advice实现
这里我们写一个非常原始的,连spring bean都不用的例子来帮助理解aop。写的是advice增强,用的是jdk代理的方式,也就是接口代理。但要先引入Spring aop的相关jar包
思路就是,要加强的类实现一个接口,然后advice作为加强类,要实现SpringAOP提供的Advice相关接口。然后通过ProxyFactory来拿代理类,往代理类中addAdvice来达到加强的效果。
这里我们有个Greeting接口。
package com.stuPayment.aopTest; public interface Greeting { public void sayHello(String name); public int saySth(); }
然后它的实现:
package com.stuPayment.aopTest; public class GreetingImpl implements Greeting { @Override public void sayHello(String name) { // TODO Auto-generated method stub System.out.println("hello, " + name); } @Override public int saySth() { // TODO Auto-generated method stub System.out.println("this is say sth"); return 213; } public int sayMorning(String name) { System.out.println("good morning, " + name); return 123; } public void sayAfternoon(String name) { System.out.println("good afternoon, " + name); } }
然后是我们的advice增强类,要这里我们用的是MethodBeforeAdvice接口和AfterReturningMethod,表示着advice是加在方法调用前还是方法返回之后。
package com.stuPayment.aopTest.advice; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; public class GreetingBeforeAndAfterAdvice implements MethodBeforeAdvice, AfterReturningAdvice{ @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { // TODO Auto-generated method stub System.out.println("this is after"); } @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { // TODO Auto-generated method stub System.out.println("this is before"); } }
然后测试类:
package com.stuPayment.aopTest; import org.junit.Test; import org.springframework.aop.framework.ProxyFactory; import com.stuPayment.aopTest.advice.GreetingAroundAdvice; import com.stuPayment.aopTest.advice.GreetingBeforeAndAfterAdvice; public class Test1 { @Test public void demo1() { ProxyFactory proxyFactory = new ProxyFactory();//创建代理工厂 proxyFactory.setTarget(new GreetingImpl());//注入目标类对象 proxyFactory.addAdvice(new GreetingBeforeAndAfterAdvice());//添加前置加强和后置加强 //proxyFactory.addAdvice(new GreetingAroundAdvice());//添加前置加强和后置加强 Greeting greeting = (Greeting)proxyFactory.getProxy();//从代理工厂中获取代理 greeting.sayHello("Ben");//调用代理的方法 greeting.saySth(); } }
结果:
所以大概思路就是,编写一个增强类去实现Spring aop提供的几个advice的接口(其实这些接口就是决定在接入点的哪个位置加入新advice),然用aop给的ProxyFactory,先设定一个target,然后addAdvice来加入你要切入的功能,然后你就可以通过这个代理工厂来获得一个代理类(对应接口的),一个加强后的adviced object,调用它里面方法就会看到advice的效果。
然后SpringAop给了好几种Advice的增强接口:
二、一般,到了后面,我们会把这个Proxy的配置加入Spring的配置文件中
这个greetingAroundAdvice就像我们刚刚那个GreetingBeforeAndAfterAdvice一样是个实现了一个advice接口的增强类。
这里再介绍一下一个Introduction Advice,来看看jdk动态代理和CGLIB类代理的区别。
上面说到,这个引介增强Introduction Advice是一种特殊的增强,之前的连接点都是方法级别的,而这个是类级别的,也就是对类的加强。
引入增强Introduction Advice的概念:一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。
先定义一个新接口Love:
然后定义授权引入增强类:
这个DelegatingIntroductionInterceptor就是一个引入的advice类,继承它就有这种advice的能力。
然后是Proxy的配置:
proxyTargetClass属性表示是否代理目标类,默认是false,也就是代理接口,上面一个例子的配置就是没有这一项属性所以用JDK动态代理,现在是true即使用CGLib动态代理。
然后看测试方法里面:
首先看到,从代理里面拿这个GreetingImpl的时候,不是像以前一样:Greeting greeting = (Greeting).......
而是直接用实现类GreetingImpl来拿。因为现在是代理目标类而不是接口类。
然后是这里的Love love = (Love)greetingImpl是将目标类强制向上转换成了Love接口,但注意,我们并没有把这个greetingImpl实现这个Love接口哦,这就是引用增强(DelegatingIntroductionInterceptor)的特性——“接口动态实现的”功能。所以display()方法可以由GreetingImpl的对象来调用,只需要强行转换接口就行了。
三、好了,在advice的层次下的编程之后,就到了后面aspect切面的编程了:
所谓切面,其实就是advice+pointcut,我们在切面中一般要做的就是定义要加的功能还有正则表达式确定要拦截的方法。
这里开始内容就很多了,也有点乱,这里稍微捋一下,迟点会通过阅读相关的书籍来理清楚关系。
切面编程一开始,我们可以通过springAOP提供的切面类RegexpMethodPointcutAdvisor来配置切面。一样还是刚刚的实现了Greeting的GreetingImpl作为target,配置文件如下:
这上面的proxy中的配置中的InterceptorNames,不再是之前的advice加强了,而是一个定义好的切面。我们看这个greetingAdvisor的bean的配置,可以看到一个属性是advice,而另一个是pattern,其实就相当于要加强的功能和pointcut。
再往后发展,proxy也不用怎么写配置了,有了个叫aop自动代理的东西,大概就是spring框架自动生成代理:
配置文件:(属性optimize为true,表示,如果target有接口,就用jdk动态代理,若谷target没有接口,就用CGLib动态代理)
还有测试代码:
此时因为是自动代理,getBean()的值不再是原来的代理id(greetingProxy),而是目标类GreetingImpl的bean的id,这同样也是个代理类,只是自动代理,隐藏了代理的工作和代码。
再往后,就到了AspectJ风格的切面编程,用注解就可以完成切面的编程,大大节省了配置的时间。
先是利用<aop:config>元素声明切面的方法,配置文件类似于下图的xml:
也可以直接使用@Aspect注解,只需要配置文件中简单得配置一下自动代理:
<context:component-scan base-package="demo.spring"/> <!-- 用@Component自动发布bean,需要配置这个元素。 --> <aop:aspectj-autoproxy /> <!-- 使用@AspectJ及其它AOP注解需要配置,否则无法使用注解;@AspectJ注解,将@Component自动发布出来的"interceptor" bean转换为一个aspectj切面,而@Pointcut、@Before、@After、@Around等注解,功能与在xml文件中配置是一样的;@Pointcut注解下面的方法内容无意义,只是要求一个相应方法提供注解依附。 -->
注解只能在使用能获得源码的场景,如果不能获取源码,则只能通过xml配置的形式,将指定的对象配置成拦截器,对指定的目标进行拦截;因此,通过xml文件配置,而不是注解,是更加通用的方式。
然后再下一篇博客,会记录如何在Springboot用AspectJ风格的注解来用SpringAOP实现一个日志记录的功能。
参考过的博客:
https://blog.csdn.net/h525483481/article/details/79625718
https://blog.csdn.net/icarus_wang/article/details/51737474 讲增强类型的
https://www.cnblogs.com/jacksonshi/p/5863313.html