JAVA 动态代理
2021-05-06 18:44:23 Author: www.secpulse.com(查看原文) 阅读量:163 收藏

JAVA 动态代理

因为要分析一下 ysoserial 中的反序列化链 Jdk7u21,其中涉及到了关于 java 动态代理的相关知识,之前对这方面没有了解,通过看了两天的文章,大致有所了解,做一个简单的总结。

Proxy 代理模式是一种结构型设计模式,主要解决直接访问对象时带来的问题。

代理类和委托类会实现相同的接口,代理类主要负责实现为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类的对象本身并不真正实现服务,而是通过调用委托类对象的相关方法来实现。如此一来,通过代理类进而访问委托类,有效的控制对委托类对象的直接访问,起到了隐藏和保护委托类对象,同时可以附加多种功能在代理类中。

静态代理

静态代理:由程序员创建代理类或者特定工具自动生成源代码再对其进行编译,在程序运行前代理类的 .class 文件就已经存在了。也就是在编译之前就已经将接口、委托类、代理类全部都确定好了。

构造一个简单的静态代理的例子。班级的同学需要向老师提交班费,通过班长把钱转交给老师,班长代理学生上交班费,班长就是学生的代理。

创建一个 Person 接口,这个接口就是 学生(委托类)、班长(代理类)的公共接口,他们的共同方法是上交班费

public interface Person {
   void giveMoney();
}

Student (学生)类 实现 Person 接口,实现上交班费的操作

public class Student implements Person{
   private String name;
   public Student(String name){
       this.name = name;
   }

   @Override
   public void giveMoney() {
       System.out.println(name+" giveMoney");
   }
}

StudentProxy (班长)类 实现 Person 接口,同时持有学生类对象,可以代理 Student 执行 giveMoney

public class StudentProxy implements Person{
   Student student;
   public StudentProxy(Person student) {
       if (student.getClass() == Student.class) {
           this.student = (Student) student;
       }
   }
   public void giveMoney(){
       student.giveMoney();
   }
}

进行测试

public class ProxyTest {
   public static void main(String[] args){
       Person zhangsan = new Student("张三");
       Person monitor = new StudentProxy(zhangsan);
       monitor.giveMoney();
   }
}

运行结果

20210428161247.png

没有直接去调用 zhangsan (委托类)的 giveMoney 的方法,而是通过 monitor (代理类)来执行。

代理模式最为重要的就是有一个公共的接口(Person),一个具体的类 (Student),一个代理类(StudentProxy)。代理模式在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途,我们就以在代理过程中加上一些其他用途。比如班长在帮张三上交班费前向老师反应张三最近学习有很大的进步,通过代理模式可以很轻松的办到。

public class StudentProxy implements Person{
   Student student;
   public StudentProxy(Person student) {
       if (student.getClass() == Student.class) {
           this.student = (Student) student;
       }
   }
   public void giveMoney(){
       System.out.println("学习有很大的进步!");
       student.giveMoney();
   }
}

20210428162550.png

虽然静态代理实现简单,但是当场景稍微复杂时

  • 当需要代理多个类时,由于代理类要实现和委托类的一致接口

    • 只维护一个代理类,由于一个代理类实现多个接口,会导致代理类过于庞大

    • 新建多个代理类,每个目标对象对应一个代理类,会导致产生过多的代理类

  • 当接口需要增加、删除、修改方法时,目标对象与代理对象都要同时修改,不易维护

动态代理

动态代理是程序运行时运用反射机制动态创建代理类而成的。

构造一个简单的动态代理的例子,还是选择班长帮学生代交班费。

创建一个 Person 接口,这个接口就是 学生(委托类)、班长(代理类)的公共接口,他们的共同方法是上交班费、登记名字

public interface Person {
   void giveMoney();
   void giveName();
}

Student (学生)类 实现 Person 接口,实现上交班费、登记姓名的操作

public class Student implements Person{
   private String name;
   public Student(String name){
       this.name = name;
   }

   @Override
   public void giveMoney() {
       try {
           Thread.sleep(500);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println(name+" giveMoney");
   }

   @Override
   public void giveName() {
       try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println(name+" giveName");
   }
}

再定义一个检测方法执行的工具类,在任何方法执行前先调用 start 方法,执行之后调用 finsh 方法,就可以计算出该方法的运行时间。

public class TimeUtil {
   private static ThreadLocal<Long> tl = new ThreadLocal<>();

   public static void start() {
       tl.set(System.currentTimeMillis());
   }
   
   public static void finish(String methodName) {
       long finishTime = System.currentTimeMillis();
       System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
   }
}

创建 StuInvocationHandler 类(中介类),实现 InvocationHandler 接口,这个类中持有一个被代理对象的实例 target,InvocationHandler 中有一个 invoke 方法,所有执行代理对象的方法都会被替换成执行 invoke 方法。

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

public class StuInvocationHandler<T> implements InvocationHandler {
   T target;
   public StuInvocationHandler(T target){
       this.target = target;
   }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       System.out.println("代理执行 "+method.getName()+" 方法");
       TimeUtil.start();
       Object result = method.invoke(target,args);
       TimeUtil.finish(method.getName());
       return result;
   }
}

运行测试

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTest {
   public static void main(String[] args){
       Person zhangsan = new Student("张三");
       InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
       Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
       stuProxy.giveMoney();
       stuProxy.giveName();
   }
}

运行结果

20210428171748.png

我们在 StuInvocationHandler 类中的 invoke 函数中添加了对代理方法运行时间的检测,再执行两个方法时,都会被执行

我们关注一下 InvocationHandler 中的 invoke 方法

20210429095326.png

InvocationHandler 的 invoke 接收三个参数

  • proxy      代理后的实例对象

  • method  对象被调用的方法

  • args         调用方法时候的参数

我的理解:动态代理是什么呢 ->动态代理就是创建了一个中介类,中介类中实现了 InvocationHandler  接口,实现了其中的 invoke 方法,这个方法中,就会通过反射的方式去调用委托类的方法,这个方法中还可以添加各种其他的功能。

接下来深入的理解动态代理的根本原理

我们注意到生成 代理对象的语句为

Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

加入断点看一下

java.lang.reflect.Proxy#newProxyInstance

20210429092153.png

  • loader :      一个 ClassLoader 对象,定义了代理类的 ClassLoder

  • interfaces:  一个 Interface 对象的数组,定义代理类实现的接口列表

  • h:                  一个 InvocationHandler 对象,表示当动态代理对象在调用方法时,会关联到哪一个 InvocationHandler 对象上

首先是利用 getProxyClass0 生成代理类 Proxy 的 Class 对象

java.lang.reflect.Proxy#getProxyClass0

20210429100328.png

java.lang.reflect.WeakCache#get

20210429100547.png

java.lang.reflect.WeakCache.Factory#get

20210429103502.png

java.lang.reflect.Proxy.ProxyClassFactory#apply

20210429103502.png

通过语句 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); 生成代理类

代码中添加语句 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 会产生一个$Proxy0.class 文件,可以将动态生成的代理类显示出来

生成的 $Proxy0.class 就是最终的代理类,他继承自 Proxy 并实现了 Person

20210429101246.png

com.sun.proxy.$Proxy0#giveName

20210429100928.png

我们在实现代理类中的 giveName 的方法时,会用反射去调用 StuInvocationHandler#invoke

个人感觉只看这一段代码也是蛮好理解的

import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class TestProxy {
   @Test
   public void testInvocationHandler() throws Exception {
       // 被代理的对象
       Map map = new HashMap();
       // JDK 本身只支持动态代理接口
       // 创建 proxy object,参数为 ClassLoader、要代理的接口Class array、实际处理方法调用的 InvocationHandler
       Map proxy = (Map) Proxy.newProxyInstance(TestProxy.class.getClassLoader(), new Class[]{Map.class}, new MyInvocationHandler(map));
       proxy.put("key", "value");
       proxy.get("key");
   }

   public static class MyInvocationHandler implements InvocationHandler {
       private Map map;

       public MyInvocationHandler(Map map) {
           this.map = map;
       }

       // 实际的方法调用都会变成调用 invoke 方法
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           System.out.println("method: " + method.getName() + " start");
           Object result = method.invoke(map, args);
           System.out.println("method: " + method.getName() + " finish");
           return result;
       }
   }
}

本文作者:Whippet

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/158531.html


文章来源: https://www.secpulse.com/archives/158531.html
如有侵权请联系:admin#unsafe.sh