代理模式
定义
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用 ——百度百科
想象一个需求
张三有一个方法doSomeThing,现在需要在这个方法调用前后做一些事情,比如打印日志
条件是不能改动张三的doSomeThing方法,而且还想继续之前的调用方式
先来看一种简单的实现方式
静态代理
IPerson.java
public interface IPerson {
void doSomeThing();
}
ZhangSan.java
public class ZhangSan implements IPerson{
@Override
public void doSomeThing() {
System.out.println("我是张三,我在吃饭");
}
}
ZhangSanProxy.java
public class ZhangSanProxy implements IPerson {
IPerson zhangsan = new ZhangSan();
@Override
public void doSomeThing() {
//前置方法
System.out.println("我是张三的代理类,张三要去吃饭了");
//代理对象的方法
zhangsan.doSomeThing();
//后置方法
System.out.println("我是张三的代理类,张三吃完饭了");
}
}
Test.java
public class Test {
public static void main(String[] args) {
IPerson zhangsan = new ZhangSanProxy();
zhangsan.doSomeThing();
}
}
还是同样用IPerson接收对象,这时候就可以和之前一样调用doSomeThing方法,并且做到方法增强
这样做有什么问题?
实现起来简单,但是不够灵活
主要表现在两个维度
第一是每个被代理类都需要对应一个代理类,如果又来一个李四也需要做方法增强,我还要重新创建一个代理类
第二是每个方法都需要重新去重写,如果现在张三还有100个方法都需要做方法增强,那我每个都要写一遍岂不是要累死
这样简单的时候还可以,如果复杂的情况下肯定是不行的
那,怎么办?
下面一步一步分析
JDK动态代理
既然现在每加一个被代理类都需要手动写一个代理类,那能不能写代码去自动生成一个代理类
把手动的工作改成自动的,这不就解决问题了嘛
OK,看下这个思路是不是可行
首先肯定需要先传入需要被代理的对象
然后可以通过反射获取到该对象实现了那些接口以及需要实现那些方法
在实现方法的部分可以使用传入的被代理对象执行原来的方法再加上增强的操作
这样先用简单的字符串拼接就可以生成一个java文件了吧
可是还有一个问题,这个过程肯定是在程序运行的时候执行的,那这个java文件只是生成了,没有编译,所以没有办法调用啊
那在生成java文件以后,再去编译成class,给它加载到JVM里行不行
这…行是行,但是这套操作太复杂了啊,先用字符串手动拼接一个java文件,还要用代码编译加载,有这功夫我还不如自己去写一个代理类
别急,JDK已经帮我们实现了一套操作了,我们直接用就可以
来看看怎么用,前面都一样,多了一个李四的类用来测试,重点在PersonProxy.java
IPerson.java
public interface IPerson {
void doSomeThing();
}
ZhangSan.java
public class ZhangSan implements IPerson{
@Override
public void doSomeThing() {
System.out.println("我是张三,我在吃饭");
}
}
LiSi.java
public class LiSi implements IPerson{
@Override
public void doSomeThing() {
System.out.println("我是李四,我在吃饭");
}
}
PersonProxy.java
public class PersonProxy implements InvocationHandler {
private IPerson target;
//获取代理对象
public IPerson getInstance(IPerson person) {
this.target = person;
//传入三个参数,第一个是类加载器,用于动态加载class文件到JVM中,第二个是需要实现那些接口来确定要实现那些方法,第三个参数是一个处理器,所有实现的方法都会调用这个处理器的invoke方法来实现自定义代理类中的方法操作
return (IPerson) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
//被代理对象的所有方法都会走到这里,在这里可以自定义方法操作
//第一个参数为被代理对象,第二参数为正在调用的方法,第三个参数为正在调用方法传入的参数
//返回值为该方法的返回值
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置方法
System.out.println("我是代理类,这个人要开始吃饭了");
//代理对象的方法
Object result = method.invoke(target, args);
//后置方法
System.out.println("我是代理类,这个人吃完饭了");
return result;
}
}
Test.java
public class Test {
public static void main(String[] args) {
PersonProxy personProxy = new PersonProxy();
//传入需要被代理的对象,还是用接口接收
IPerson zhangsanProxy = personProxy.getInstance(new ZhangSan());
//直接调用就可以
zhangsanProxy.doSomeThing();
IPerson lisiProxy = personProxy.getInstance(new LiSi());
lisiProxy.doSomeThing();
}
}
当然上面说的只是最基本的流程,JDK的实现流程要复杂的多
呃…我看上面都实现一个接口的类,如果被代理对象是没有实现接口的怎么办?
那,这套流程就不行了,下面介绍一个第三方的解决方案
CGLib动态代理
这个一般都是在框架中集成的,只需要配置一下就可以,所以只做简单使用介绍
其他类都没有变,这里贴上改动的PersonProxy.java和Test.java
PersonProxy.java
public class PersonProxy implements MethodInterceptor {
//因为不需要接口,所以直接传入Class就行
public Object getInstance(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
//和JDK的invoke方法基本类似,但是注意调用方法部分,没有使用反射,而是使用了FastClass机制
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//前置方法
System.out.println("我是代理类,这个人要开始吃饭了");
//代理对象的方法
Object result = methodProxy.invokeSuper(o, objects);
//后置方法
System.out.println("我是代理类,这个人吃完饭了");
return result;
}
}
Test.java
public class Test {
public static void main(String[] args) {
PersonProxy personProxy = new PersonProxy();
//这里传Class就可以
IPerson zhangsanProxy = (ZhangSan) personProxy.getInstance(ZhangSan.class);
zhangsanProxy.doSomeThing();
IPerson lisiProxy = (LiSi) personProxy.getInstance(LiSi.class);
lisiProxy.doSomeThing();
}
}
总结
JDK和CGLib动态代理的区别
- JDK动态代理需要被代理类实现某个接口,而CGLib动态代理不需要
- 两种都是在运行期间动态生成class,但是CGLib的生成效率比JDK低
- JDK动态代理使用反射调用方法,CGLib使用FastClass机制调用,JDK的执行效率比CGLib低
代理模式的优缺点
优点:
- 代理模式能将代理对象与真实被调用的目标对象分离。
- 一定程度上降低了系统的耦合度,扩展性好。
- 可以起到保护目标对象的作用。
- 可以对目标对象的功能增强。
缺点:
- 代理模式会造成系统设计中类的数量增加。
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
- 增加了系统的复杂度。