代理模式之静态代理和动态代理~

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 代理模式之静态代理和动态代理~

代理模式之场景模拟:

第一步:新建一个modules

第二步:创建接口,其中包含我们业务所涉及的功能

package spring_CalculatorProxy;
public interface Calculator {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}

第三步:创建接口的实现类并重写其中的方法

package spring_CalculatorProxy;
public class CalculatorImpl implements  Calculator{
    @Override
    public int add(int i, int j) {
      //实现日志功能--额外功能
        System.out.println("日志,方法:add,参数"+i+","+j);
        //核心功能--算数运算
        int result=i+j;
        System.out.println("方法内部:result"+result);
        System.out.println("日志,方法:add,结果"+result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        System.out.println("日志,方法:sub,参数"+i+","+j);
        int result=i-j;
        System.out.println("方法内部:result"+result);
        System.out.println("日志,方法:sub,结果"+result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        System.out.println("日志,方法:mul,参数"+i+","+j);
        int result=i*j;
        System.out.println("方法内部:result"+result);
        System.out.println("日志,方法:mul,结果"+result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        System.out.println("日志,方法:div,参数"+i+","+j);
        int result=i/j;
        System.out.println("方法内部:result"+result);
        System.out.println("日志,方法:div,结果"+result);
        return result;
    }
}

提出场景模拟中存在的问题:

现有代码缺陷:

针对带日志功能的实现类,我们发现有如下缺陷:

1: 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
2: 附加功能分散在各个业务功能方法中,不利于统一维护

解决思路:

解决这两个问题,核心就是:解耦,我们需要把附加功能从业务功能代码中抽取出来

但要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决,原因是面向对象是纵向继承机制,我们只能将连续执行的代码进行封装,而不能实现抽取任意的某几行不连续的代码,所以需要引入新的技术

代理模式:

其实代理模式在我们的日常生活中,也是非常常见的,比如:

广告商找明星拍广告需要通过经纪人
合作伙伴找老板谈合作约见面时间需要经过秘书
房产中介是买卖双方的代理

概念:

将非核心逻辑剥离出来以后,封装这些非核心逻辑的类,对象,方法

它是属于23种设计模式中的一种,属于结构型模式,它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用,让不属于目标方法核心逻辑的代码从目标方法中剥离出来—解耦,调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时附加功能能够集中在一起也有利于统一维护


之前我们都是调用目标方法,直接实现功能,如下所示:

使用代理模式之后,如下所示:

目标对象/类/方法:被代理“套用”了非核心逻辑代码,例如我们上述场景模拟中的Calculator


我们现在就是创建目标对象所对应的代理类,每次访问目标对象的时候,都是通过代理对象进行访问,我们就不再直接访问目标对象了,而是通过代理对象对其进行访问,代理对象是间接访问目标对象的功能,代理对象和目标对象实现的功能是相同的,那么如何说明他们实现的功能都是相同的呢?


代理对象去实现目标功能的时候,直接在代理对象中去调用目标对象实现功能的方法


拿我们上述场景模拟中的add方法来说,如果使用代理模式,那么意味着,代理对象中也会产生一个add方法,当我们需要访问add方法时,我们并不会直接访问该方法,而访问的是代理对象中的add方法,通过代理对象间接去访问,怎么个间接法呢?


即为在代理对象中的add方法中直接去调用目标对象的add方法,代理对象能够控制目标对象方法的执行,这样我们就能保证,无论是通过目标对象实现还是通过代理对象实现,最终所实现的结果是不变的


代理模式之静态代理实现:

静态代理的特点就是一对一,当前一个代理类对应一个目标类,当前代理类只能作为目标类的代理,代理对象是为目标对象创建一个访问它的对象,如果需要访问目标对象中的功能时,我们就通过代理对象来访问,既然是这样,那么是不是就可以说明只要是目标对象可实现的事情,代理对象也可以实现,从代码层面来说,目标对象所拥有的方法,代理对象也必须有,因为目标对象都是通过代理对象来进行间接访问的,我们可以通过代理对象去控制目标对象的执行过程从而加入一些额外的操作,这样可以进行代码增强的效果,代码不改变的情况下,通过代理对象间接去访问目标对象中的功能,并且在它实现功能的过程中去加入一些额外的操作


创建代理类:

1:代理类和目标类需要实现相同的功能----代理类和目标类实现相同的接口
2:目标对象是通过代理对象间接访问它----实现访问目标对象所实现的功能

package spring_CalculatorProxy;
public class Calculator_proxy implements Calculator{
    //声明目标类的实现类或接口
    private CalculatorImpl target;
    //创建目标类的对象---通过该对象调用目标类中的核心功能
    public Calculator_proxy() {
        target=new CalculatorImpl();
    }
    @Override
    public int add(int i, int j) {
        System.out.println("日志,方法:add,参数"+i+","+j);
        //通过创建的目标类对象去调用add方法
        int result= target.add(i,j);
        System.out.println("日志,方法:add,结果"+result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        System.out.println("日志,方法:sub,参数"+i+","+j);
        int result= target.sub(i,j);
        System.out.println("日志,方法:sub,结果"+result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        System.out.println("日志,方法:mul,参数"+i+","+j);
        int result= target.mul(i,j);
        System.out.println("日志,方法:mul,结果"+result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        System.out.println("日志,方法:div,参数"+i+","+j);
        int result= target.div(i,j);
        System.out.println("日志,方法:div,结果"+result);
        return result;
    }
}

目标类:

此时只专注于核心的算术运算功能

package spring_CalculatorProxy;
public class  CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        return i+j;
    }
    @Override
    public int sub(int i, int j) {
        return i-j;
    }
    @Override
    public int mul(int i, int j) {
        return i*j;
    }
    @Override
    public int div(int i, int j) {
        return i/j;
    }
}

编写测试类:

import org.junit.Test;
import spring_CalculatorProxy.CalculatorImpl;
import spring_CalculatorProxy.Calculator_proxy;
public class ProxyTest {
    @Test
    public void ProxyTest(){
       //通过代理类对象去调用目标方法,内部其实是通过代理类中的目标类的对象去访问目标类中的目标方法
        Calculator_proxy calculatorProxy=new Calculator_proxy();
        calculatorProxy.add(2,3);
        calculatorProxy.sub(20,10);
        calculatorProxy.div(18,6);
        calculatorProxy.mul(15,3);
    }
}

输出如下所示:

日志,方法:add,参数2,3
日志,方法:add,结果5
日志,方法:sub,参数20,10
日志,方法:sub,结果10
日志,方法:div,参数18,6
日志,方法:div,结果3
日志,方法:mul,参数15,3
日志,方法:mul,结果45

通过代理模式进行功能增强时,不仅只有目标功能前后,还可以通过try-catch-finally中加入额外的操作,但是并不是所有位置都可以,比如目标对象存在10行代码,我们不能说想在第五行添加就在第五行添加,虽然说我们可以在目标对象实现的过程中增加新的功能,但是我们只能在目标对象方法执行之前,执行之后,catch,finally这四个位置进行


引入动态代理:

静态代理确实实现了解耦,但是由于代码不够灵活不便于修改,就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,因此产生了大量重复的代码,日志功能还是分散的,没有统一管理,但如果我们将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现,这就需要使用动态代理技术了


那么动态代理技术的动态体现在哪里呢?


动态生成目标类所对应的代理类,使用动态代理技术就可以不用创建动态代理类,而是通过jdk提供的方法和API动态的为某一个目标类创建它的代理类,和静态代理不同的是,动态代理类起初是不存在的,我们需要通过jdk提供的API在代码运行的过程中,动态创建每个目标类所对应的动态代理类


动态代理的实现:

创建工厂类:

工厂类—>ProxyFactory 并不是真正的代理类,而是动态生成目标类所对应的代理类的一个工具类,该类本身并没什么功能,它并不是某一个目标类所对应的代理类,而是我们可以通过这个类帮助我们动态生成目标类的代理类,之所以为工厂,是因为它的功能是批量地生产代理类


下面为一个生产代理类工厂的创建过程:


由于该工厂的作用是生产代理类,而代理类所代理的对象又为目标类,因此我们需要先创建目标对象,由于我们不知道目标对象的类型,因此设置为Object,再创建有参构造或者set方法为目标对象赋值,如下所示:

package SpringCalculatorProxy;
public class ProxyFactory {
    private  Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
}

接下来我们通过使用jdk中的API中的newProxyInstance去创建代理类的实例,如下所示:

其动态创建代理类的过程是通过反射完成的:

newProxyInstance方法包含三个参数,如下所示:

第一步类加载的过程:

ClassLoader classLoader=this.getClass().getClassLoader();

在 Java 中,每个类都有一个对应的类加载器,用于加载和解析该类的字节码文件。类加载器负责将字节码文件加载到内存中,并生成对应的 Class 对象,以便在程序中使用,我们可通过当前对象去调用getClassLoader()以获取当前对象所属类的类加载器,用代码表示即为this.getClass().getClassLoader(),this.getClass() 返回当前对象所属的类的 Class 对象,getClassLoader() 方法返回的是一个 ClassLoader 对象,它是一个抽象类,具体的实现由不同的类加载器提供。在实际使用中,可以根据需要进行类型转换,或者使用 ClassLoader 类中定义的方法来加载和管理类。

第二步获取目标对象实现的所有接口的class对象的数组:

Class<?>[] interfaces= target.getClass().getInterfaces();

target.getClass().getInterfaces() 用于获取目标对象所实现的接口数组。


在 Java 中,一个类可以实现一个或多个接口,通过实现接口,类可以定义和实现接口中声明的方法。target.getClass() 用于获取目标对象的类的 Class 对象,getInterfaces() 方法是 Class 类的一个方法,用于获取当前类所实现的接口数组。 getInterfaces() 方法返回的是一个 Class<?>[] 数组,其中的元素是 Class 对象,表示目标对象所实现的接口。可以通过遍历数组或者直接访问数组元素来获取具体的接口信息。


第三步执行处理操作:

我们通过创建一个实现了 InvocationHandler 接口的匿名内部类的实例,该实例可以作为参数传递给 Proxy.newProxyInstance 方法,用于创建代理对象。


在 Java 中,InvocationHandler 是一个接口,用于在动态代理中处理代理对象的方法调用。它只有一个方法 invoke,在代理对象的方法被调用时,该方法会被调用。通过重写 invoke 方法,可以在代理对象的方法调用前后进行额外的操作,例如打印日志、权限验证等。

  InvocationHandler invocationHandler=new InvocationHandler() {
            //proxy:代理对象 method:要执行/重写的方法 args:要执行方法的参数列表
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //在调用目标对象的方法执行前进行额外的操作---输出日志信息
                System.out.println("日志,方法名:"+method.getName()+",参数:"+ Arrays.toString(args));
                //调用目标对象的方法
                Object result=  method.invoke(target,args);
                //在调用目标对象的方法执行后进行额外的操作---输出日志信息
                System.out.println("日志,方法名:"+method.getName()+",结果:"+ result);
                return result;
            }
        };

这样的日志信息并不完整,在学习静态代理的时候,我们说过进行功能增强时,我们可以通过try-catch-finally进行额外的操作,那么下面我们就演示一下:


修改ProxyFactory类中的getProxy方法中的InvocationHandler中的方法的代码,添加try-catch-finally代码块,如下所示,IDE中可使用快捷键Ctrl+Alt+t

InvocationHandler invocationHandler = new InvocationHandler() {
            //代理类中的方法如何重写
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    System.out.println("日志,方法名:" + method.getName() + ",参数:" + Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("日志,方法名:" + method.getName() + ",结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("日志,方法名:" + method.getName() + ",异常:" +e);
                } finally {
                    System.out.println("日志,方法名:" + method.getName() + ",方法执行完毕");
                }
                return result;
            }
};

完整代码如下所示:

package SpringCalculatorProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyFactory {
    private  Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    public Object getProxy() {
        //获取类加载器
        ClassLoader classLoader = this.getClass().getClassLoader();
        //2:获取目标对象实现的所有接口的class对象的数组---为了保证目标类和代理类实现的功能是一致的,代理类和目标类实现相同的接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //3:执行处理:设置代理类中的抽象方法如何重写
        InvocationHandler invocationHandler = new InvocationHandler() {
            //代理类中的方法如何重写
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    System.out.println("日志,方法名:" + method.getName() + ",参数:" + Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("日志,方法名:" + method.getName() + ",结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("日志,方法名:" + method.getName() + ",异常:" + e);
                } finally {
                    System.out.println("日志,方法名:" + method.getName() + ",方法执行完毕");
                }
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
    }
}

测试类:

package ProxyTest;
import SpringCalculatorProxy.Calculator;
import SpringCalculatorProxy.CalculatorImpl;
import SpringCalculatorProxy.ProxyFactory;
import org.junit.jupiter.api.Test;
public class ProxyTest {
    @Test
    public void ProxyTest(){
        ProxyFactory proxyFactory=new ProxyFactory(new CalculatorImpl());
        //由于我们不清楚此时代理类所代理的目标对象时什么类型,又不能使用Object类,但我们知道它实现的接口,因此可以通过上转型完成
        Calculator Proxy= (Calculator) proxyFactory.getProxy();
        Proxy.mul(2,4);
    }
}

输出如下所示:

日志,方法名:mul,参数:[2, 4]
日志,方法名:mul,结果:8
日志,方法名:mul,方法执行完毕

上述是未有异常出现的情况,似乎和为加try-catch-finally时的输出结果差别不大,那么下面我们进行有异常的测试:

在测试类中调用除法运算:

//被除数不能为0,因此这里会抛出ArithmeticException算数异常
Proxy.div(2,0);

测试结果如下:

动态代理有两种:

jdk代理:要求必须有接口,最终生成的代理类和目标类实现相同的接口在com.sun.proxy包下,类名为$proxy2
cglib代理:,生成的代理类最终会继承目标类,并且和目标类在相同的包下

静态代理和动态代理的区别:

静态代理需要代理类和被代理类实现同一个接口或继承同一个父类代理类在编译期间就已经确定代理类中的方法也都是固定的。而动态代理则是在运行时动态生成代理类,不需要代理类和被代理类实现同一个接口或继承同一个父类,代理类中的方法也可以动态添加或删除

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
11月前
|
机器学习/深度学习 计算机视觉
一文详解残差网络
残差网络(ResNet)源于2016年的论文《Deep Residual Learning for Image Recognition》,旨在解决深层网络中的梯度消失和爆炸问题。通过引入残差块,即在网络中添加跳跃连接,使得信息可以直接跨过多层传递,从而有效解决了网络加深导致的训练困难。ResNet不仅显著提高了模型性能,还促进了深度学习领域的发展。
1578 3
|
Kubernetes Cloud Native Go
云原生之旅:构建和部署一个简单的Go应用程序
【8月更文挑战第31天】在本文中,我们将探索如何利用云原生技术构建和部署一个Go语言编写的简单Web应用。通过实际操作示例,我们不仅能够了解云原生的基本概念,还能学习到如何在Kubernetes集群上运行和管理容器化应用。文章将引导读者从零开始,逐步搭建起自己的云原生环境,并实现代码的容器化与自动化部署,最终达到持续交付的目的。
|
存储 JSON 关系型数据库
mysql中find_in_set()函数用法详解及增强函数
总结而言,`FIND_IN_SET()`是MySQL中处理由逗号分隔的字符串列表的一种便捷方法,尤其适用于列表相对较短且不经常更改的场景。然而,对于更为复杂的需要高性能和可扩展性的数据库设计,它可能不是最优选择,应考虑使用更加正规化的数据库结构。
1746 2
mysql中find_in_set()函数用法详解及增强函数
|
Java 程序员 索引
Python中魔术方法汇总
Python中魔术方法汇总
207 0
|
人工智能 安全 量子技术
量子力学的挑战和未来:未解决的问题和可能的发展方向
量子力学作为现代物理学的基础理论,在过去几十年中取得了巨大的成功,并在许多领域展现出了巨大的应用潜力。然而,它仍然面临一些未解决的问题,如量子测量问题、量子力学与相对论的统一、退相干和纠缠保持等。未来,我们可以期待量子技术的进一步发展,包括量子计算、量子通信和量子感应等领域的突破,为人类带来更多的科学和技术进步。
410 0
量子力学的挑战和未来:未解决的问题和可能的发展方向
|
安全 编译器 数据库
C++特性——inline内联函数
C++特性——inline内联函数
|
XML 前端开发 Java
Spring Boot的Web开发之Thymeleaf模板引擎的解析及使用(Thymeleaf的基础语法以及常用属性)
Spring Boot的Web开发之Thymeleaf模板引擎的解析及使用(Thymeleaf的基础语法以及常用属性)
411 0
|
Java
74.【JavaWeb -02】(五)
74.【JavaWeb -02】
66 0
|
Linux 虚拟化
VMware虚拟机安装Linux教程(超详细) 1
VMware虚拟机安装Linux教程(超详细)
2279 0
|
资源调度 前端开发 搜索推荐
从0搭建vue3组件库: 如何完整搭建一个前端脚手架?
从0搭建vue3组件库: 如何完整搭建一个前端脚手架?
473 0

热门文章

最新文章