代理模式(Proxy Pattern)

·  阅读 249

一、代理模式的定义

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介,代理模式也叫做委托模式。

二、为什么使用代理模式

中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

三、代理模式优缺点

  代理模式的主要优点有:

代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

代理对象可以扩展目标对象的功能;

代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

  代理模式的主要缺点是:

在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;

增加了系统的复杂度;

四、代理模式的结构与实现

  代理的实现是有多种方式的,常见就是静态代理、动态代理(JDK动态代理、CGLIB动态代理),因此接下来一一讲解这三种实现方式。

  1、静态代理

  静态代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问。代理模式的主要角色如下:

抽象主题(MoveAble)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。

真实主题(Tank)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

代理(TimeProxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

  其结构图如图所示。

image.png

/**
 *  模拟坦克移动过程
 * @author: yangqiang
 * @create: 2021-02-25 22:01
 */
public interface MoveAble {
    void move();
}
复制代码
/**
 * @author: yangqiang
 * @create: 2021-02-25 22:02
 */
public class Tank implements MoveAble {
    @Override
    public void move() {
        System.out.println("移动中");
        try {
            Thread.sleep(new SecureRandom().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码
/**
 * @author: yangqiang
 * @create: 2021-02-25 22:05
 */
public class TimeProxy implements MoveAble{
    Tank tank;
    public TimeProxy() {
        this.tank = new Tank();
    }

    @Override
    public void move() {
        long start = System.currentTimeMillis();
        tank.move();
        long end = System.currentTimeMillis();
        System.out.println("tank跑了"+(end-start)+" ms");
    }
}
复制代码

public class Test {
    public static void main(String[] args) {
        TimeProxy timeProxy = new TimeProxy();
        timeProxy.move();
    }
}
复制代码

静态代理优点: 符合开闭原则的情况下对目标对象进行功能扩展
静态代理缺陷: 得为每一个代理对象都得创建代理类 工作量太大 不易管理 同时接口 一旦新增功能 代理类也得跟着变化

2、动态代理(JDK动态代理)

  静态代理会手动创建很多代理类的问题,动态代理就解决了这个问题,其中一种就是JDK自带动态代理。其通过自己实现InvocationHandler来实现动态代理,真正的代理对象由JDK再运行时为我们动态的来创建。其结构图如下:

image.png

注意Proxy.newProxyInstance()方法接受三个参数:
ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
Class[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

```java
public class LogHandler implements InvocationHandler {
    Object object;

    public LogHandler(Object object) {
        this.object = object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(proxy.getClass().getName());
        byte[] b = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces());
        FileOutputStream out = new FileOutputStream("./" + proxy.getClass().getSimpleName() + ".class");
        out.write(b);
        out.flush();
        out.close();
        return method.invoke(object, args);
    }
    public static MoveAble getProxy(){
        return (MoveAble) Proxy.newProxyInstance(Tank.class.getClassLoader(), new Class[]{MoveAble.class}, new LogHandler(new Tank()));
    }
}
复制代码
/**
 *  模拟坦克移动过程
 * @author: yangqiang
 * @create: 2021-02-25 22:01
 */
public interface MoveAble {
    void move();
    void eat();
}
复制代码
public class Tank implements MoveAble {
    @Override
    public void move() {
        System.out.println("移动中");
        try {
            Thread.sleep(new SecureRandom().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void eat() {
        System.out.println("坦克加油");
    }
}
复制代码
/**
 * 分离代理与被代理对象
 * 使用jdk动态代理
 * @author: yangqiang
 * @create: 2021-02-25 22:24
 */
public class Test {
    public static void main(String[] args) {
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");
        //reflection 通过二进制字节码分析类的属性和方法
        MoveAble proxy = LogHandler.getProxy();
        proxy.move();
        proxy.eat();
    }

}
复制代码

JDK动态代理总结:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是JDK自带动态代理只能支持实现了Interface的类。这点可以在生成的Proxy文件中看到。由于java是单继承 而生成的代理类对象继承了Proxy对象不能再做其他继承了。所以只能对接口实现。所以只能代理接口类。

public final class $Proxy0 extends Proxy implements MoveAble {
    private static Method m1;
    private static Method m4;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void move() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void eat() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("com.example.smallwhite.designpatterns.proxy.V3.MoveAble").getMethod("move");
            m3 = Class.forName("com.example.smallwhite.designpatterns.proxy.V3.MoveAble").getMethod("eat");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
复制代码

  3、动态代理(CGLIB动态代理)

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLIB了。CGLIB采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。

  我们通过实现MethodInterceptor来实现CGLIB动态代理。

public class CglibProxy implements MethodInterceptor {
  private Object target;

  public Object getInstance(final Object target) {
      this.target = target;
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(this.target.getClass());
      enhancer.setCallback(this);
      return enhancer.create();
  }
  @Override
  public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      return methodProxy.invoke(target,args);
  }
}
复制代码
public final class Tank {
    public void move(){
        System.out.println("tank在运动中!");
    }
}
复制代码
public class Test {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        Tank tank =(Tank) cglibProxy.getInstance(new Tank());
        tank.move();
    }
}
复制代码

CGLIB代理总结: CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

以上就是三种代理具体实现。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。在Spring的AOP编程中: 如果加入容器的目标对象有实现接口,用JDK代理;如果目标对象没有实现接口,用Cglib代理。

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改