一、JDK代理模式
1.简介
在 Java 动态代理机制中 InvocationHandler
接口和 Proxy
类是核心。这种JDK自带的类代理方式是非常常用的一种,也是非常简单的一种。基本会在一些中间件代码里看到例如:数据库路由组件、Redis组件等,同时我们也可以使用这样的方式应用到设计模式中。在中间件开发、设计模式中代理模式和装饰器模式等中得到应用。
2.实现方法
-
定义一个接口及其实现类;
要实现动态代理的话,必须需要实现
InvocationHandler
来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler
接口类的invoke
方法来调用。 -
自定义
InvocationHandler
并重写invoke
方法,在invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;public class JDKInvocationHandler implements InvocationHandler { /** * 代理类中的真实对象 */ private final Object target; public JDKInvocationHandler(Object target) { this.target = target; } /** * @param proxy:动态生成的代理类 * @param method:与代理类对象调用的方法相对应 * @param args: 当前 method 方法的参数 * @return Object * @author WenHui * @date 2024/3/1 1:14 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); } }
-
通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象;
public class JDKProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new JDKInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}
通过Proxy
类的 newProxyInstance()
创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler
接口的类的 invoke()
方法。 你可以在 invoke()
方法中自定义处理逻辑,比如在方法执行前后做什么事情。
- 测试
定义发送短信的接口
public interface ISmsService {
String send(String message);
}
实现发送短信的接口
public class SmsService implements ISmsService {
@Override
public String send(String message) {
System.out.println("短信已成功发送");
return message;
}
}
测试类
@Slf4j
public class testReflect {
@Test
public void testJDKProxy() throws Exception {
ISmsService proxy = (ISmsService) JDKProxyFactory.getProxy(new SmsService());
String invoke = proxy.send("你好啊");
System.out.println(invoke);
}
}
二、CGLIB
1、简介
CGLIB不同于JDK,它的底层使用ASM字节码框架在类中修改指令码实现代理,所以这种代理方式也就不需要像JDK那样需要接口才能代理。在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。在Spring、AOP切面、鉴权服务、中间件开发、RPC框架等中得到运用。
2、实现方式
-
引入依赖;
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
-
自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似;public class CglibProxy implements MethodInterceptor { public Object newInstall(Object object) { return Enhancer.create(object.getClass(), this); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("我被CglibProxy代理了"); return methodProxy.invokeSuper(o, objects); } }
在newInstall方法利用Enhancer创建代理
- 测试
@Slf4j public class testReflect { @Test public void testCglibProxy() throws Exception { CglibProxy cglibProxy = new CglibProxy(); SmsService smsService = (SmsService) cglibProxy.newInstall(new SmsService()); String invoke = smsService.send("你好啊"); System.out.println(invoke); } }
三、JDK代理和CGLIB代理的对比
类型 | 机制 | 回调方式 | 适用场景 | 效率 |
---|---|---|---|---|
JDK动态代理 | 委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类的原始方法 | 反射 | 目标类是接口类 | 效率瓶颈在反射调用稍慢 |
CGLIB动态代理 | 继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑 | 通过FastClass方法索引调用 | 非接口类、非final类,非final方法 | 第一次调用因为要生成多个Class对象,比JDK方式慢。多次调用因为有方法索引比反射快,如果方法过多,switch case过多其效率还需测试 |