# Spring AOP 的两种实现方案

Spring AOP 可以利用两种不同的方案实现对目标对象的方法的增强。

  • JDK 动态代理

  • Cglib 动态代理

# JDK 动态代理

JDK 动态代理是 Spring AOP 的默认实现方案。其基本原理如下图:

spring-aop-jdbc

  • 接口和被代理类

    public interface Animal {
        void eat();
    }
    
    public class Cat implements Animal {
        public void eat() {
            System.out.println("猫吃鱼");
        }
    }
    
  • 代理类

    public class CatProxy implements Animal {
    
        private Cat target;
    
        public CatProxy() {
            this.target = new Cat();
        }
    
        @Override
        public void eat() {
            System.out.println("执行前调用");
            target.eat();
            System.out.println("执行后调用");
        }
    }
    

从上述的 UML 图可以看到,JDK 动态代理有一个限制:它要求被代理对象必须有一个接口

在整个方案中,如果 Animal 接口并不存在,那么就无法使用 JDK 动态代理方案实现 AOP 功能。

# cglib 动态代理

cglib 是 Spring AOP 的另一个方案,它利用了 cglib 包来实现动态代理功能。

使用 cglib 需要引入 cglib 包:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.2.11</version>
</dependency>

不过由于 Spring AOP 的 org.springframework.cglib 包中包含了 CGLIB 的相关代码,所以也可以选择导入 Spring AOP 的 Maven 依赖 。

Cglib 实现的动态代理基本原理如下:

spring-aop-cglib

public class Cat {

    public void eat() {
        System.out.println("猫吃鱼");
    }

}
public class CatProxy extends Cat {

    public CatProxy() {
    }

    @Override
    public void eat() {
        System.out.println("执行前调用");
        super.eat();
        System.out.println("执行后调用");
    }

}

cglib 方案没有 jdk 方案的限制,它利用的是继承,因此对 Cat 类有无接口不作要求。不过继承也带来一个代价,那就是如果 Cat 类中有 final 方法,那么 CatProxy 无法对 final 方法进行增强。

# 总结与对比

JDK 方案和 Cglib 方案各有优缺点。jdk 方案是 Spring AOP 的默认方案,但是在 Spring Boot 中官方建议大家采用 Cglib 方案。

两种方案各自的缺陷:

  • JDK 方案要求被代理对象(Cat)必须是某个接口的实现类。
  • Cglib 方案无法增强被代理对象(Cat)的 final 方法。

另外,如果需要代理的对象的体系较复杂(实现了多个接口和较深的继承层次结构),Spring 使用 JDK 实现动态代理时有可能出现问题。这也是 Spring Boot 中建议使用 Cglib 方案的原因。

# 强制使用 Cglib 方案

如果是以 AspectJ 注解的方式使用 Spring AOP,那么配置方式是将元素 <aop:aspectj-autoproxy >proxy-target-class 属性设置为 true

<aop:aspectj-autoproxy proxy-target-class="true"/>

如果是通过 Java 代码进行配置的话,则使用 @EnableAspectJAutoProxy(proxyTargetClass=true) 注解。


如果是以 .xml 配置的方式使用 Spring AOP,那么将元素 <aop:config>proxy-target-class 属性的值设置为 true 。

<aop:config proxy-target-class="true">
    ... <!-- 切面配置 -->
</aop:config>