[关闭]
@MrXiao 2018-04-25T14:02:37.000000Z 字数 3803 阅读 736

代理模式

设计模式


Java本身给我们提供了静态代理和动态代理两种代理方式,spring提供了基于类的代理方式:cglib.

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

1、介绍

意图:为其他对象提供一种代理,从而控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题。

关键代码:实现与被代理类的组合。

优点

  1. 职责清晰,高扩展性,智能化。
  2. 建立一套触发机制。

缺点

  1. 增加了代理对象,某些情况下会造成请求的处理速度变慢
  2. 需要额外的编码,某些情况实现很复杂

注意事项

  1. 与适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
  2. 与装饰着模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

2、静态代理

静态代理:代理类在程序运行前就已存在,即人为的编写代理类代码。代理类与委托类实现同一接口或派生自相同的父类。

以下是静态代理的简单实现:

  1. public interface House {
  2. void sayHi();
  3. }
  1. public class HouseImpl implements House {
  2. @Override
  3. public void sayHi() {
  4. System.out.println("welcome to my house!");
  5. }
  6. }
  1. public class ProxyHouse implements House {
  2. private House house;
  3. public ProxyHouse(House house) {
  4. this.house = house;
  5. }
  6. @Override
  7. public void sayHi() {
  8. System.out.println("I'm proxy");
  9. house.sayHi();
  10. }
  11. }
  1. public static void main(String[] args) {
  2. House h = new HouseImpl();
  3. h.sayHi();
  4. h = new ProxyHouse(h);
  5. h.sayHi();
  6. }

可以实现客户与委托类之间的解耦,在不修改委托类的前提下能够做一些额外的处理。
静态代理的局限在于运行前必须编写好代理类,且针对每个需要代理方法都必须单独实现。

3、动态代理

动态代理:在程序运行时创建的代理方式,即代理类是动态生成的,而不是提前编写的。
相比于静态代理,动态代理可以很方便的对代理类进行统一处理,而不用修改每个代理类的函数,常用于权限控制、日志记录等场景。

约束:委托类必须实现接口。

以下是动态代理的简单实现:在对某个类的一个方法的调用前和调用后都要做一下日志操作

  1. //一个普通的接口
  2. public interface House {
  3. void sayHi();
  4. }
  1. //委托类,需要增加日志的类
  2. public class HouseImpl implements House {
  3. @Override
  4. public void sayHi() {
  5. System.out.println("welcome to my house!");
  6. }
  7. }
  1. //动态代理的中介类,关键
  2. public class DynamicProxy implements InvocationHandler {
  3. private Object obj;//目标对象的引用,这里设计成Object类型,更具通用性
  4. public DynamicProxy(Object obj) {
  5. this.obj = obj;
  6. }
  7. @Override
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. System.out.println("方法执行前");
  10. Object res = method.invoke(obj, args);//调用目标对象的方法
  11. System.out.println("方法执行后");
  12. return res;
  13. }
  14. }
  1. public static void main(String[] args) {
  2. House h = new HouseImpl();
  3. //创建中介类,代理了委托类
  4. DynamicProxy dynamicProxy = new DynamicProxy(h);
  5. //获取代理类实例,代理了中介类
  6. House instance = (House)Proxy.newProxyInstance(House.class.getClassLoader(), new Class[]{House.class}, dynamicProxy);
  7. //代理对象调用方法
  8. instance.sayHi();
  9. }

Java的动态代理,实际上是两次静态代理的组合。动态代理的重点是中介类的实现,需要实现java.lang.reflect.InvocationHandler接口。

动态代理与静态代理的区别:

  1. Proxy类的代码被固定下来,不会因为业务的逐渐庞大而庞大;
  2. 可以实现AOP编程,这是静态代理无法实现的;
  3. 解耦,如果用在web业务下,可以实现数据层和业务层的分离;
  4. 动态代理的优势就是实现无侵入式的代码扩展。静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。所以引入动态代理来解决此类问题

4、CGLIB

在Spring AOP中,通常会用它来生成AopProxy对象。不仅如此,在Hibernate中PO(Persistant Object 持久化对象)字节码的生成工作也要靠它来完成。

以下是简单实现:

  1. //一个普通类,委托类
  2. public class HelloServiceImpl {
  3. void sayHello() {
  4. System.out.println("I'm helloServiceImpl");
  5. }
  6. }
  1. //实现MethodInterceptor接口,回调函数,是增强方法的具体实现
  2. public class CglibCallBack implements MethodInterceptor {
  3. @Override
  4. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  5. System.out.println("before");
  6. Object invokeSuper = proxy.invokeSuper(obj, args);
  7. System.out.println("after");
  8. return invokeSuper;
  9. }
  10. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Enhancer enhancer = new Enhancer();
  4. //设置委托类
  5. enhancer.setSuperclass(HelloServiceImpl.class);
  6. //设置回调,即增强实现的类
  7. enhancer.setCallback(new CglibCallBack());
  8. //创建代理类
  9. HelloServiceImpl helloService = (HelloServiceImpl)enhancer.create();
  10. //执行方法
  11. helloService.sayHello();
  12. }
  13. }

小结:

  1. 生成的代理类 HelloServiceImpl$$EnhancerByCGLIB$$1a4d04e8@32e6e9c3 继承委托类 HelloServiceImpl。注意:如果委托类被final修饰,那么该类不可被继承,也就不能被代理;同样,如果方法被final修饰,该方法也不可被代理。
  2. 代理类会为委托类的委托方法生成两个方法,一个是重写的sayHello()方法,另一个是CGLIB$sayHello$()方法,直接调用父类的方法。执行代理方法时,先判断是否存在实现MethodInterceptor接口的回调类对象,如果存在,则调用MethodInterceptor中的intercept方法,否则调用父类方法(不代理)。

    此处输入图片的描述

  3. cglib中的方法调用不是通过反射实现的,而是直接的方法调用。有一个数组存放着方法的引用。

5、三者对比

代理方式 实现 优点 缺点 特点
JDK静态代理 代理类与委托类实现同一接口,并且在代理类中需要硬编码接口 实现简单,容易理解 代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率很低 好像没啥特点
JDK动态代理 代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理 不需要硬编码接口,代码复用率高 只能够代理实现了接口的委托类 底层使用反射机制进行方法的调用
CGLIB动态代理 代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 不能对final类以及final方法进行代理 底层将方法全部存入一个数组中,通过数组索引直接进行方法调用

参考

  1. 深入理解CGLIB动态代理机制
  2. 深入理解JDK动态代理机制
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注