1、Spring AOP
面向切面编程,是OOP面向对象编程的补充和完善
通过抽取一些系统级(非业务代码)的功能,以达到解耦和重复利用,如日志、事务、权限、缓存等
2、AOP相关概念
-
横切关注点
对哪些方法在什么时候进行拦截,拦截后怎么处理,这些被称为横切关注点 -
Aspect(切面)
类时对象的抽象,切面就是横切关注点的抽象,把非业务代码功能放到一个类中形成切面,包含切入点、通知 -
Pointcut(切入点)
对连接点进行拦截的定义,在程序中主要体现为书写切入点表达式,在哪里 -
Advice(通知)
有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕),什么时间拦截到连接点之后需要执行的代码—非业务逻辑实现 -
JointPoint(连接点)
程序执行过程中明确的点,一般是方法的调用或异常抛出。 -
weave(织入)
将切面应用到目标对象并导致动态代理对象创建的过程,spring在运行时织入 -
introduction(引入):
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段 -
AOP代理(AOP Proxy):
AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于类 -
目标对象(Target Object):
被代理对象,POJO
Advice通知类型
- Before: 在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
- AfterReturning: 在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
- AfterThrowing: 主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象
- After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式
- Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知
3、四种使用AOP的方式
- 经典的基于代理的AOP
- 通过AspectJ提供的注解实现AOP
- 通过AspectJ的XML方式实现AOP
- 基于Scheme-base的AOP
SpringBoot中使用AOP
@EnableAspectJAutoProxy开启AspectJ注解功能,使用@Aspect、@Before、@After等等,参考上面【AspectJ注解使用AOP】
自定义注解+AspectJ实现AOP
4、AspectJ和Spring AOP
AspectJ:
AspectJ 在编译时自动编译得到了一个新类,这个新类增强了原有的类的功能,因此 AspectJ 通常被称为编译时增强的 AOP 框架,称为静态代理。
Spring AOP:
与 AspectJ 相同的是,Spring AOP 同样需要对目标类进行增强,也就是生成新的 AOP 代理类;与 AspectJ 不同的是,Spring AOP 无需使用任何特殊命令对 Java 源代码进行编译,它采用运行时动态地、在内存中临时生成“代理类”的方式来生成 AOP 代理,称为动态代理。
Spring 允许使用 AspectJ Annotation 用于定义方面(Aspect)、切入点(Pointcut)和增强处理(Advice),Spring 框架则可识别并根据这些 Annotation 来生成 AOP 代理。Spring 只是使用了和 AspectJ 5 一样的注解,但并没有使用 AspectJ 的编译器或者织入器(Weaver),底层依然使用的是 Spring AOP,依然是在运行时动态生成 AOP 代理,并不依赖于 AspectJ 的编译器或者织入器。
5、expose-proxy作用
public interface UserService{
public void a();
public void b();
}
public class UserServiceImpl implements UserService{
@Transactional(propagation = Propagation.REQUIRED)
public void a(){
this.b();
}
@Transactional(propagation = Propagation.REQUIRED_NEW)
public void b(){
System.out.println("b has been called");
}
}
a的事务会生效,b中不会有事务,因为a中调用b属于内部调用,没有通过代理,所以不会有事务产生。
如何设置b中事务也生效,如下:
<aop:aspectj-autoproxy expose-proxy=“true”>
expose-proxy设置为true,将代理暴露出来,使用AopContext.currentProxy()获取当前代理,将this.b()改为((UserService) AopContext.currentProxy()).b()。
6、Spring AOP底层原理分析
AOP动态代理(运行时生成、目标类的增强)
2种生成方式:
JDK
类实现了接口,代理类同样实现接口,代理其中的方法,如果有方法不在接口中定义的话就无法代理这个方法;
CGLIB
类没有实现接口,字节码增强技术,通过继承生成该类的子类, 类和方法不要声明为final
从源码可知,spring aop使用哪种方式依赖proxyTargetClass变量,如果为true则按照类是否实现接口选择JDK和CGLIB;如果为false则直接使用JDK(默认)。
配置proxyTargetClass值:
<aop:config proxy-target-class=“true”>
@EnableAspectJAutoProxy(proxyTargetClass=true)
源码跟踪:
singletonObjects是缓存,map结构,存放完全初始化好的单例bean(代理对象)
singletonObjects缓存数据在初始化spring容器时就已经放进去了,如:
BeanPostProcessor关于aop代理生成的是AbstractAutoProxyCreator