• 关于代理模式


    本篇博客的内容

    对于一个应用开发者而言,光学习框架的使用想要到达一个较高的水准是不可能的,在工作中也非常容易被被取代,比较好的提升方式是学习设计模式,探究框架背后的设计思想。
    本篇博客是我学习框架中遇到的第一个设计模式,代理模式,主要总结代理模式的作用,静态代理和动态代理分别如何实现。

    代理模式的作用

    代理的思想是角色的替换。现实生活中,请律师可以看作一种代理机制,我们自己对法律流程不清楚,只需要将资料提供给律师,他来帮我们做其它的工作。当然,律师并不能完全替代原告和被告,这个例子并不完全恰当。一个比较好的例子是,厂家与代理商。厂家把产品生产出来以后靠它自己销售渠道有限,它就会把产品交给代理商,让他帮忙售卖。对于我们客户来说,代理商替代了厂商的卖家身份。

    public class Factory {
        public void sale(){
            System.out.println("卖出产品");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    工厂类有个sale方法,逻辑就是卖出产品

    public class ProxySaler extends Factory{
        @Override
        public void sale() {
            System.out.println("找销售渠道");
            super.sale();
            System.out.println("赚取差价");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    ProxySaler就是代理商,它继承了Factory对象,实现了sale方法,在卖出产品的基础上,它增加了找销售渠道和赚取差价的过程。

    public class MainTest {
        public static void main(String[] args) {
            Factory factory = new Factory();
            factory.sale();
            Factory proxySaler = new ProxySaler();
            proxySaler.sale();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在使用时,因为proxySaler是Factory的子类,所以可以父类引用指向子类对象,这就实现了角色的完全代替。
    代理对象增强了被代理对象的功能。
    代理模式不仅有增强方法的功能这一个作用,还可以实现流程上的监管。比如,我想在对象执行某个方法时记录一些日志信息,或者统计方法的执行次数等等。

    静态代理及其局限性

    上面的继承方式就是实现静态代理的一种方法,用子类重写的增强方法代替父类。另一种方式是,代理对象和被代理对象同时实现某个接口,代理对象持有被代理对象的实例。

    public interface Sale {
        void sale();
    }
    
    • 1
    • 2
    • 3

    这是一个接口,它定义了sale方法。

    public class Factory implements Sale{
        public void sale(){
            System.out.println("卖出产品");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Factory是被代理对象,它实现了Sale接口。

    public class ProxySaler implements Sale{
        private  final Sale saleInstance;
    
        public ProxySaler(Sale saleInstance) {
            this.saleInstance = saleInstance;
        }
    
        public void sale() {
            System.out.println("找销售渠道");
            saleInstance.sale();
            System.out.println("赚取差价");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ProxySaler也实现了Sale接口,同时,它持有一个saleInstance,可以增强saleInstance的功能。

    public class MainTest {
        public static void main(String[] args) {
            Sale factory = new Factory();
            factory.sale();
            Sale proxySaler = new ProxySaler(factory);
            proxySaler.sale();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    静态代理确实可以利用代理对象来增强被代理对象在一些方法上的功能,但是它的缺点却也很多。
    首先,假如你有10个代理策略,就需要编写十个代理类,这会造成类别爆炸的问题,并且如果你的接口发生了改变,就需要对每一个代理类进行修改,增加了代码的维护难度。
    其次,如果一个接口有10个方法,你只想要增强其中一个方法,但是却必须重写其它九个方法,者增加了代码的冗余度。

    基于这些我们渴望的代理机制如下:

    1. 不需要我们去编写大量的代理类文件,能够在运行时动态的创建代理类
    2. 只对需要代理的方法进行代理

    Java提供的动态代理机制

     Sale proxyObj = (Sale)Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    factory.getClass().getInterfaces(),
                    (proxy, method, args1) -> {
                        System.out.println("寻找销售渠道");
                        Object result = method.invoke(factory, args1);
                        System.out.println("获得差价");
                        return result;
                    }
            );
            proxyObj.sale();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Proxy是java.reflect.reflect包中提供的工具类,它有一个静态方法newProxyInstance用来在运行时动态生成一个代理对象。
    在学习它的用法之前,先了解下面几个知识。

    1. 类加载器
      java源程序实际上是个文本文件,需要经过javac编译层字节码文件,JVM将某个类加载入内存的过程称为类加载。类加载器就是用来加载字节码的类,在Java中,默认使用AppClassLoader来加载用户编写的Java类,但是你可以自定义类加载器来自定义加载字节码文件的行为。
    2. 反射机制
      反射实际上是对字节码文件的操作。它将字节码文件映射成了Class对象,该Class对象包含了类别的字段,构造器,方法的信息,使得能够编写自由度很高的代码。

    我们先讨论下动态代理的源码逻辑是什么,再去解释它的参数和如何使用。首先,我们的目的是在内存中动态创建一个类,可以上面提到了要想创建一个类必须要有字节码文件加载到JVM虚拟机中。JDK不是魔术师,即使它想动态创建一个类也必须有相应的字节码文件。所以,它需要先获得对象需要增强哪些方法,修改这些方法并在内存中创建一个字节码文件,再用类加载器去加载这个字节码文件获得Class对象。有了Class对象以后就能够获得增强后的方法了。

    现在总结下newProxyInstance的参数,

    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
    
    • 1
    • 2
    • 3

    第一个是ClassLoader类型,表明接受一个类加载器,就如上面所提到的用来加载后面自己创建的字节码。我们需要传入什么加载器呢?如果没有特殊需求,传入AppClassLoader对象即可,可以通过多个接口访问到它

    ClassLoader.getSystemClassLoader()
    Thread.currentThread().getContextClassLoader();
    
    • 1
    • 2

    第二个参数是被代理对象实现的接口Class数组,这也是Java的动态代理缺陷之处,你只能增强它实现接口的方法。
    第三个是Java定义的接口,它只有一个invoke 方法,第一个参数是用来表示生成后的代理对象,method是被代理对象的接口方法,args表示传入方法的参数。
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

    import java.lang.reflect.Proxy;
    
    public class MainTest {
        public static void main(String[] args) {
            Sale factory = new Factory();
            Sale proxyObj = (Sale) Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    factory.getClass().getInterfaces(),
                    (proxy, method, args1) -> {
                        Object result = null;
                        if(method.getName().equals("sale")) {
                            System.out.println("寻找销售渠道");
                            result = method.invoke(factory, args1);
                            System.out.println("获得差价");
                        }else
                            result = method.invoke(factory, args1);
                        return result;
                    }
            );
            proxyObj.sale();
           
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    第三个参数,我们使用的是lambda表达式,可以看作匿名内部类。对于方法体中的内容,执行代理对象的任何方法都会被执行。通过method判断如果当前方法是Sale,然后执行增强方法的逻辑,如果不是sale,则方法正常执行。

    这种代理方法确实很强,但是缺点咱们已经提到了,代理对象只能向下转型成被代理对象实现的接口。

    有没有一种代理方法能够实现Java动态代理的功能,同时又可以转成被代理对象的类型(这样就能访问被代理对象的所有方法,实现真正意义上的替代)。

    cglib实现了上述的功能,它生成的代理对象是被代理对象的子类,利用这种继承的关系就可以拿到被代理对象的所有方法。

    public class Factory {
        public void sale(){
            System.out.println("卖出产品");
        }
        public void produce(){
            System.out.println("生产商品");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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 MainTest {
        public static void main(String[] args) {
            Factory factory = new Factory();
            Factory proxySaler = (Factory)Enhancer.create(factory.getClass(), new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
                        throws Throwable {
                    Object result = null;
                    if(method.getName().equals("sale")){
                        System.out.println("找销售渠道");
                        result = method.invoke(factory, objects);
                        System.out.println("赚取差价");
                    }else
                        result = method.invoke(factory, objects);
                    return result;
                }
            });
            proxySaler.produce();
            proxySaler.sale();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述
    可以发现factory的方法,proxyFactory都可以实现,也不要求factory实现任何接口。

  • 相关阅读:
    Docker: 小白之路九(从0搭建自己的Docker环境centos7)
    vue 01 创建一个简单vue页面
    ReaLTaiizor开源.NET winform控件库学习使用
    手写raft(二) 实现日志复制
    为什么我抓不到baidu的数据包
    Completeness (order theory)
    【Java】综合案例——发红包【界面版】
    Vue的transition组件
    01-25-javajvm-JVM和Java体系架构
    Spring Boot 3.0 正式发布了!一个超重要的版本!!
  • 原文地址:https://blog.csdn.net/qq_43152622/article/details/127420163