设计模式之代理模式
之前看了很多关于代理模式相关的文章,本篇主要是做一个整理和总结。主要是以下几点:
1、静态代理模式实现及优缺点
2、jdk动态代理模式实现及优缺点
3、cglib动态代理模式实现及优缺点
1、静态代理
静态代理可以做到不对目标对象进行修改的前提下,对目标对象进行功能的扩展和拦截。代理和被代理对象在代理之前是确定的。他们都实现相同的接口或者继承相同的抽象类。
先来看一个类图:
代理模式的角色:
- Subject:代理者与被代理者共同实现的接口,可以理解为需要代理的行为;
- RealSubject:被代理者,其为具有某种特定行为的实现者;
- ProxySubject:代理者,其会全权代理RealSubject所具有的功能,在实现其功能的基础上做一些额外的工作;
- Client:客户端,客户端访问代理者与访问被代理者具有类似的效果,其无法区分访问的是代理者还是被代理者。
静态代理简单实现如下:
public interface Subject { void say(String name); }
public class RealSubject implements Subject { @Override public void say(String name) { System.out.println("我是真实对象~~~"); } }
public class ProxySubject implements Subject { private Subject target = null; public ProxySubject(Subject target) { this.target = target; } @Override public void say(String name) { // 在转调具体的目标对象之前,可以执行一些功能处理,比如权限判断 eat(); // 转调具体的目标对象 target.say(name); // 在转调具体的目标对象之后,可以执行一些功能处理 // 这也是代理模式的核心 drink(); } public void eat() { System.out.println("我是代理对象,我要吃"); } public void drink() { System.out.println("我是代理对象,我要喝"); } }
可以看到,代理对象ProxySubject保存了一个Subject实例,当客户端调用ProxySubject的say方法时,其还调用了其它方法。在调用被代理对象的方法之前和之后都打印了相关的语句。如下是测试示例:
/** * 测试静态代理 */ @Test public void testStaProxy() { Subject sub = new ProxySubject(new RealSubject()); sub.say("xwj"); }
我是代理对象,我要吃 我是真实对象~~~ 我是代理对象,我要喝
优点:所有的类都是已经编写好的,客户端只需要取得代理对象并且执行即可,所以效率也是最高的
缺点:因为代理对象需要实现与目标对象一样的接口,会导致代理类十分繁多,不易维护,同时一旦接口增加方法,则目标对象和代理类都需要维护。
2、动态代理
2.1、jdk动态代理
jdk动态代理是jdk提供的相关类通过反射来实现的代理模式。jdk动态代理解决了静态代理需要为每个业务接口创建一个代理类的问题,但是jdk代理的限制也是比较明显的,即需要被代理的对象必须实现一个接口。
在实现代理模式时,只需要实现InvocationHandler接口即可。如下是实现该接口的示例:
public class JdkProxy implements InvocationHandler { private Object target; //代理中含有具体实现类的引用 public Object bind(Object obj){ //绑定具体实现类 this.target = obj; /* * 通过反射机制获取代理对象 * * loader 类加载器 * interfaces 实现接口 */ return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this); } /** * 参数: * proxy 被代理对象 * method 被代理对象的方法 * args 方法的参数 * * @return 方法的返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前事件处理..."); Object obj = method.invoke(target, args); System.out.println("后事件处理..."); return obj; } }
可以看到:
1、声明了一个Object类型的属性用来保存目标对象的实例,也是为啥可以通过代理类获取目标类的原因
2、在调用目标对象时,其首先会调用代理对象,然后在代理对象的逻辑中请求目标对象
如下测试示例:
/** * 测试jdk动态代理 */ @Test public void testJdkProxy() { // 实例化代理操作类 JdkProxy handler = new JdkProxy(); // 代理接口的实现类 Subject sub = (Subject) handler.bind(new RealSubject()); sub.say("xuwenjin"); }
前事件处理... 我是真实对象~~~ 后事件处理...
再试下如果代理一个没有实现接口的类:
public class Train { public void run(){ System.out.println("火车行驶中"); } }
/** * 测试jdk动态代理 */ @Test public void testJdkProxy() { // 实例化代理操作类 JdkProxy handler = new JdkProxy(); // 代理接口的实现类 // Subject sub = (Subject) handler.bind(new RealSubject()); // sub.say("xuwenjin"); // 代理没有接口的类 Train train = (Train) handler.bind(new Train()); train.run(); }
运行结果如下(抛出异常的原因待研究):
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy1 cannot be cast to com.xwj.proxy.Train at com.xwj.proxy.dynaproxy.jdk.Test.main(Test.java:22)
可以看出,使用jdk动态代理时,被代理的对象必须实现一个接口
2.2、Cglib动态代理
Cglib动态代理是功能最为强大的一种代理模式,因为其不仅解决了静态代理需要创建多个代理类的问题,还解决了jdk代理需要被代理对象实现某个接口的问题。其原理就是动态生成被代理类的子类字节码。由于是通过创建子类字节码来实现的,如果被代理类的方法被声明是final类型,则Cglib是无法正常运行的,因为final类型方法不能被重写。
在实现代理模式时,需要实现MethodInterceptor接口,简单示例如下:
public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); /** * 创建代理类 * * @param clazz * @return */ public Object getProxy(Class<?> clazz) { enhancer.setSuperclass(clazz); // 设置创建子类的类(即设置父类) enhancer.setCallback(this); return enhancer.create(); } /** * 拦截所有目标类方法的调用 * * obj 目标类的实例 method 目标类的方法 args 方法的参数 proxy 代理类的实例 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("前事件处理..."); Object retObj = proxy.invokeSuper(obj, args); // 代理类调用父类的方法 System.out.println("后事件处理..."); return retObj; } }
可以看到,在代理类中,初始化了一个类型为Enhancer的实例。当创建一个代理对象时,会通过Enhancer实例设置父类及代理回调类对象,最终会为该目标类创建相关的子类字节码,并且将代理逻辑植入到该子类字节码中
测试示例:
/** * 测试cglib动态代理 */ @Test public void testCglibProxy() { CglibProxy proxy = new CglibProxy(); // 获取Train的代理类 Train train = (Train) proxy.getProxy(Train.class); // 非final方法 train.run(); }
前事件处理...
火车行驶中
后事件处理...
再试下如果目标类中的方法是final类型。
向Train类中增加一个fianl方法:
public final void go() { System.out.println("这是一个final方法"); }
/** * 测试cglib动态代理 */ @Test public void testCglibProxy() { CglibProxy proxy = new CglibProxy(); // 获取Train的代理类 Train train = (Train) proxy.getProxy(Train.class); // 非final方法 //train.run(); // final方法 train.go(); }
运行结果:
这是一个final方法
可以看到,在go方法运行时,虽然运行成功了,但是代理类没起作用