java静态代理和动态代理

2021-02-17  本文已影响0人  JerrysCode

所谓代理模式就是当对象A无法直接引用对象B时,引入对象C作为中间人,对象A调用对象C,对象C调用对象B,这个中间对象C就是代理。


代理模式

代理模式应用场景

  1. 隔离变化:当对象B发生变化是,只需要修改代理对象C, 对象A不受影响。
  2. 一些“横切”功能可以放到代理对象中实现,如安全认证,鉴权、日志打印等。
    代理模式在软件开发中应用非常广泛,Srping AOP, gRPC远程调用都使用了代理模式。

java中的代理分为静态代理 和 动态代理。

静态代理

静态代理就是代理类是写死的,代理关系和代理类处理逻辑是“静态”的,无法在运行时更改

代码实现

  1. 先定义一个接口Movable,表示可以移动行驶的物体。
public interface Movable
{
    void move() throws Exception;
}
  1. 小汽车是可以行驶的,所以Car可以实现Movable接口
public class Car implements Movable
{
    @Override
    public void move()
    {
        System.out.println("汽车行驶中…,模拟核心业务");
    }
}
  1. 在main方法中执行代码
public static void main(String[] args) throws Exception
    {
        Movable car = new Car();
        car.move();
    }

控制台会输出

汽车行驶中…

这时候有新需求,希望在move方法前后分别记录汽车启动和停止的时间,该如何实现呢?

第一种方法:直接修改Car类的代码

在move方法的第一行和最后一行记录汽车开始和停止时间

public class Car implements Movable
{
    @Override
    public void move()
    {
        System.out.println("汽车启动:" + System.currentTimeMillis());
        
        System.out.println("汽车行驶中…,模拟核心业务");

        System.out.println("汽车停止:" + System.currentTimeMillis());
    }
}

这样做有两个缺点:
一是违反了“对扩展开放,对修改封闭的”开闭原则,我们修改了类Car的move方法,无法保证这个修改不会影响既有功能,move方法相关的业务逻辑都需要重新测试。
二是“记录启动时间”不属于核心业务,应该从核心类Car中分离

第二种方法是静态代理模式

  1. 首先定义一个静态代理类CarProxy,CarProxy记录汽车的启动和停止时间,真正的业务调用委托给realMovable
public class CarProxy implements Movable {
    private Movable realMovable;
    public CarProxy(Movable realMovable){
        this.realMovable = realMovable;
    }
    @Override
    public void move() throws Exception {
        //前置代理逻辑
        System.out.println("汽车启动:" + System.currentTimeMillis());

        //真实对象调用
        realMovable.move();

        //后置代理逻辑
        System.out.println("汽车停止:" + System.currentTimeMillis());
    }
}
  1. 执行代码
public static void main(String[] args) throws Exception {
    Movable realCar=new Car();    //创建真实业务对象,真正执行业务操作
    Movable proxyLogCar = new CarProxy(realCar);    //创建代理对象,真实对象作为参数传入
    proxyLogCar.move();
}

执行代码结果如下

汽车启动:1609428420257
汽车行驶中…,模拟核心业务
汽车停止:1609428420258

我们通过使用静态代理模式,在不修改Car类的情况下加入“记录汽车启动停止时间”的业务逻辑。


image.png

上面的例子中,Car和CarProxy要实现共同的接口Movable,意味着CarProxy只能代理实现了Movable接口的对象,不能代理其他接口。 而实际项目中的接口成百上千,为每个接口都创建一个代理类工作量太大,于是就有了动态代理。

动态代理

代理类不是预先写好的,而是在程序运行时动态创建。相比静态代理,动态代理更加灵活高效。

代码实现

JDK通过实现InvocationHandler接口定义代理逻辑,通过Proxy类来生成动态代理对象。

  1. 首先定义代理逻辑
public class LogHanlder implements InvocationHandler
{
    private Object target;

    //构造函数中传入要代理的对象
    public LogHanlder(Object object)
    {
        this.target = object;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        //前置代理逻辑
        System.out.println("汽车启动:" + System.currentTimeMillis());

        //真实对象调用
        method.invoke(target, args);

        //后置代理逻辑
        System.out.println("汽车停止:" + System.currentTimeMillis());

        return null;
    }
}
  1. 定义代理类
public static void main(String[] args) throws Exception
    {
        //要被代理的真实对象是car
        Movable car = new Car();

        //定义代理逻辑处理类,通过代理调用,会转发到这里
        InvocationHandler handler = new LogHanlder(car);
        
       //通过java反射机制,生成动态代理对象
        Movable proxyCar = (Movable) Proxy.newProxyInstance(
                car.getClass().getClassLoader(),   //指定动态代理类的classloader
                new Class[]{Movable.class},        //动态代理类要实现的接口
                handler                            //代理逻辑处理类
        );
        proxyCar.move();
    }

运行代码

汽车启动:1609429682889
汽车行驶中…,模拟核心业务
汽车停止:1609429682890

原理分析

这些类是怎么协同工作的呢?动态代理类在哪里呢?
我们在上述代码main函数中加入如下代码

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

重新运行程序,就能在java工程的 ./sun/proxy目录下看到 $Proxy0.class文件,反编译可以看到$Proxy0实现了Movable接口,在move方法中调用了InvocationHandler 实例的invoke方法,是不是和静态代理类长得很像呀??

public final class $Proxy0 extends Proxy implements Movable {
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    public final void move() throws Exception {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (Exception | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}
  1. java动态代理就是在程序运行时生成代理类的class文件
  2. JVM加载动态class文件
  3. 通过反射生成动态代理对象。

基于jdk实现动态代理,要求被代理类必须实现一个接口,这个条件大多数情况下都可以满足,因为“面向接口编程”要求每个业务类都要实现一个接口,但是个别场景下一些类可能没有实现任何接口,该如何实现动态代理呢?
这个时候就需要借助cglib, asm,javassist等第三方库直接操作字节码生成class文件。

总结

代理模式应用广泛,经常用于隔离变化,以及在代码中增加横切功能。本文介绍了java实现静态代理和动态代理的方法。动态代理的实现原理就是动态生成class文件,利用反射实例化代理对象。实现动态代理可以使用jdk自带的库,也可以借助于cglib, asm,javassist等第三方库。

上一篇下一篇

猜你喜欢

热点阅读