一、开篇
代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。现实世界的代理人被授权执行当事人的一些事宜,无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上代理人是要有当事人的授权,并且在核心问题上还需要请示当事人。
在软件设计中,使用代理模式的意图也很多,比如因为安全原因需要屏蔽客户端直接访问真实对象,或者在远程调用中需要使用代理类处理远程方法调用的技术细节 (如 RMI),也可能为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。
代理模式角色分为 4 种:
- 主题接口:定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
- 真实主题:真正实现业务逻辑的类;
- 代理类:用来代理和封装真实主题;
- Main:客户端,使用代理类和主题接口完成一些工作。
代理模式的使用本质上是对开闭原则(Open for Extension, Close for Modification)的直接支持。
二、动态代理
动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。与静态处理类相比,动态类有诸多好处。首先,不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;其次,使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性
2.1 jdk动态代理
jdk动态代理有两个要求:
- 1.实现
InvocationHandler
接口,重写invoke方法(在这个方法中写业务逻辑); - 2.被代理的类必须实现接口
抽象主题,Person接口代码如下
/**
* author luyi
*/
public interface Person {
/**
* 寻找另一半
*/
public void findLove();
/**
* 找工作
*/
public void findJob();
}
被代理类,小卢
/**
* @author luyi
* @date 2020/20/25
* 小卢类
*/
public class XiaoLu implements Person {
public XiaoLu(){
System.out.println("初始化小卢");
}
@Override
public void findLove() {
System.out.println("我是小卢,我找女朋友的要求如下:前凸后翘;大长腿;160+");
}
@Override
public void findJob() {
System.out.println("不是996的工作我不干!");
}
}
代理类:媒婆
/**
* @author luyi
* jdk媒婆代理类
*/
public class JdkMeiPo implements InvocationHandler {
private Person target;
public Object getInstance(Person person) {
this.target = person;
Class<?> clazz=person.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是媒婆,我根据双方要求找到合适的对象");
method.invoke(this.target,args);
System.out.println("如果合适就准备结婚");
return null;
}
}
调用client:
Person person= (Person) new JdkMeiPo().getInstance(new XiaoLu());
person.findLove();
调用输出结果:
我是媒婆,我根据双方要求找到合适的对象
我是小卢,我找女朋友的要求如下:前凸后翘;大长腿;160+
如果合适就准备结婚
2.2 cglib
**
* @author luyi
* cglib的媒婆代理类
*/
public class CglibMeiPo implements MethodInterceptor {
public Object getInstance(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
//要把哪个类设置成即将生成类的父类
enhancer.setSuperclass(clazz);
//设置回调的类
enhancer.setCallback(this);
//创造代理类
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//这里是业务方法
System.out.println("我是媒婆,我根据双方要求找到合适的对象");
methodProxy.invokeSuper(o, objects);
System.out.println("如果合适就准备结婚");
return null;
}
}
public static void main(String[] args) {
XiaoLu xiaoLu = (XiaoLu) new CglibMeiPo().getInstance(XiaoLu.class);
xiaoLu.findLove();
}
2.3 动态代理的本质
在运行时动态生成代理类,jdk是生成接口的子类,而cglib是生成被代理类的子类,我们可以通过断点或者将代理类的字节码文件输出,输出的代码:
//得到字节码,$proxy0根据实际情况写,为了方便我写死了,$proxy0这种以$开头的都一半就是不存在的类,在项目中找不到,自动生成的
byte[] bytes=ProxyGenerator.generateProxyClass("$proxy0",new Class[]{Person.class});
//输出的路径
FileOutputStream fileOutputStream=new FileOutputStream("/home/luyi/Desktop/$proxy.class");
fileOutputStream.write(bytes);
fileOutputStream.close();
生成代理类的步骤为:
- 1.拿到被代理类的引用,并且获取它的所有的接口(反射获取)。
- 2.JDK Proxy类要重新生成一个新的类,实现了被代理类所有接口中的方法
- 3.动态生成Java代码,把我们增强的逻辑加入到新生成的代码中。
- 4.编译生成新的Java代码的Class文件
- 5.加载并重新运行新的Class,得到的类就是全新的类# 静态代理
静态代理就是直接传入引用,显示声明被代理对象。比如小卢爸爸给小卢找对象,只能给小卢找,而不能给其他的人找。这种代理方式不方便后期拓展。
public class XiaoLuFather{
public void findLove(XiaoLu xiaoLu) {
System.out.println("给儿子找对象,找到了还可以,看儿子喜不喜欢");
xiaoLu.findLove();
System.out.println("搞定了");
}
}
三、JDK与CGLib性能对比
- 有研究表明CGLib所创建的动态代理对象的性能比JDK所创建的动态代理对象的心梗高不少(大概10倍)。
- CGLib在创建代理对象时所花费的时间比JDK动态代理多(大概8倍)