参考
静态代理
在理解动态代理之前,我们要先理解静态代理。
静态代理和动态代理的关键区别就在于代理类是否在编译器就已经确定。如果已确定,那么就是静态代理。
下面是两种常见的静态代理实现,分别通过接口组合+继承的方式来实现。这都算是静态代理,因为代理类(接口实现类/子类)APIStaticProxy都是在编译器就确定的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class APIStaticProxy implements APIInterface {
private final APIInterface target;
public APIStaticProxy(APIInterface target) { this.target = target; }
@Override public void logInfo(String msg) { before(); target.logInfo(msg); after(); } }
|
1 2 3 4 5 6 7 8 9
| public class APIStaticProxy extends API {
@Override public void logInfo(String msg) { before(); super.logInfo(msg); after(); } }
|
而常见的动态代理实现主要有两种:
- JDK自带的:Spring AOP 默认优先 JDK 动态代理
- CGLIB:Spring AOP 在无接口或强制使用类代理时,底层使用 CGLIB 实现
动态代理实现
JDK自带
基于接口,要求被代理的类实现某个接口
原理
JDK动态代理的实现方式是在程序运行时通过字节码生成技术(Bytecode Generation)生成了一个新的class,这个class也实现了被代理类所实现的指定接口。
生成字节码 → ClassLoader define → 通过反射构造实例。
同时这个生成的类还继承 Proxy 类,所有方法都会统一转发给 InvocationHandler
1
| class $Proxy0 extends Proxy implements 接口1, 接口2, ...
|
由于 Java 只能单继承,但是可以多实现,因此JDK自带的实现方式强制要求被代理类实现某个接口
简单用法
Proxy.newProxyInstance, InvocationHandler接口
接口
1 2 3 4
| public interface UserService { String createUser(String username); String deleteUser(String username); }
|
被代理类(必须实现某个接口)
1 2 3 4 5 6 7 8 9 10 11
| public class UserServiceImpl implements UserService{ @Override public String createUser(String username) { return "用户 " + username + " 创建成功!"; }
@Override public String deleteUser(String username) { return "用户 " + username + " 删除成功!"; } }
|
代理类,实现InvocationHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class BusinessLogAndCheckProxy implements InvocationHandler {
private final Object target;
public BusinessLogAndCheckProxy(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("[权限校验]检验用户是否有权限调用" + method.getName()); System.out.println("[日志]方法" + method.getName() + "开始执行"); Object res = method.invoke(target, args); System.out.println("[日志]" + method.getName() + "方法执行完毕,结果为:" + res); return res; } }
|
实例化代理类
通过Proxy.newProxyInstance()动态创建代理类
1 2 3 4 5 6 7 8 9 10 11
| public class JDKDynamicProxyTest { public static void main(String[] args) { UserServiceImpl target = new UserServiceImpl(); UserService userService = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new BusinessLogAndCheckProxy(target) ); userService.createUser("hyy"); } }
|
以上面的代码为例,这个JDK在底层动态创建的代理类实际的结构是这样:
1
| class $Proxy0 extends Proxy implements UserService
|
CGLIB
基于继承,所以不要求被代理类实现某个接口。但也正因为基于继承,CGLIB的方式不可以代理final的类,同时就算类不是final的,也无法代理final的方法。
原理
底层使用 ASM 字节码生成库,在运行时为目标类生成一个子类,在子类方法中织入增强逻辑。
使用
业务类
1 2 3 4 5 6 7
| public class ProductService { public String getProductById(String id) { try { Thread.sleep(100); } catch (InterruptedException e) {} return "商品信息: " + id; } }
|
CGLIB代理,实现方法调用缓存器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class CacheInterceptor implements MethodInterceptor { private final Map<String, Object> cache = new HashMap<>();
@Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { String key = method.getName() + Arrays.toString(args); if (cache.containsKey(key)) { System.out.println("[缓存命中] key=" + key); return cache.get(key); } Object result = proxy.invokeSuper(obj, args); cache.put(key, result); return result; } }
|
动态创建代理对象
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(ProductService.class); enhancer.setCallback(new CacheInterceptor());
ProductService proxy = (ProductService) enhancer.create();
System.out.println(proxy.getProductById("123")); System.out.println(proxy.getProductById("123")); }
|
核心价值和应用场景
动态代理在和日常业务开发中的核心价值:
将和业务无关但是通用的公共逻辑,从每个方法里,挪到每个方法外统一处理。
在实际业务中最常见的应用场景就是事务
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class OrderServiceImpl implements OrderService{
@Override public void createOrder() { orderMapper.insert(...);
stockMapper.decrease(...);
accountMapper.decrease(...); } }
|
现在处于业务需求,我们需要让下订单这三件事要么都完成,要么都不完成。换言之需要考虑到操作数据库时的事务。
如果不使用动态代理:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public void createOrder() { Transaction tx = transactionManager.begin(); try { orderMapper.insert(...); stockMapper.decrease(...); accountMapper.decrease(...);
tx.commit(); } catch (Exception e) { tx.rollback(); throw e; } }
|
但是这样的try-catch段可能到处都需要,也有可能只有部分地方需要。
如果每一处都这么写,不仅维护复杂度很高,同时也会将真正的业务操作和非业务逻辑耦合在一起。
在日常开发中我们会直接使用Spring的Transactional注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class OrderServiceImpl implements OrderService {
@Transactional @Override public void createOrder() { orderMapper.insert(...);
stockMapper.decrease(...);
accountMapper.decrease(...); } }
|
Spring在背后就是基于反射+动态代理的方式来对业务操作中的事务需求进行优雅处理的。
实现逻辑
简单来说,Spring在底层干了这些事情:
- 启动应用程序时通过反射扫描
@Transactional注解修饰的所有方法,发现OrderServiceImpl这个类需要统一事务切面增强。
- 为
OrderServiceImpl 动态创建代理对象,由于在这个例子中被代理的 OrderServiceImpl 实现了 OrderService 接口,因此 Spring 采用 JDK 代理的方式,创建了动态代理对象。
- Controller 中或是其他依赖
OrderService 的 Spring Component 中,实际注入的都是 Spring 底层通过动态代理创建的代理类OrderServiceImpl$$Proxy
- 在这些 Controller 或是依赖
OrderService 的 Spring Component 中任何调用 OrderService.createOrder()都会进入到OrderServiceImpl$$Proxy中,进行统一的异常捕获和事务回滚。
我们发现在我们实际硬编码的 OrderServiceImpl 中并未进行任何事务的处理,在实际执行的时候仍能进行事务的保障。这就是动态代理的作用。如果仍采用静态代理进行增强,不是不可以,会产生很多无业务意义的逻辑。通过使用动态代理,可以让开发者聚焦在核心业务逻辑,在不侵占业务的情况下仍能在程序运行时获得功能增强。
回顾分析
知道了Spring底层的实现逻辑,我们也就知道,为什么在像是OrderServiceImpl这样的类中内部调用被@Transactional修饰的方法时事务不起作用了。因为并未经过Spring动态代理生成的代理对象。
最佳实践&总结
静态代理可以通过接口或是继承的方式实现。
静态代理与动态代理的本质区别在于代理类是否在编译期就已确定,与是否使用接口或继承无关。
并不是所有场景都可以无脑选择动态代理。当业务复杂且需要对某些类采取通用的增强逻辑时,可以考虑使用动态代理。如果很简单的逻辑可以考虑直接使用静态代理。
如果需要代理的类已实现接口,用JDK自带的。
如果需要代理的类未实现接口(可能是某些代码历史原因),同时也没有被 final 修饰,使用 CGLIB 进行动态代理。
JDK 动态代理与 CGLIB 动态代理并不存在绝对的优劣之分,它们本质上是 Java 语言模型约束下的两种不同实现路径。
接口优先的设计天然适合 JDK 动态代理,而遗留代码或无接口场景则更适合 CGLIB。
而什么时候可以使用动态代理?想在不改业务代码的前提下,在方法前后统一做些业务不相关的逻辑。这个时候就可以使用动态代理优雅地实现需求。