设计模式 - 静态代理 VS 动态代理静态代理与动态代理
动态代理是什么?有哪些应用?
动态代理是运行时动态生成代理类。
动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。
怎么实现动态代理?
JDK 原生动态代理和 cglib 动态代理。
- JDK 原生动态代理是基于接口实现的
Proxy类
Proxy.newProxyInstance 用来生成代理实例
InvocationHandler接口
要@Override invoke()方法,拦截所有原始类的各种方法,可在其前后增加逻辑
- 而 cglib 是基于继承当前类的子类实现的
Enhancer类
Enhancer.create(clazz, this) 获取被代理后的目标类
MethodInterceptor接口
要@Override intercept()方法,拦截所有原始对象的各种方法,可在其前后增加逻辑
当想给一个类的每个method进行扩充(统计、打印耗时)、拦截(判断是否登录)时:
常规思路
- 修改了原来的代码逻辑
- 每个涉及的method都需要改动,改动量很大
代理思路
可以使用代理(静态代理/动态代理),统一处理这类额外功能(非核心业务功能),让原来的代码仍然只关心最核心的业务逻辑。
2-静态代理
关键点
代理类ServiceImplProxy和被代理类ServiceImpl实现同样的Service接口
代理类ServiceImplProxy持有被代理类ServiceImpl的引用
代理类ServiceImplProxy在接口方法中,填充扩展/拦截功能,核心逻辑依然由被代理类ServiceImpl的引用来完成
代码实例
接口 service
void play(String xxx);
String run(String xxx);
}
被代理类 ServiceImpl
@Override
public void play(String xxx){
//....
}
@Override
public String run(String xxx){
//...
}
}
代理类 ServiceImplProxy
private Service service;
//***关键*** 持有ServiceImpl的引用
public ServiceImplProxy() {
service = new ServiceImpl();
}
/**
* 扩展功能
*/
@Override
public void play(String xxx){
long start = System.currentTimeMillis();//计时开始
//***关键*** 执行真正的逻辑
service.play(xxx);
long end = System.currentTimeMillis();//计时结束
System.out.println("耗时:" + (end - start) + "毫秒");//打印耗时
}
/**
* 拦截功能
*/
@Override
public String run(String xxx){
//添加拦截逻辑
if("xxx".equals("...")){
//***关键*** 执行真正的逻辑
service.run(xxx);
}
//...
}
}
优缺点
优点:
- 可以不修改原目标类(指ServiceImpl)的代码
- 可以在代理类中对功能进行拦截和扩充
缺点:
- 代理类需要实现与目标类一样的接口,会导致代理类数量较多,不易维护
- 一旦接口增加方法,目标类和代理类都需要维护
鉴于这两个缺点,JDK 提供了动态代理
3-动态代理,JDK(基于接口)
为何要引入JDK动态代理
为了解决静态代理的带来的以下问题 -> 引入了JDK动态代理:
- 代理类需要实现与目标类一样的接口,会导致代理类数量较多,不易维护
- 一旦接口增加方法,目标类和代理类都需要维护
代码示例
JDK动态代理的实现和静态代理一样,不同的是代理类的创建方式不同:
- 静态代理是直接新增一个代理类。
- JDK动态代理需要:
1-实现JDK动态代理的util代码(拷贝到任何项目中,都能直接使用)
Proxy类
Proxy.newProxyInstance 用来生成代理实例
InvocationHandler接口
要@Override invoke()方法,拦截所有原始类的各种方法,可在其前后增加逻辑
2-实际例子
优缺点
优点:见“为何要引入JDK动态代理”
缺点:只能对该类,所实现接口中定义的方法,进行代理。
详细解析
- JDK 动态代理
- JDK动态代理原理+为何只能代理接口,不能代理类
4-动态代理,CGlib(基于类)
为何要引入CGlib动态代理
为了解决 JDK 的动态代理无法代理不实现接口的类的问题 -> 引入使用 CGLib 的实现动态代理。
CGLib(Code Generator Library)是一个强大的、高性能的代码生成库。底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。动态生成一个目标类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
关键点
- CGlib动态代理需要:
- maven中添加cglib依赖
- 不再持有目标类对象 - 构造函数就是默认的空构造函数
- 创建代理对象 - 使用cglib自带的Enhancer.create(clazz), 参数是目标类的clazz对象。Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展,以后会经常看到它。
- 定义拦截器/处理器 - 在调用目标方法时,CGLib会回调MethodInterceptor(Object obj, Method method, Object[] params, MethodProxy proxy)接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口
- 参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
- 返回:从代理实例的方法调用返回的值。
- 其中,proxy.invokeSuper(obj,arg) 调用代理类实例上的proxy方法的父类方法(即实体类TargetObject中对应的方法)
优缺点
优点:完全不受代理类必须实现接口的限制
缺点:对于final方法,无法进行代理
