引言
相信作为Java开发人员,应该都有用过或见过代理设计模式,像Spring的AOP中,就是使用的动态代理模式,Mybatis中xxxMapper接口,也是使用的动态代理来生成相应的Mapper代理对象,可见,动态代理模式的重要性。
代理模式一般分为静态代理和动态代理,目标都是为了扩展增强原有方法功能。
静态代理
- 抽象对象角色(AbstractObject):一般使用接口或抽象类,声明了目标对象和代理对象的共同接口,这样依赖在任何可以使用目标对象的地方都可以使用代理对象。
- 目标对象角色(RealObject):被代理的真实角色。
- 代理对象角色(ProxyObject):实现目标对象的相同接口,并含有目标对象的引用,可对目标方法进行自定义扩展增强。
定义一个订单接口,添加下单方法
public interface IOrderService {
// 提交订单
void submitOrder();
}
编写订单接口实现类
public class OrderServiceImpl implements IOrderService{
// 提交订单测试
@Override
public void submitOrder() {
System.out.println("-------保存订单-------");
}
}
然后,假如当前的submitOrder()方法已经不能满足我们的需求,需要在此方法前后做一些特殊的业务处理,但我们又不希望在原有订单接口实现类上做修改(开闭原则),所以我们可以新增一个订单代理类,这个代理类定义了一个目标接口对象的成员属性,通过构造注入,从而可以操作目标对象,可以在目标对象方法前后添加自定义逻辑:
public class OrderStaticProxy implements IOrderService {
private IOrderService orderService;
// 构造注入
public OrderStaticProxy(IOrderService orderService){
this.orderService=orderService;
}
@Override
public void submitOrder() {
System.out.println("--------提交订单前,自定义逻辑");
orderService.submitOrder();
System.out.println("--------提交订单前,自定义逻辑");
}
}
测试类:
public class Test {
public static void main(String[] args) {
IOrderService orderService = new OrderServiceImpl();
OrderStaticProxy proxy = new OrderStaticProxy(orderService);
proxy.submitOrder();
}
}
输出如下:
分析:在不改变原有OrderServiceImpl实现类的情况下,扩展增强了原订单接口的功能
优点:
- 可以做到在不修改目标对象的前提下,拓展目标对象的功能;
- 公共的业务交由代理类进行处理,更加方便;
- 符合开闭原则、单一原则。
缺点:
- 代理类需要同目标对象实现相同的接口,所以每个类都需要写代理类时,造成类过多,维护不方便;
- 假如接口中增加方法,目标实现类与代理类都需要进行修改。
因此,我们引出动态代理。
动态代理
动态代理的角色与静态代理一样。
静态代理会为每一个业务增强都提供一个代理类, 由代理类来创建代理对象;而动态代理并不存在代理类, 代理对象直接由代理生成工具动态生成。
动态代理的代理对象是运行时动态生成的,静态代理的代理类需要我们提前写好,即代理类在编译时期就已经确定了。
动态代理又分为:
- 基于接口的动态代理(JDK动态代理)
- 基于类的动态代理(cglib)
JDK动态代理
在JDK动态代理中,核心是InvocationHandler接口和Proxy类,具体可以参考JDK帮助文档。
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
其中,我们需要实现InvocationHandler#invoke()方法,参数为:
- Object proxy:代理对象(基本没什么用)
- Method method:代理对象调用的方法
- Object[] args:被调用方法中的参数
通过Proxy#newProxyInstance()生成代理类对象
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//。。。。
}
其中,该方法在Proxy
类中是静态方法,参数为:
ClassLoader loader
:指定当前目标对象使用类加载器,获取加载器的方法是固定的。Class<?>[] interfaces
:目标对象实现的接口类型,使用泛型方式确认类型。InvocationHandler h
:事件处理器,执行目标对象的方法时,会触发事件处理器的invoke()方法,会把当前执行目标对象的方法作为参数传入。
代码实现
抽象对象角色与目标对象角色与静态代理一样(IOrderService-->OrderServiceImpl)。
自定义实现InvocationHandler#invoke(),如下
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------动态代理,前置增强-----");
Object invoke = method.invoke(target, args);
System.out.println("------动态代理,后置增强-----");
return invoke;
}
// 生成动态代理对象
public static Object getProxy(Object target){
DynamicProxy proxy = new DynamicProxy(target);
return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), target.getClass().getInterfaces(),proxy);
}
}
测试类:
/**
* @description: 测试
* @author: stwen_gan
* @date:
**/
public class Test {
public static void main(String[] args) {
// 为了在项目下生成代理类
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
// 静态代理
// IOrderService orderService = new OrderServiceImpl();
// OrderStaticProxy proxy = new OrderStaticProxy(orderService);
// proxy.submitOrder();
// 动态代理
IOrderService orderService = new OrderServiceImpl();
IOrderService dynamicProxy = (IOrderService) DynamicProxy.getProxy(orderService);
dynamicProxy.submitOrder();
}
}
输出:
其中,添加如下配置语句
//该设置用于输出cg1ib动态代理产生的类
// System.setProperty(Debuggingclasswriter.DEBUG_LOCATION_PROPERTY,"D:\\class");
//该设置用于输出jdk动态代理产生的类
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
就会在我们的当前目录下,JDK默认会生成了一个代理类,名字默认$Proxy0,继承了Proxy类
核心:一个动态代理,一般代理某一类业务,可代理多个类,代理的是接口。
从上面的代码可以看出,动态代理对象不需要实现目标对象接口,但是目标对象一定要实现接口,否则不能使用JDK动态代理。
改进:返回对象类型为泛型,抽象接口可以没有实现类,类似Mybatis中的xxxMapper接口,底层也是通过动态代理生成mapper代理对象,去拼接执行sql,我们无需编写mapper实现类
Cglib代理
上面的静态代理和JDK动态代理模式都需要目标对象去实现一个接口,但是有的时候,目标对象可能只是一个独立的类,并没有实现任何的接口,这个时候,我们就可以使用目标对象子类的方式实现代理,这种代理方式就是:Cglib代理,使用得相对少一些。(引用)
Cglib动态代理,原理是生成目标类的子类(即这个子类对象就是代理对象),它是在内存中构件一个子类对象,从而实现对目标对象的功能拓展。
注意: 不管有没有实现接口都可以使用Cglib动态代理, 而不是只有在无接口的情况下才能使用。
- JDK的动态代理有个限制,就是使用动态代理的目标对象必须实现至少一个接口,由此,没有实现接口但是想要使用代理的目标对象,就可以使用Cglib代理。
- Cglib是强大的高性能的代码生成包,它可以在运行期间拓展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的
interception
(拦截)。 - Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不鼓励直接使用ASM,因为它要求你必须对JVM内部结构,包括class文件的格式和指令集都很熟悉。
MethodInterceptor
public interface MethodInterceptor extends Callback {
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
Cglib工具类,我们只需要实现MethodInterceptor#intercept(),对目标方法进行增强
public class CglibProxy implements MethodInterceptor {
//维护目标对象
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
// //获取目标对象的代理对象
// public Object getProxy() {
// //1. 实例化工具类
// Enhancer en = new Enhancer();
// //2. 设置父类对象
// en.setSuperclass(this.target.getClass());
// //3. 设置回调函数
// en.setCallback(this);
// //4. 创建子类,也就是代理对象
// return en.create();
// }
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("-------动态代理,前置增强-----");
//执行目标对象的方法
Object object = method.invoke(target, objects);
System.out.println("-------动态代理,后置增强-----");
return object;
}
}
其中,注释掉的那个方法,是为了获取目标类的代理对象,我们可以把它单独抽取出来,新增一个cglib代理工厂:
/**
* @description: cglib代理工厂
* @author: stwen_gan
* @date:
**/
public class CglibProxyFactory {
//获取目标类的代理对象
public static <T> T createProxy(final Class<?> targetClass, final MethodInterceptor methodInterceptor) {
return (T) Enhancer.create(targetClass,methodInterceptor);
}
}
新增一个OrderDao类,无需实现任何接口
public class OrderDao {
void submitOrder(){
System.out.println("--------保存订单-------");
}
}
测试类:
输出:
总结
JDK动态代理 相比较 静态代理 相同点在于都是面向接口编程,在不修改原有代码情况下增强目标方法功能,而且动态代理无需像静态代理一样,针对目标对象需要编写一一对应的代理类。使用代理模式,符合“开闭原则”,功能职责划分清晰,目标都是为了扩展增强原有方法功能,更易于后期的扩展与维护。
●阿里巴巴为什么能抗住90秒100亿?--服务端高并发分布式架构演进之路
●SpringCloud电商秒杀微服务-Redisson分布式锁方案
查看更多好文,进入公众号--撩我--往期精彩
一只 有深度 有灵魂 的公众号0.0