代理模式
使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
[实际应用很少] 静态代理
静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
静态代理实现步骤:
- 定义一个接口及其实现类;
- 创建一个代理类同样实现这个接口;
- 将目标对象(接口实现类)注入代理类,然后在代理类的对应方法中调用目标类中的对应方法。
这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
动态代理
动态代理是在运行时动态生成类字节码,并加载到 JVM 中的,不需要针对每个目标类都单独创建一个代理类并实现接口,比较灵活。
JDK 动态代理
核心是 InvocationHandler
接口和 Proxy
类。最大的问题是只能代理实现了接口的类。
Proxy 类使用频率最高的是 newProxyInstance()
方法,用于生成一个代理对象。
一共有三个角色:被代理的目标对象 target、代理对象,由 newProxyInstance 生成、InvocationHandler接口实现类,自定义(扩展)实现方法。
使用时,通过调用代理对象的目标对象方法,实际调用的是InvocationHandler接口实现类中的 invoke 方法。
// 生成代理对象
public static Object newProxyInstance(ClassLoader loader, // 类加载器,用于加载代理对象
Class<?>[] interfaces, //
InvocationHandler h)
throws IllegalArgumentException {
......
}
// 使用
public static Object getProxy(Object target) { // target,被代理的目标对象
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
// 根据目标对象获取目标对象的代理对象
SmsService smsServiceProxy = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
// 方法调用,实际调用代理对象的 invoke 方法,在 invoke 方法中调用目标对象的对应方法
smsServiceProxy.send("java");
InvocationHandler 接口,核心是想要实现动态代理的话,需要实现 InvocationHandler 类来自定义代理逻辑。当动态代理对象 Proxy 调用一个方法时,这个方法的调用会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。
CGLIB 动态代理
CGLIB 通过继承方式实现代理,很多知名的开源框架都使用到了 CGLIB,例如 Spring AOP 模块:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
核心是 MethodInterceptor
接口和 Enhancer
类。
CGLIB 动态代理类使用步骤:
- 定义一个类;
- 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
- 通过 Enhancer 类的 create()创建代理类;
JDK 动态代理与 CGLIB 动态代理的区别
参考资料:JavaGuide代理模式