首页 > 编程语言 >Java动态代理机制

Java动态代理机制

时间:2023-01-16 14:35:50浏览次数:63  
标签:Java target 代理 Class public InvocationHandler 动态 final

概念

代理模式是Java当中最常用的设计模式之一。其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。而Java的代理机制分为静态代理和动态代理,这里学习Java自带的jdk动态代理机制。

静态代理示例

静态代理是在编译使用时,定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。我们用一个出租房子作为实例讲解。

  1. 定义一个接口
public interface Rental {
    public void sale();
}
  1. 定义委托类
public class Entrust implements Rental{
    @Override
    public void sale() {
        System.out.println("出租房子");
    }
}
  1. 定义代理类
public class AgentRental implements Rental{
    private Rental target;
    public AgentRental(Rental target) {
        this.target = target;
    }

    @Override
    public void sale() {
        System.out.println("房子出租价位有1k-3k");
        target.sale();
    }
}

然后我们通过生成委托类实例对象,并将对象传入代理类构造函数中。

public class Test {
    // 静态代理
    public static void consumer(Rental subject) {
        subject.sale();
    }
    public static void main(String[] args) {
        Rental test  = new Entrust();
        System.out.println("使用代理之前:");
        consumer(test);
        System.out.println("使用代理之后:");
        consumer(new AgentRental(test));
    }
}

静态代理的优点:

在不改变委托类Entrust源代码的情况下,通过代理类AgentRental来修改委托类Entrust的功能,从而实现“代理”操作。

静态代理的缺点:

当需要过多的代理类对委托类进行修改的情况,会出现如下情况:

  1. 当接口类需要增加和删除方法的时候,委托类和代理类都需要更改,所以不易维护。
  2. 同时需要代理多个类的时候,每个委托类都需要编写一个代理类,会导致代理类繁多,不好管理。

正是因为静态代理存在如上的缺点,所以就有了动态代理机制。

动态代理示例

动态代理介绍

Java动态的代理位于java.lang.reflect包下,一般仅涉及java.lang.reflect.Proxy类与InvocationHandler接口,使用其配合反射实现动态代理的操作。

InvocationHandler接口:负责提供调用代理操作,是由代理对象调用处理器实现的接口,定义了一个invoke方法,每个代理对象都有一个关联的接口。当在代理对象上调用方法时,该方法会被自动转发到InvocationHandler.invoke()方法来进行调用。

Proxy类:负责动态构建代理类,提供4个静态方法:

  1. getInvocationHandler(Object proxy):通过指定代理类实例查找与它相关联的调用处理器实例
  2. getProxyClass(ClassLoader loader, Class<?>... interfaces):通过指定类加载器获取动态代理类对象
  3. isProxyClass(Class<?> cl):通过传入类判断是否为一个动态代理类
  4. newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):通过类加载器、接口组、调用处理器生成代理类

实现过程

通过上述流程图我们首先需要自定义调用处理器

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

public class DynamicAgentRental implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("房子出租价位有1k-3k");
        Object result = method.invoke(target, args);
        return result;
    }
}

之后我们写测试用例

import java.lang.reflect.Proxy;

public class DynameicTest {
    public static void main(String[] args) {
        // 创建委托类对象实例
        Entrust entrust = new Entrust();
        // 获取类加载器
        ClassLoader classLoader = entrust.getClass().getClassLoader();
        // 获取接口组
        Class<?>[] interfaces = entrust.getClass().getInterfaces();
        // 获取调用处理器
        DynamicAgentRental dynamicAgentRental = new DynamicAgentRental(entrust);

        // 生成代理类
        Rental newProxyInstance = (Rental)Proxy.newProxyInstance(classLoader, interfaces, dynamicAgentRental);

        // 执行代理类方法
        newProxyInstance.sale();
    }
}

ysoserial示例

在ysoserial工具中,很多的poc和exp都用到了动态代理,这里就来看看其中的CommonsCollections1.java,抛去前边的Transformer链,之后使用ysoserial.payloads.util.Gadgets创建动态代理

    final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

    final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

跟进Gadgets.createMemoitizedProxy 方法:

    public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception {
        return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
    }

这里继续跟进createMemoizedInvocationHandler方法:

    public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";

    public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception {
        return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
    }

这里又调用了Reflections.getFirstCtor,继续跟进:

    public static Constructor<?> getFirstCtor(final String name) throws Exception {
        final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
        setAccessible(ctor);
        return ctor;
    }

这里传入参数name的值为sun.reflect.annotation.AnnotationInvocationHandler,很明显这里通过反射返回了AnnotationInvocationHandler的构造器

之后在createMemoizedInvocationHandler方法中通过返回的构造器创建了类对象示例(其也就是个调用处理器)并返回

所以在createMemoitizedProxy方法中的第一个参数是AnnotationInvocationHandler调用处理器,然后传入createProxy方法,跟进:

    public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) {
        final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1);
        allIfaces[ 0 ] = iface;
        if ( ifaces.length > 0 ) {
            System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
        }
        return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih));
    }

该方法前边的几句话都是在创建接口类数组,其就是实现将iface(Map.class)添加到ifaces(new Class[]{}是个空数组)的第一位

最后通过Proxy.newProxyInstance去创建代理类,第一个参数为类加载器,第二个参数为类接口数组,第三个参数就是AnnotationInvocationHandler调用处理器。之后通过iface.cast方法进行对象类型转换(iface为Map对象类型)

参考文章

JAVA安全基础(三)-- java动态代理机制 - 先知社区

标签:Java,target,代理,Class,public,InvocationHandler,动态,final
From: https://www.cnblogs.com/seizer/p/17055308.html

相关文章

  • iview daterange动态设置可选范围
    需求:iview中日期选择控件daterange动态设置日期可选范围,如选择一个月内。当用户选择第一个日期后,往前、往后,都只能选择一个月内的日期。思路:1、当用户选中第一个日期时,......
  • linux加载动态库.so的3种方法
        昨天同事联系我,他部署新版本的MS软件提示找不到动态库。但是他能找到这个动态库文件,但不知道如何加载。这样的问题对于我来说是个再简单不过的问题,但对于一个新......
  • javaScript中的一些简写,请备好!
    废话不多说,直接列举一些JavaScript中的简写语法,仅供大家参考!1、当我们确实有一个对象数组并且我们想要根据对象属性查找特定对象时,find方法确实很有用。constdata=[......
  • JavaScript 浅拷贝和深拷贝
    JavaScript中的拷贝分为两种:浅拷贝和深拷贝。一、浅拷贝浅拷贝是指在拷贝过程中,只拷贝一个对象中的指针,而不拷贝实际的数据。所以,浅拷贝中修改新对象中的数据时,原对象中......
  • java8 apline docker
    FROMalpine:3.11ADDjdk-8u202-linux-x64.tar.gz/usr/java/jdk/ENVJAVA_HOME/usr/java/jdkENVPATH${PATH}:${JAVA_HOME}/binADDdata-integration/usr/kettle......
  • java Properties类
    Java中的Properties文件是一种配置文件,主要用于表达配置信息,格式是文本文件,文件的内容是“键=值”,在properties文件中,可以用“#”来注释。Properties 类表示了一个持久的......
  • 【学习日志】Java8的CompletableFuture
    Java8引入的CompletableFuture,对Future做了改进:1.可以传入回调对象,不再像Future那样循环查询执行结果。2.另外可以将多个Future结合到一起并行或串行执行,主要方法如下:......
  • python和C++调用动态库
    python和C++调用动态库python和C++相互调用动态库的方法有4种:python调用C/C++编译的动态库python调用python编译的动态库C/C++调用python编译的动态库C/C++调用C/C++......
  • 内网Linux下安装Nginx1.23,添加stream模块实现tcp/udp代理转发
    环境:centos7.6ngx_stream_core_module这个模块在1.9.0版本后将被启用。但是并不会默认安装,需要在编译时通过指定--with-stream参数来激活这个模块,window下并不支持udp......
  • Java对象转JSON动态设置字段
    需求User类:@DatapublicclassUser{ privateStringname; privateIntegerage;}序列化成JSON时,处理动态增加一个sex字段{ "name":"张三", "age":20, "sex......