Java:动态代理

news2024/9/21 0:32:00

Java:动态代理

在这里插入图片描述

什么是代理

代理模式 是一种设计模式,它为其他对象提供了一种代理以控制对这个对象的访问。代理对象通常包装实际的目标对象,以提供一些附加的功能(如延迟加载、访问控制、日志记录等)。我们一般可以使用装饰器模式来包装实际对象,从而实现代理模式,比如说:

//具体提供的服务接口
interface HelloService {
    void sayHello();
}
//服务的具体实现类
class ServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello World!");
    }
}
//具体实现类的代理类--用来控制具体的服务访问和资源回收,日志打印等增强功能
class ServiceProxy implements HelloService{
    private HelloService target;
    ServiceProxy(HelloService target) {
        this.target = target;
    }

    @Override
    public void sayHello() {
        try {
            long currentTimes = System.currentTimeMillis();
            System.out.println("Invoke Time is:" + currentTimes);
            target.sayHello();
            Thread.sleep(2000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
            recycleRes();
        }
    }
	//回收资源的方法
    private void recycleRes()  {
        System.out.println("回收Over");
    }
}

代理模式的优点

  • 职责清晰:
    代理模式将真实对象的实现与代理对象的控制逻辑分开,使得每个对象都承担单一职责,符合单一职责原则。
  • 控制访问:
    代理可以控制对目标对象的访问,这在需要控制权限或在访问前后添加额外操作时非常有用。例如,在远程代理中,可以控制客户端与服务器之间的通信。
  • 增强功能:
    不修改目标对象的情况下,代理模式可以在目标对象的访问前后添加额外的逻辑。例如,缓存代理可以缓存对象的返回结果以减少重复计算;日志代理可以记录方法的调用。
  • 延迟实例化:
    虚拟代理可以在真正需要目标对象时才创建它,从而节省内存和性能。例如,在图形应用程序中,如果图像对象较大,可以在首次需要显示时才进行加载。
  • 灵活性和可扩展性:
    代理模式提供了一种灵活的方式来扩展对象的功能。通过使用不同类型的代理,开发者可以轻松切换或扩展目标对象的行为。
  • 保护目标对象:
    保护代理可以控制对目标对象的访问权限,防止不适当的操作。这在多用户环境中尤其有用。

动态代理

代理的类型具体又可以分为静态代理动态代理,所谓静态代理,意思就是代理对象是在编译期就已经生成了,无法变更。而动态代理的意思就是代理对象是在运行期动态生成的。

在Java中,反射模块里提供了一个接口InvocationHandler来帮助我们实现动态代理,一些知名的开源库,比如说Retrofit,也是通过动态代理来实现具体的方法调用的:

public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                if (method.getDeclaringClass() == Object.class) {
                //如果外部调用的是 Object 中声明的方法的话则直接调用
                //例如 toString()、hashCode() 等方法
                  return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                //根据 method 是否默认方法来决定如何调用
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

我们也可以改造之前的例子实现一个动态代理的例子:

public static void main(String[] args) {
        HelloService service = new ServiceImpl();
        //通过Proxy.newProxyInstance方法生成具体的动态代理对象
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
                //被代理的接口的类加载器
                service.getClass().getClassLoader(),
                //被代理的接口类型
                service.getClass().getInterfaces(),
                //具体实现了InvocationHandler接口的动态代理类
                new DynamicServiceProxy(service)
        );
        //通过动态代理对象访问
        proxy.sayHello();
    }
}

interface HelloService {
    void sayHello();
}

class ServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello World!");
    }
}

class DynamicServiceProxy implements InvocationHandler {
    private HelloService target;
    public static String resource1 = "资源1";
    public static String resource2 = "资源2";

    public DynamicServiceProxy(HelloService target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("拦截方法:"+method.getName());
        System.out.println("执行时间:" + System.currentTimeMillis());
        String res = (String) getRes();
        Object resp = null;
        if (res.equals(resource1)) {
            resp = method.invoke(target,args);
        } else {
            System.out.println(resource2+"不可用!!!");
        }
        System.out.println("获取资源:"+res);
        System.out.println("执行完毕");
        return resp;
    }

    public Object getRes() {
        Random random = new Random();
        int ran = random.nextInt(10);
        return ran <= 5 ? (Object) resource1 : (Object) resource2;
    }
}

我们把 getRes() 作为一个模拟线上获取资源的方法,当获取到资源一时执行被代理类的原有逻辑,当获取到资源二时,我们就完全拦截原有的逻辑,而去执行我们自己的逻辑。这样就相当于是可以动态的选择方法的实际执行逻辑

使用场景

这种场景在直觉上显然就很适合鉴权访问的场景,先在先上验证当前用户是否有相应的权限,如果确定有相应权限在执行访问的逻辑,反之则拦截并提示无权限。

我们先用静态代理代理的方法实现需求:

interface ConnectionInterface {
    public String getResource(String Id);
}

//静态代理管理访问权限
class ConnectionProxy implements ConnectionInterface {
    protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");
    private ConnectionInterface target;
    public ConnectionProxy(ConnectionInterface target) {
        this.target = target;
    }

    @Override
    public String getResource(String Id) {
        if (!whiteList.contains(Id)) {
            System.out.println("没有权限!");
            return "Error";
        };
        return target.getResource(Id);
    }
}

class ConnectionService implements ConnectionInterface {
    static Random random = new Random();
    static Map<String,Integer> resourceMap = Map.of(
            "Android",random.nextInt(),
            "IOS", random.nextInt(),
            "Web",random.nextInt(),
            "Server", random.nextInt()
        );
    @Override
    public String getResource(String Id) {
        return resourceMap.get(Id).toString();
    }
}

如果我们后续有一个新的接口,或者说接口升级的话,我们还需要为这个新接口新实现一个代理类,而用动态代理就可以用一个动态代理类管理这两个逻辑:

interface ConnectionInterface {
    public String getResource(String Id);
}

interface ConnectionInterfaceV2 {
    public String getResourceV2(String Id);
}

//静态代理管理访问权限
class ConnectionProxy implements ConnectionInterface {
    protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");
    private ConnectionInterface target;
    public ConnectionProxy(ConnectionInterface target) {
        this.target = target;
    }

    @Override
    public String getResource(String Id) {
        if (!whiteList.contains(Id)) {
            System.out.println("没有权限!");
            return "Error";
        };
        return target.getResource(Id);
    }
}

class ConnectionProxyV2 implements ConnectionInterfaceV2 {
    protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");
    private ConnectionInterface target;
    public ConnectionProxyV2(ConnectionInterface target) {
        this.target = target;
    }

    @Override
    public String getResourceV2(String Id) {
        System.out.println("新逻辑V2");
        if (!whiteList.contains(Id)) {
            System.out.println("没有权限!");
            return "Error";
        }
        return target.getResource(Id);
    }
}

class Dynamic_Proxy implements InvocationHandler {
    protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");
    private Object target;

    public Dynamic_Proxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("拦截方法:"+method.getName());
        String param = (String) args[0];
        if (target instanceof ConnectionInterface) {
            if (whiteList.contains(param)) {
                System.out.println("有权限 V1");
                return method.invoke(target,param);
            } else {
                System.out.println("无权限 V1");
                 return (Object) "Error";
            }
        }

        if (target instanceof ConnectionInterfaceV2) {
            if (whiteList.contains(param)) {
                System.out.println("有权限v2");
                return method.invoke(target,param);
            }
            System.out.println("无权限v2");
        }
        return (Object) "Default";
    }
}

class ConnectionService implements ConnectionInterface {
    static Random random = new Random();
    static Map<String,Integer> resourceMap = Map.of(
            "Android",random.nextInt(),
            "IOS", random.nextInt(),
            "Web",random.nextInt(),
            "Server", random.nextInt()
        );
    @Override
    public String getResource(String Id) {
        return resourceMap.get(Id).toString();
    }
}

这样我们相当于是减少了无用的代码量,实现了代码的逻辑复用。调用时我们可以这样使用:

public class DynamicPro {
    public static void main(String[] args) {
        ConnectionInterface service = new ConnectionProxy(new ConnectionService());
        ConnectionInterfaceV2 service2 = new ConnectionProxyV2(new ConnectionService());
        ConnectionInterface v1 = (ConnectionInterface)Proxy.newProxyInstance(
             service.getClass().getClassLoader(),
             service.getClass().getInterfaces(),
             new Dynamic_Proxy((Object) service)
        );
        v1.getResource("Windows");
        ConnectionInterfaceV2 v2 = (ConnectionInterfaceV2) Proxy.newProxyInstance(
                service2.getClass().getClassLoader(),
                service2.getClass().getInterfaces(),
                new Dynamic_Proxy((Object) service2)
        );
        v2.getResourceV2("Windows");
    }
}

这样我们轻松用一个代理类代理了两个对象。

具体的原理

这种在程序运行时动态修改方法入口的效果具体是基于Java的动态分派机制来实现的,即一个对象的方法调用总是在被调用时才真正确定其方法入口,对应到一个对象中,每个对象都有其的一个虚方法表,每次调用的时候就从这个虚方法表中查找具体的方法入口。

第二个机制就是基于Java中自己提供的反射框架,即在运行时可以动态生成方法对象,即Method对象,然后用Method对象作为逻辑,被代理类作为对象来执行。

使用 Proxy 类生成代理对象,InvocationHandler 接口来处理方法调用,是实现 AOP(面向切面编程)和其他动态功能的核心技术。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2123736.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++中的内存管理和模板初识

一、内存管理 1.1内存区域的划分 1.1.1内存划分区域图示 1.1.1补&#xff1a;堆和栈都可以进行动态分配和静态分配吗&#xff1f; 不是的&#xff0c;堆无法进行静态分配&#xff0c;只能动态分配&#xff1b;栈可以利用_alloca动态分配&#xff0c;但是分配的空间不能用fre…

基于Logistic-Map混沌序列的数字信息加解密算法matlab仿真,支持对文字,灰度图,彩色图,语音进行加解密

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于Logistic-Map混沌序列的数字信息加解密算法matlab仿真,系统包含GUI操作界面&#xff0c;系统支持对文字,灰度图,彩色图,语音进行加解密。 2.测试软件版本以及…

项目运行插件-日志管理

日志管理 项目运行时模块提供了项目日志收集&#xff0c;检索和保存查询方案等功能。 体验地址&#xff1a; http://119.163.197.219:13456/view/runtime/index.html#/log/aioLogPage 沟通加QQ群 &#xff1a; 908377977 gitee 开源地址 &#xff1a; https://gitee.com/aio…

打印文档时,只有图片中的文本不清晰该如何处理

最近打印东西的时候&#xff0c;发现只有图片中的文本并不清晰&#xff0c;就想研究一下如何改善这个问题。 打印机是佳能的 MF113w&#xff0c;一个不错的多功能激光黑白打印机&#xff0c;支持无线打印。唯一问题就是每次 DHCP 分配 IP 到期后&#xff0c;这款打印机就会亮错…

AI提质增效率赋能工业产品质检,基于高精度YOLOv5全系列参数【n/s/m/l/x】模型开发构建工业生产场景下PCB电路板缺陷问题智能化分割检测识别分析系统

在PCB电路板的生产制造过程中&#xff0c;质量检测是确保产品质量、维护品牌形象的关键环节。然而&#xff0c;传统的人工检测方式依赖于经验丰富的工人师傅通过光学显微镜等设备进行逐块检查&#xff0c;这不仅劳动强度大、效率低下&#xff0c;而且受限于人的主观判断、视力疲…

《华为 eNSP 模拟器安装教程》

1.电脑安装环境要求&#xff1a; 检查电脑是否安装过 eNSP 和依赖软件&#xff0c;如果有&#xff0c;请全部卸载。 安装软件列表&#xff1a; 2.软件安装&#xff1a; 安装 WinPcap&#xff1a; 打开安装包&#xff0c;单击【Next】 单击【I Agree】 单击【Install】 单击【…

《信息系统安全》课程实验指导

第1关&#xff1a;实验一&#xff1a;古典密码算法---代换技术 任务描述 本关任务&#xff1a;了解古典密码体制技术中的代换技术&#xff0c;并编程实现代换密码的加解密功能。 注意所有明文字符为26个小写字母&#xff0c;也就是说字母表为26个小写字母。 相关知识 为了完…

1、常用的数据库、表操作

基本的建表和数据库拷贝操作。 一、数据定义语言DDL show databases; # 查看全部数据库 show create database db; # 查看数据库db create database db; # 创建数据库db drop database db; # 删除数据库db use db; # 使用数据库db基本…

1 Linux SSH安全加固_linux system-auth

![在这里插入图片描述](https://img-blog.csdnimg.cn/20201117150524918.png?x-oss-processimage/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwOTA3OTc3,size_16,color_FFFFFF,t_70#pic_center) ![在这里插入图片描述](https://…

11、LLaMA-Factory自定义数据集微调

1、数据集定义 针对实际的微调需求&#xff0c;使用专门针对业务垂直领域的私有数据进行大模型微调才是我们需要做的。因此&#xff0c;我们需要探讨如何在LLaMA-Factory项目及上述创建的微调流程中引入自定义数据集进行微调。**对于LLaMA-Factory项目&#xff0c;目前仅支持两…

什么是 Grafana?

什么是 Grafana&#xff1f; Grafana 是一个功能强大的开源平台&#xff0c;用于创建、查看、查询和分析来自多个来源的数据。通过可视化仪表盘&#xff08;Dashboard&#xff09;&#xff0c;它能够帮助用户监控实时数据、生成历史报告&#xff0c;甚至进行预测分析。Grafana…

深入理解Java虚拟机:Jvm总结-类文件结构以及类加载机制

第六章 类文件结构 6.1 意义 代码编译的结果从本地机器码转变为字节码&#xff0c;冲破了平台界限。 6.2 无关性的基石 实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java虚拟机不与包括Java语言在内的任何程序语言绑定&#xff0c;它只与“Class文件”这种特定的二…

vue2实践:el-table实现由用户自己添加删除行数的动态表格

需求 项目中需要提供一个动态表单&#xff0c;如图&#xff1a; 当我点击添加时&#xff0c;便添加一行&#xff1b;点击右边的删除时&#xff0c;便删除这一行。 至少要有一行数据&#xff0c;但是没有上限。 思路 这种每一行的数据固定&#xff0c;但是不定行数的&#x…

校园水电费管理|基于java的校园水电费管理小程序系统 (源码+数据库+文档)

校园水电费管理 目录 基于java的校园水电费管理小程序系统 一、前言 二、系统设计 三、系统功能设计 小程序端 后台功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕…

Selenium使用浏览器用户配置进行测试

本文主要介绍了如何在使用Selenium WebDriver进行自动化测试时&#xff0c;创建和使用自定义的Firefox配置文件。 什么是Firefox配置文件&#xff1f; Firefox会将用户的个人信息&#xff0c;如书签、密码和用户偏好设置存储在一个称为配置文件的文件集合中&#xff0c;这些文…

C++设计模式——Iterator迭代器模式

一&#xff0c;迭代器模式的定义 迭代器模式是一种行为型设计模式&#xff0c;它使得遍历一个容器对象中的元素变得更加简单。 迭代器模式将遍历操作从容器对象&#xff08;如集合、列表&#xff09;中分离出来&#xff0c;它通过迭代器对象来遍历容器对象中的元素&#xff0…

若依后端正常启动但是uniapp移动端提示后端接口异常

pc端能用模拟器也能正常连接接口&#xff0c;手机端真机调试连不上接口 解决&#xff1a; 1. 先看config.js的 填自己的ip地址 module.exports { // baseUrl: https://vue.ruoyi.vip/prod-api, baseUrl: "http://192.168.101.5:8080", } 2.网络环境问题&#…

mysql -小计

//表单某字段值为当前打开文档Id (function () { var rdoc getRelateDocument(); var warehouseName rdoc.getItemValueAsString(“warehouseName”); var name rdoc.getItemValueAsString(“name”); var color rdoc.getItemValueAsString(“color”); var batchNumber r…

2024年Web前端JavaScript面试题整理附答案

&#xff08;1&#xff09;两等号判等&#xff0c;会在比较时进行类型转换&#xff1b; &#xff08;2&#xff09;三等号判等(判断严格)&#xff0c;比较时不进行隐式类型转换&#xff0c;(类 型不同则会返回false)&#xff1b; &#xff08;3&#xff09;Object.is 在三等号…

基于风力发电系统的开关磁阻Simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于风力发电系统的开关磁阻Simulink建模与仿真&#xff0c;开关磁阻风力发电系统&#xff08;Switched Reluctance Wind Power Generation System&#xff09;利用开关磁阻电…