代理模式
代理模式是什么?
首先我们来看一下代理模式中有哪些角色,我们开看下这张简单的图
可以看到,在代理模式中主要有客户端、代理类、目标类这3个角色,它们的含义如下:
- 客户端:就是用来发起调用请求的,没啥好说的
- 目标类:特定行为的实现类,真正“干活”的角色
- 代理类:对外代理目标类所有的功能,在目标类功能的基础上会做一些额外的工作,通过在代理类内部持有目标对象引用实现
那么代理模式是怎么来玩的呢?
其实很简单,就和上边那张图一样,开始的时候客户端会来调用代理类的方法,由于在代理类内部持有目标对象的引用,此时代理类会直接调用目标对象的方法来完成这个功能,同时呢,代理类还可以自己添加一些增强逻辑,做一些额外的功能。
好了,现在代理模式我们已经搞清楚了,并且我们知道动态代理只是代理模式的其中一种实现方式,那么代理模式还有哪些实现方式呢?我们看这张图
其实呢,代理模式分为两大类,分别是静态代理和动态代理,而动态代理主要分为jdk动态代理和cglib动态代理,所以简单来说,代理模式我们常用的实现方式分别是静态代理、jdk动态代理、cglib动态代理这三种,它们都可以实现在目标对象功能的基础上,添加一些增强逻辑,也就是做一些额外的工作。
那么静态代理、jdk动态代理、cglib动态代理它们都是怎么实现的?具体都是怎么来玩儿的呢?
大家不要着急,接下来我们就分别来玩儿一下静态代理、jdk动态代理、cglib动态代理,看下它们到底是怎么来玩的。
静态代理
静态代理之所以有“静态”两个字,是因为静态代理中的代理类在编译期间就创建好了,不是通过编译器生成的代理类,而是我们自己手动创建出来的类。
public class ProductServiceProxy implements ProductService {
private ProductService target;
public ProductServiceProxy(ProductService target) {
this.target = target;
}
@Override
public void addProduct(Product product) {
System.out.println("[记录日志]准备执行方法:addProduct, 参数列表:" + product);
target.addProduct(product);
System.out.println("[记录日志]方法执行结束:addProduct");
}
@Override
public Product getProductById(Integer productId) {
System.out.println("[记录日志]准备执行方法:getProductById, 参数列表:" + productId);
Product product = target.getProductById(productId);
System.out.println("[记录日志]方法执行结束:getProductById, 返回值:" + product);
return product;
}
}
这个ProductServiceProxy就是我们手动创建出来的代理类,我们可以看到它内部持有一个目标对象的引用target,需要注意的是这个目标对象target和代理类ProductServiceProxy是同一种接口类型,它们都是ProductService接口类型,所有它们有相同的方法
然后我们可以看到在ProductServiceProxy的addProduct()和getProductById()方法中,都直接调用目标对象target相应的方法来完成了功能,就类似于“请求转发”一样,同时在调用目标对象target方法的前边和后边都添加了增强逻辑,就是分别打印入参和出参。
这就是静态代理的玩儿法,其实是非常简单的,那接下来我们来看下jdk动态代理和cglib动态代理怎么玩儿吧
JDK动态代理
在讲jdk动态代理之前,大家先思考下,既然有了静态代理,那为啥还要有动态代理呢?
换种说法就是,现在静态代理存在什么问题?动态代理又是怎么解决这些问题的?
其实很简单,以我们刚手动创建的静态代理类ProductServiceProxy为例,它里边有两个方法,分别是addProduct()和getProductById(),然后我们分别在这两个方法中添加了增强逻辑,也就是日志代码片段。那如果这个代理类ProductServiceProxy中有20个方法呢?难道我要在20个方法中一个一个的添加增强逻辑吗?
这样做当然是可以的,但就是维护成本太高了,因为我们发现这些增强逻辑(日志代码片段),其实都是些重复代码,那么有没有一种更友好的方式,来进行统一处理呢?
那当然是有的,这种方式就是动态代理了,和静态代理不同的是,静态代理的代理类在代码运行前已经被我们手动创建好了,并且已经生成了class文件,而动态代理则是在程序运行时才创建的代理类,也就是说动态代理中的代理类不是我们手动在java代码中定义的,而是在运行的时候“动态”生成的。
而动态代理带来的好处就是可以对代理类中所有的方法进行统一处理,这样就可以解决代码重复以及维护成本过高的问题了,非常的灵活。
那动态代理怎么玩呢?我们以jdk动态代理为例,我们看这块代码
public class JdkDynamicProxy implements InvocationHandler {
/**
* 目标对象
*/
private Object target;
public JdkDynamicProxy(Object target) {
this.target = target;
}
/**
* 获取目标对象的代理
* @return
*/
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 指定当前目标对象使用类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口的类型
this // 设置回调程序
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[记录日志]准备执行方法:" + method.getName() + ", 参数列表:" + Arrays.toString(args));
Object result = method.invoke(target, args);
System.out.println("[记录日志]准备执行方法:" + method.getName() + ", 返回值:" + result);
return result;
}
}
我们可以看到这个JdkDynamicProxy实现了InvocationHandler接口,需要注意的是这个JdkDynamicProxy可不是代理类,真正的代理类在代码运行时通过getProxy()方法生成,而getProxy()方法中其实是调用了jdk的Proxy.newProxyInstance()方法来生成代理的,其中传入的参数分别为目标对象的类加载器、目标对象的接口类型以及回调程序,回调程序其实就是它自己,也就是JdkDynamicProxy。
也就是说当调用代理类的方法时,就会来回调这个JdkDynamicProxy类了,说的具体点就是会回调到JdkDynamicProxy类的invoke()方法。
说白了就是对代理类所有方法的调用,都会回调到这个invoke()方法,此时这个invoke()方法就会通过反射来调用目标对象的方法了,同时我们可以在调用目标对象方法之前和之后加一些增强逻辑,比如记录日志。
简单说这个invoke()方法就相当于统一的收口,我们可以在这里加一些通用的增强逻辑,这样静态代理重复代码和维护成本高的问题就都解决了。
CGLIB动态代理
最后我们来看一下cglib动态代理,好,那我们先来思考一下,既然有了jdk动态代理,那为啥还要有cglib动态代理呢?
是这样的,我们细心点就会发现,刚才在创建jdk动态代理时会传入三个参数,其中有一个就是目标对象的接口类型,其实jdk动态代理是基于接口实现的,所以创建代理的时候,需要指定一下生成的代理类需要实现哪些接口。
那现在问题来了,如果有一个类没有实现接口,此时jdk动态代理是无能为力的,因为它是基于接口实现的,可我们就是想为这个类创建代理怎么办?
那我们可以使用另外一种方式,那就是cglib动态代理,cglib动态代理是基于继承实现的,即使目标类没有实现接口也是可以正常生成代理的,我们看下边的代码:
public class CglibDynamicProxy implements MethodInterceptor {
private Object target;
public CglibDynamicProxy(Object target) {
this.target = target;
}
public Object getProxy() {
// 字节码增强器,可以为没有实现接口的类创建代理
Enhancer enhancer = new Enhancer();
// 设置生成的代理类的父类类型为目标对象类类型
enhancer.setSuperclass(target.getClass());
// 设置回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("[记录日志]准备执行方法:" + method.getName() + ", 参数列表:" + Arrays.toString(args));
Object result = methodProxy.invoke(target, args);
System.out.println("[记录日志]准备执行方法:" + method.getName() + ", 返回值:" + result);
return result;
}
}
我们可以看到在CglibDynamicProxy类中有一个getProxy()方法,而在这个getProxy()方法中构建了一个字节码增强器实例enhancer,同时为enhancer设置了生成代理类的父类类型和回调方法,因为Cglib的原理是动态生成要代理类的子类,然后子类去重写父类的方法,所以这里要设置生成代理类的父类类型,最后调用了enhancer.create()方法来创建代理。
当调用代理中的方法时,就会回调到这个intercept()方法,这个intercept()方法和jdk动态代理中的invoke()方法是一样的,说白了就是一个统一的收口,我们可以在这里添加一些统一的增强逻辑,比如统一记录方法的入参和出参。
这个就是cglib动态代理的玩儿法,其实和jdk动态代理的玩儿法差不多,只不过jdk动态代理是基于接口实现,而cglib动态代理是基于继承,子类重写父类方法罢了。
最后让我们来一起测试下静态代理、jdk动态代理、cglib动态代理的代码吧,看到底能不能实现统一记录日志的功能。
测试一下
public class ProxyPatternTest {
public static void main(String[] args) {
Product product = Product.builder().id(123).name("测试").price(BigDecimal.valueOf(100)).build();
ProductService target = new ProductServiceImpl();
System.out.println("--------------------静态代理--------------------");
ProductServiceProxy staticProxy = new ProductServiceProxy(target);
staticProxy.addProduct(product);
staticProxy.getProductById(product.getId());
System.out.println("--------------------jdk动态代理------------------");
ProductService jdkDynamicProxy = (ProductService) new JdkDynamicProxy(target).getProxy();
jdkDynamicProxy.addProduct(product);
jdkDynamicProxy.getProductById(product.getId());
System.out.println("--------------------cglib动态代理----------------");
ProductServiceImpl cglibDynamicProxy = (ProductServiceImpl) new CglibDynamicProxy(target).getProxy();
cglibDynamicProxy.addProduct(product);
cglibDynamicProxy.getProductById(product.getId());
}
}
可以看到静态代理直接将目标对象target通过构造方法传递进去,构建出来了代理对象staticProxy
而jdk动态代理,也是先将目标对象target通过构造方法传递进去,然后通过getProxy()方法完成了代理的创建,最后将代理对象强转为了接口类型ProductService,由于jdk动态代理是基于接口实现的,生成的代理类会实现这个ProductService接口,所以是可以这样强转的。
最后cglib动态代理,同样是先将目标对象target通过构造方法传递进去,然后通过getProxy()方法完成了代理的创建,只不过这里是将代理对象强转为了ProductServiceImpl类型,因为cglib是基于继承来的,生成的代理类本质是ProductServiceImpl类的子类,所以这里是可以强转为ProductServiceImpl类型的。
最后分别调用了代理对象中的方法,打印结果如下:
可以看到,静态代理、jdk动态代理以及cglib动态代理都按照预期完成了记录日志的功能。