美文网首页
java静态代理和动态代理

java静态代理和动态代理

作者: JerrysCode | 来源:发表于2021-02-17 11:50 被阅读0次

    所谓代理模式就是当对象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等第三方库。

    相关文章

      网友评论

          本文标题:java静态代理和动态代理

          本文链接:https://www.haomeiwen.com/subject/fafdoktx.html