• 设计模式之代理模式(proxy)


    前言

    代理模式算是Java设计模式中最难的一个,因为它涉及到的东西比较底层。

    代理模式分静态代理和动态代理,静态代理很简单,难的是动态代理。

    静态代理

    例1(现实意义上的例子,被代理类没有实现接口)

    AMD厂商生产cpu,售卖cpu,他不直接在中国销售,而是通过代理商售卖。这里以京东代理为例

    被代理商AMD

    package com.zl.proxy;
    
    public class AMD {
    
        public void sellCPU(){
            System.out.println("sell amd cpu");
        }
    
    }

    代理商JD

    package com.zl.proxy;
    
    public class JD {
    
        AMD amd = new AMD();
    
        public void sellCPU(){
            System.out.println("前置操作,加价售卖,赚差价");
            amd.sellCPU();
            System.out.println("后置操作,提供优质售后服务");
        }
    
    }

    例2:(循序渐进,项目中会用到的逻辑)

    现在有一个Car类,实现了Movable接口,如下

    package com.zl.proxy;
    
    public interface Movable {
        void move();
    }
    package com.zl.proxy;
    
    public class Car implements Movable {
        @Override
        public void move() {
            System.out.println("didi baba..........");
        }
    }

    如果我现在想要记录Car移动的时间,怎么做?

    1,直接记录

    package com.zl.proxy;
    
    public class Car implements Movable {
        @Override
        public void move() {
            long startTime = System.currentTimeMillis();
            System.out.println("didi baba..........");
            long endTime = System.currentTimeMillis();
            System.out.println(endTime - startTime);
        }
    }

    如果拿不到源码,不能直接记录怎么办? 

    2,继承(不推荐,慎用继承)

    原因:耦合度太高。除了记录时间我还想要记录日志,记录权限,或者是记录时间和日志,先记录日志后计算时间,等等等。难道每一种都要写个类继承吗,会产生类爆炸

    package com.zl.proxy;
    
    public class Car2 extends Car{
        public void move(){
            long startTime = System.currentTimeMillis();
            super.move();
            long endTime = System.currentTimeMillis();
            System.out.println(endTime - startTime);
        }
    }

    3,代理

    时间记录代理类TimeProxy

    package com.zl.proxy;
    
    public class TimeProxy implements Movable{
        Movable m;
    
        public TimeProxy(Movable m) {
            this.m= m;
        }
    
        @Override
        public void move() {
            long startTime = System.currentTimeMillis();
            m.move();
            long endTime = System.currentTimeMillis();
            System.out.println(endTime - startTime);
        }
    }

    这样看也没有什么特别的,一样建立类,而且实现Movable接口是什么意思?

    实现Movable接口代表我代理类和被代理类具有相同操作,就好比我代理了九州风神的CPU散热器,那我肯定跟他一样卖散热器而不是卖热水器。

    特别之处请接着往下看

    LogProxy日志记录代理类

    package com.zl.proxy;
    
    public class LogProxy implements Movable{
        Movable m;
    
        public LogProxy(Movable m) {
            this.m= m;
        }
    
        @Override
        public void move() {
            System.out.println("log start......");
            m.move();
            System.out.println("log end......");
        }
    }

    假如说我现在先记录日志后记录时间,怎么做?再来一个代理类? no no no

    直接聚合即可

    Main类测试

    package com.zl.proxy;
    
    public class Main {
        public static void main(String[] args) {
            new LogProxy(
                    new TimeProxy(
                            new Car()
                    )
            ).move();
        }
    }

    输出:

    注意:

    最外层代理类先执行前置操作,紧接着是内层的前置操作和内层的后置操作,最后是外层的后置操作。

    另外,这看起来怎么跟decorator模式这么像?其实就可以把decorator理解为对代理对象加上装饰,只是语义上不同罢了。没有必要咬文嚼字。

     思考:

    我们现在的代理类只能代理Movable类型的,即实现了Movable接口的类。那如果我想对任意类进行代理,也就是Object类型的,怎么办?

    我需要一个全能的代理类,怎么做?——动态代理

    动态代理

     我不再直接创建代理对象,而是去动态的生成这样一个代理类,只是这样一个代理类我们看不到罢了。

    1,jdk动态代理

    还是上面的Movable接口和Car类

    package com.zl.proxy.dynamic;
    
    public interface Movable {
        void move();
    }
    package com.zl.proxy.dynamic;
    
    public class Car implements Movable {
        @Override
        public void move() {
            System.out.println("didi baba..........");
        }
    }

    Main方法,通过jdk Proxy类动态生成代理类

    package com.zl.proxy.dynamic;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class Main {
        public static void main(String[] args) {
            Car car = new Car();
            //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            Movable m = (Movable) Proxy.newProxyInstance(Car.class.getClassLoader(), new Class[]{Movable.class}, new MyInvocationHandler(car));
            m.move();
        }
    
        public static class MyInvocationHandler implements InvocationHandler{
            Car car;
            public MyInvocationHandler(Car car) {
                this.car = car;
            }
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("前置操作");
                Object o = method.invoke(car, args);
                System.out.println("后置操作");
                return o;
            }
        }
    }
    newProxyInstance有三个参数,分别代表:

    你要用哪一个classLoader把返回回来的代理对象load到内存,让它跟被代理对象用同一个即可

    你被代理代理对象实现了哪些接口或者说代理对象应该实现哪些接口

    被代理对象的方法调用时,我们怎么做处理

    Main方法执行输出

     到这我们发现,我只调用了move方法,并没有调用MyInvocationHandler 中的invoke方法,它怎么执行了?

    首先我们先把上面main方法中的注释代码释放

    这句话的意思是,当我在生成代理类时,把代理类以文件的形式输出来(我的jdk版本1.8,高版本jdk是另外一个参数“jdk.proxy.ProxyGenerator.saveGeneratedFiLes”)

     再次运行main方法

     我们神奇的发现在项目目录下生成了这样一个包,里面有个$Proxy.class文件。它就是我们动态生成的代理对象。

    它里面除了hashcode,toString方法等其他方法,还有move方法

    当我们在main方法中调用m.move()方法时,其实是调用了$Proxy.class里面的move方法,而它又调用的父类h属性的invoke方法,而这个h就是就是之前newProxyInstance中的MyInvocationHandler。所以调用m方法实际上调用了MyInvocationHandler中的invoke方法。

    (想要弄懂,建议自己动手实现并追踪查看)

    另外,它底层是怎么生成的代理类的呢?

    main方法newProxyInstance点进去

     跟踪

     继续

     继续

     

     然后在它里面找到这一句

    继续

     

     最后

     在这里为我们生成了方法

    如果想要继续往下深究,jdk动态代理它底层用的是ASM。

    ASM可以直接操作Java二进制字节码,在不需要Java源码的情况下生成,修改,删除class文件

    思考:jdk动态代理到这里,它有一个不足之处,就是我代理对象必须要实现接口,jdk才能去动态代理(jdk通过被代理类实现了哪些接口来判断其拥有的方法)。

    除此之外还有很多种动态代理方式,各有所长。如下。

    2,cglib动态代理

    Car类,此时没有实现接口

    package com.zl.proxy.cglib;
    
    import com.zl.proxy.dynamic.Movable;
    
    public class Car{
        public void move() {
            System.out.println("didi baba..........");
        }
    }

    Main类测试

    package com.zl.proxy.cglib;
    
    import net.sf.cglib.core.DebuggingClassWriter;
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    import java.lang.reflect.Method;
    
    public class Main {
        public static void main(String[] args) {
            //System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "G:/project/XXX");
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Car.class);
            enhancer.setCallback(new MyCallback());
            Car car = (Car)enhancer.create();
            car.move();
        }
    
        static class MyCallback implements MethodInterceptor{
    
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("cglib前置操作");
                Object result = methodProxy.invokeSuper(o, objects);
                System.out.println("后置操作");
                return result;
            }
        }
    }

    测试输出:

     cglib动态生成的代理类是我们被代理类的子类(enhancer.setSuperclass(Car.class)

    通过这一句输出cglib动态代理生成的class文件(注意:需要指定文件输出的系统盘路径。建议输出到项目路径下,通过idea反编译后容易查看)

    生成如下

     这里主要看上述箭头指向这一个类即可,它继承了Car类,它里面也有一个move方法

     var10000就是我们定义的myCallback类,调用代理类的move方法就自然而然调用了myCallback里面的intercept方法

    cglib的底层实现也是ASM。

    Spring AOP

    它的实现就是运用的cglib和jdk动态代理的组合

  • 相关阅读:
    JavaScript异步编程1——Promise的初步使用
    Pailler
    ElGamal
    RSA
    密码基础
    博客园中:为文章添加版权保护
    DCT实现水印嵌入与提取(带攻击)
    量子:基于EPR块对的两步量子直接通信
    量子:拜占庭协议和测谎问题的量子协议的实验证明
    liunx:网络命令
  • 原文地址:https://www.cnblogs.com/zhulei2/p/13753708.html
Copyright © 2020-2023  润新知