spring AOP

深坑

在软件业中 AOP 是 Aspect Oriented Programming 的缩写,中文是面向切面编程,是一种通过预编译方式和运行期动态代理实现程序统一维护的技术。

在 Spring AOP 中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标没有实现接口,那么 Spring 会选择使用 CGLIB 来动态代理目标类。

AOP是OOP的延续,也是Spring框架中的一个重要内容,以及函数式编程的一种衍生泛型,利用AOP课余对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提供程序的可用性和开发的效率。

AOP采用了横向抽取机制(代理机制),取代了传统纵向继承体系重复性代码,比如:

  • 可以进行性能监控
  • 可以进行事务管理
  • 可以进行安全检查
  • 缓存的相应操作

Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码。

AOP相关术语
  • Joinpoint(连接点): 所谓连接点是指那些被拦截到的点(增删改查这些方法都可以被增强,这些方法称为连接点),在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义(我只想对 save 方法进行增强做权限校验,save方法称为是切入点)。
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要的事情就是通知 (在执行保存方法进行权限校验,权限效验的方法称为通知),通知分为【前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)】
  • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态添加一些方法或Field。
  • Target(目标对象):被增强的对象
  • Weaving(织入):是将 Advice 应用到 Target 并创建新的代理对象的过程,spring采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
  • Proxy(代理):被应用增强后,产生的一个代理对象。
  • Aspect(切面):就是切入点和通知(引介)的组合
Spring JDK动态代理

JDK动态代理只能对实现接口的类产生代理,如果这个类没有实现接口,那么JDK动态代理就无效【github demo】。

  • 必须继承 InvocationHandler 接口
  • 通过 Proxy.newProxyInstance(claassLoader, Interfaces, this) 3个参数
  • 必须实现 InvocationHandler 接口的 invoke 方法
Spring CGLIB生成代理

CGlib采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题【github demo】。

  • 必须继承 MethodInterceptor 接口
  • 通过 new Enhancer() 类
  • 必须实现 MethodInterceptor 接口的 intercept 方法
代理知识总结
  • Spring 在运行期,生成动态代理对象,不需要特殊的编译器。
  • Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术,为目标Bean执行横向织入。
    • 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
    • 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
  • 程序中应优先对接口创建代理,编译程序解耦维护。这样有什么好处呐?因为我们一直提倡面向接口式的编程,因为面向接口式的编程它可以方便我们程序的解耦和并且也很容易后期进行相对应维护的工作。所以我们一般情况下都会创建接口,然后它底层默认会采用JDK动态代理这种方式来帮我们进行增强
  • 标记为final的方法,不能被代理,因为无法进行覆盖
    • JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰
    • CGLIB是针对目标类产生子类,因为类或方法不能使用final的
  • Spring只支持方法连接点,不提供属性连接点


Spring AOP增强类型

AOP是由一个叫AOP联盟提出的,spring只是其中实现最好的一个框架,AOP联盟为通知Advice定义了 org.aopalliance.aop.Interface.Advice,Spring按照通知Advice在目标方法的连接点位置可以分为以下5类。

  • 前置通知 org.springframework.aop.MethodBeforeeAdvice,在目标方法执行前实施增强。
  • 后置通知 org.springframework.aop.AfterReturningAdvice,在目标方法执行后实施增强。
  • 环绕通知 org.aopalliance.intercept.MethodInterceptor,在目标方法执行前后实施增强。
  • 异常抛出通知 org.springframework.aop.ThrowsAdvice,在方法抛出异常后实施增强。
  • 引介通知 org.springframework.aop.IntroductionInterceptor,在目标类中添加一些新的方法和属性 可以不用管
Spring AOP切面类型
  • Advisor: 代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截
  • PointcutAdvisor: 代表具有切点的切面,可以指定拦截目标类哪些方法
  • IntroductionAdvisor: 代表引介切面,针对引介通知而使用切面
Spring AOP 一般切面案例

Advice切面案例地址

PointcutAdvisor 切点切面
  • 使用普通Advice作为切面,将对目标类所有方法进行拦截,不够灵活,在实际开发中常采用带有切点的切面。
  • 常用PointcutAdvisor 实现类
    • DefaultPointcutAdvisor 最常用的切面类型,它可以通过任意Pointcut和Advice组合定义切面。
    • JdkRegexpMethodPointcut 构造正则表达式切点。

切点切面案例地址

自动创建代理
  • 在上面的案例中,每个代理都是通过 ProxyFacotryBean 织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFacotryBean开发维护量巨大
  • 解决方案: 自动创建代理
    • BeanNameAutoProxyCreator 根据 Bean名称创建代理,点击查看案例
    • DefaultAdvisorAutoProxyCreator 根据Advice本身包含信息创建代理,点击查看案例
    • AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ注解进行自动代理