Spring AOP 实现原理

关于 Aspect 切面

在 AOP 的使用中,Aspect 切面由 Pointcut 和 Advice 组成,但是没有写到如何进行切入的,这就是本篇博客所介绍的 “ Proxy ” ,即 代理


Proxy 代理

首先,当在 bean 的生命周期中,spring在给 bean 执行初始化后置,即方法postProcessAfterInitialization()的时候(一般情况下是这样,如果是出现了bean循环依赖,会在实例化结束后即调用了createBeanInstance()反射方法之后依赖注入之前进行生成 Proxy),去进行 AOP 的生成。

因为初始化后置方法是每生成一个 Bean 就要执行一次这个方法,正好对应了 AOP 的需求,即为每一个需要 AOP 的 Bean 进行 面向切面编程

而就是这个时候 AOP 就会生成 Bean 的代理对象 Proxy


关于Proxy的实现方式

这里运用到了一个技术叫做 “动态代理” 。而对于动态代理的实现方式有两种,分别为 JDK 代理CGLIB 代理
为了方便理解,这里先用 “静态代理” 做演示。

1. 静态代理

定义接口

  • 首先先写一个接口。
java

// 定义接口类 UserService

public interface UserService {

void saveUser(String username);

}

接口实现

  • 再写一个实现类去实现此接口。
java

// 接口实现类 UserServiceImpl

public class UserServiceImpl implements UserService {

@Override

public void saveUser(String username) {

System.out.println("【真实业务】正在保存用户: " + username);

}

}

静态代理类

  • 这里是静态代理的重点,静态代理类创建。
java

// 静态代理类 UserServiceStaticProxy

public class UserServiceStaticProxy implements UserService {

private UserService target;

// 拿到目标对象

public UserServiceStaticProxy(UserService target) {

this.target = target;

}

// 运用重写的操作进行业务增强

@Override

public void saveUser(String username) {

// 前置增强

System.out.println("静态代理-前置操作...");

// 调用目标方法

target.saveUser(username);

// 后置增强

System.out.println("静态代理-后置操作...");

}

}

实际使用

  • 使用静态代理进行工作
java

// 实际的使用

public class StaticProxyTest {

public static void main(String[] args) {

// 创建一个实现类为目标类 target

UserService target = new UserServiceImpl();

// 创建一个代理对象 proxy 进行对 target 的代理

UserService proxy = new UserServiceStaticProxy(target);

// 执行代理的方法(这是重写增强后的方法)

proxy.saveUser("张三");

}

}


2. 动态代理 之 JDK代理

定义接口

  • 同上写一个接口。
java

// 定义接口类 UserService

public interface UserService {

void saveUser(String username);

}

接口实现

  • 也是写一个实现类去实现此接口。
java

// 接口实现类 UserServiceImpl

public class UserServiceImpl implements UserService {

@Override

public void saveUser(String username) {

System.out.println("真实业务..." + username);

}

}

动态代理(重点)

java

// 实现动态代理

public class JdkProxyHandler implements InvocationHandler {

private Object target;

public Object bind(Object target) {

this.target = target;

return Proxy.newProxyInstance(

target.getClass().getClassLoader(),

target.getClass().getInterfaces(),

this);

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 前置增强

System.out.println("JDK代理-前置方法调用: " + method.getName());

// 核心:通过反射调用目标方法

Object result = method.invoke(target, args);

// 后置增强

System.out.println("JDK代理-后置方法调用");

// 返回原方法的执行结果

return result;

}

}

  • 首先对于方法 invoke() ,这是一种特殊的方法:每当调用代理 proxy 的时候,proxy 会自动执行invoke()方法,而这也是所谓proxy代理的核心作用。
    invoke() 的参数 proxy 指的是本身, method 指的是原对象的方法, arg 表是以列表的形式存放的原对象的方法所需的参数
  • 对于方法 Proxy.newProxyInstance :这里的 proxy 是一种 “伪造的实现类” ,通过 getClassLoader() 和 getInterfaces() 将目标的 “类的加载” 和 “接口实现” 传给了 proxy 。并通过处理器 new JdkProxyHandler(target) 的处理从而使 proxy 成功 “伪装” 成了实现了对应接口的目标类,变成了原方法的 “替身” 。

实际使用

java

// 实际的使用

public class JdkProxyTest {

public static void main(String[] args) {

JdkProxyHandler jdkProxyHandler = new JdkProxyHandler();

// 用接口类去接收这个 proxy 代理类

UserService userService = (UserService)jdkProxyHandler.bind(new UserServiceImpl())

userService.saveUser("张三");

}

}

  • 对于代码 UserService userService = (UserService)jdkProxyHandler.bind(new UserServiceImpl()) ,它将原实现类 new UserServiceImpl() 通过 bind 方法进行包装成一个proxy代理类,然后用接口类实例 UserService userService 接收。

问题来了: 那为什么不用原来的实现类接收,而是用接口类接收呢??

: proxy相当于这个接口的实现类,而原目标类也是这个接口的实现类。
虽然proxy是增强了目标类,但是和目标类本质上不一样!所以相当于现在是有两个实现类,都实现了同一个接口,所以才用接口接收,以免报错。


3. 动态代理 之 CGLIB代理

定义接口

  • CGLIB 其实不强依赖接口。
java

// 不需要接口哦

普通类实现

  • CGLIB 的强大之处在于,即使只有一个普通的类,没有接口,它也能进行代理
java

体验AI代码助手

代码解读

复制代码

// 普通类 UserServiceImpl (不需要 implements UserService 去实现接口)

public class UserServiceImpl {

public void saveUser(String username) {

System.out.println("真实业务...正在保存用户: " + username);

}

}

动态代理(重点)

java

// 实现动态代理

public class CglibProxyHandler implements MethodInterceptor {

// 创建代理对象的核心方法

public Object bind(Class<?> targetClass) {

// 1. 创建增强器(代理工厂)

Enhancer enhancer = new Enhancer();

// 2. 设置目标类(也就是父类)

enhancer.setSuperclass(targetClass);

// 3. 设置回调(也就是拦截器本身)

enhancer.setCallback(this);

// 4. 创建并返回代理对象

return enhancer.create();

}

@Override

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

// 前置增强

System.out.println("CGLIB代理-前置方法调用: " + method.getName());

// 通过 MethodProxy 调用父类(目标类)的方法

// 注意:这里使用 invokeSuper 而不是 method.invoke,效率更高

Object result = methodProxy.invokeSuper(proxy, args);

// 后置增强

System.out.println("CGLIB代理-后置方法调用");

return result;

}

}

  • 关于 enhancer :这个所谓的 “增强器” 其实就是一个 “代理工厂”,可以用 enhancer.create() 生成代理对象 proxy 。
  • 关于 enhancer.setCallback() 方法,参数为 this 。这个方法叫做 “回调” ,是为了将下面的 intercept 增强的方法传递给代理工厂 enhancer ,等到需要生成代理对象 proxy 的时候,工厂会自动将这个 intercept 增强方法塞进代理对象 proxy 中
  • 对于方法 enhancer.create():这里的 proxy 是一种 “伪造的子类” ,即将目标类作为父类,让代理类 proxy 去继承目标类。CGLIB 会在内存中动态生成了一个 继承自目标类 的新类( 就是这个 proxy )。它通过重写父类的所有非 final 方法,并在方法中织入我们的增强逻辑,从而变成了原方法的“替身”。
  • 对于方法 intercept(),这是 CGLIB 的拦截核心。每当调用代理对象的方法时,会执行此方法。 proxy:指的是 CGLIB 生成的代理对象本身。 method:指的是 目标类的方法。 args:指的是 方法参数。 methodProxy:这是 CGLIB 特有的参数,它是对目标方法的快速代理,比 JDK 的反射调用性能更好。

实际使用

java

// 实际的使用

public class CglibProxyTest {

public static void main(String[] args) {

CglibProxyHandler cglibProxyHandler = new CglibProxyHandler();

// 用原来的类去接收这个 proxy 代理类

UserServiceImpl userService = (UserServiceImpl) cglibProxyHandler.bind(UserServiceImpl.class);

userService.saveUser("张三");

}

}

  • 对于代码 UserServiceImpl userService = (UserServiceImpl) cglibProxyHandler.bind(UserServiceImpl.class),它将原类 UserServiceImpl.class 通过 bind 方法包装成一个 proxy 代理类,然后用 原类实例 UserServiceImpl userService 接收。
    问题来了:为什么 CGLIB 可以用原来的类接收,而 JDK 代理不行呢??

:因为 CGLIB 采用的是继承的方式。
CGLIB 生成的代理类是目标类的子类,而目标类是父类
在 Java 中,子类对象是可以赋值给父类引用的(多态)。所以,代理对象(子类)可以直接被当作目标类(父类)来使用,自然就可以用原来的类去接收了。


关于动态代理使用

在 Spring 框架中,AOP 代理的创建是由 AopProxyFactory 决定的。

Spring 默认遵循以下策略:

  • 如果目标对象实现了接口:默认使用 JDK 动态代理。
    建议:在这种情况下,业务类应当实现接口,这是 Java 设计模式的最佳实践。
  • 如果目标对象没有实现接口:必须使用 CGLIB。
  • 强制使用 CGLIB:
    即使目标对象实现了接口,你也可以通过配置强制 Spring 使用 CGLIB ,例如:
    • 在 XML 中设置 <aop:config proxy-target-class="true"/>
    • 在注解配置中开启 proxyTargetClass=true 。

注意:使用 CGLIB 时,目标类不能是 final 的,且需要被代理的方法也不能是 final 或 private 的(因为子类无法重写这些方法)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值