java动态代理机制


0x00 前言

在之前讲RMI的笔记中,我都会涉及到代理这个概念,比如stub(存根)和skeleton(骨架),stub是远程对象在rmi client端的代理,而skeleton则是rmi server端的代理,skeleton用来处理stub发送来的请求,然后去调用客户端需要的请求方法,最终将方法执行结果返回给stub。这里其实涉及到的java的动态代理的机制,感觉不去了解这个机制总觉得很抽象,这篇文章特地来说说java的这个动态代理机制。

0x01 概念

代理模式是一种java的设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了,只需要在代理类上增加就可以了。

代理模式设计特征主要为一个接口以及实现这个接口的委托类和代理类,委托类实现基本的功能,即接口中的所有方法,一些额外要实现的功能都委托给代理类处理,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

java的代理机制又分为静态代理和动态代理,这里我们重点掌握java自带的jdk动态代理机制。

0x02 静态代理

我们先从相对简单的静态代理开始了解,以便更好地明白代理的概念,以及为啥需要动态代理。

先以经纪人为明星代理为例:

先定义一个接口,便于明星和经纪人来实现其中的唱歌、跳舞功能:

package com.Anchor;

public interface Star_impl {
    void sing(String song);
    String dance();
}

然后定义一个明星类,即委托类,实现这个接口的所有方法:

package com.Anchor;

public class Star implements Star_impl{
    private String name;

    public Star(String name) {
        this.name = name;
    }

    public void sing(String song){
        System.out.println(this.name + "正在唱" + song);
    }

    public String dance(){
        System.out.println(this.name + "正在跳舞。");
        return "感谢各位!";
    }

}

再定义一个经纪人类,即代理类,负责帮忙实现委托类不能实现的一些操作,比如准备话筒、舞台等。

package com.Anchor;

public class Broker implements Star_impl{
    private Star_impl target; //被代理对象

    public Broker(Star_impl target) {
        this.target = target;
    }
    @Override
    public void sing(String song){
        System.out.println("准备话筒"); // 添加自定义的委托逻辑
        target.sing(song); //调用委托类Star的基本方法
    }
    @Override
    public String dance(){
        System.out.println("准备舞台");
        return target.dance();
    }
}

main函数看看效果:

package com.Anchor;

public class Main {
    public static void start(Star_impl subject){
        subject.sing();
    }

    public static void main(String[] args) {
        Star_impl star = new Star("一个不知名歌手"); //获取委托类的实例对象
        String song = "一个不知名的歌曲";
        System.out.println("-----使用代理之前-----");
        start(star, song);
        System.out.println("-----使用静态代理之后-----");
        start(new Broker(star), song);
    }
}

结果

可以看到,我们能够在不修改Star类(即委托类)的源码的前提下,通过Broker类(即代理类)扩展Star类的功能,从而实现代理功能。在使用代理后,布置舞台、收钱的功能实现了,歌手可以放心唱歌了。

这样看来,静态代理很轻松就实现了代理功能,那为啥我们还需要引入动态代理?

试想一下,如果我们需要实现更多额外的功能,那可能会需要编写更多的代理类,这时当原来的那个接口的源码需要修改一些东西时,那委托类和这些代理类都要进行相应修改,这显然是不容易维护的。另外,我们要代理的类可能不止一个,当需要代理多个类时,每个委托类都要编写相应的代理类,这又会导致代理更多,更不容易维护。

java静态代理是对类进行操作的,我们需要一个个代理类去实现对委托类的更改操作,主要的缺点就是维护难的问题。针对这种情况,我们可以用动态代理来解决,通过程序运行时自动生成代理类。

0x03 动态代理

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。

静态代理与动态代理的区别主要在:

  • 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
  • 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中

学习反射时我们就涉及过java.lang.reflect这个包,关于java的动态代理,也是要用到这个包下的这两位:java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口,通过这它们并配合反射机制,就可以实现动态代理。

java.lang.reflect.InvocationHandler接口

InvocationHandler接口:负责提供调用代理操作。是由代理对象调用处理器实现的接口,定义了一个invoke()方法,每个代理对象都有一个关联的接口。

这里的invoke()是关键,当代理对象上调用方法时,该方法会被自动转发到InvocationHandler.invoke()方法来进行调用。所以我们可以通过重写invoke()方法达到修改委托类对应功能的目的。

public interface InvocationHandler {

    /**
     略
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke()需要传入的三个参数的含义:

Object proxy,当前代理对象。

Method method, 当前调用的方法的Method对象,比如代理对象调用sing方法,这里就会传入sing的Method对象。

Object[] args,当前调用的方法需要传入的所有参数,比如sing方法要传入song这个参数。

java.lang.reflect.Proxy类

Proxy类:负责动态构建代理类,提供四个静态方法来为一组接口动态生成的代理类并返回代理类的实例对象。

四个静态方法如下:

静态方法名 作用
getProxyClass(ClassLoader,Class<?>…) 获取指定类加载器和动态代理类对象。
newProxyInstance(ClassLoader,Class<?>[],InvocationHandler) 指定类加载器,一组接口,调用处理器。
isProxyClass(Class<?>) 判断获取的类是否为一个动态代理类。
getInvocationHandler(Object) 获取指定代理类实例查找与它相关联的调用处理器实例。

实现

具体的实现过程如下:

  1. 自定义一个调用处理器类,该类需要继承自java.lang.InvocationHandler接口,由它来实现invoke方法,执行代理函数;

  2. 使用java.lang.reflect.Proxy类指定一个ClassLoader,一组interface接口和一个InvocationHandler;

  3. 通过反射机制获得动态代理类的构造方法,其唯一参数类型是调用处理器接口类型;

  4. 调用java.lang.reflect.Proxy.newProxyInstance()方法,分别传入类加载器,被代理接口,调用处理器;创建动态代理实例对象。

  5. 通过代理对象调用目标方法.

我们还是以上面的经纪人和明星为例,其中,Star_impl接口和Star委托类不做任何修改。

我们先编写一个调用处理器类:

package com.Anchor;

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

public class BrokerHandler implements InvocationHandler {
    private Object target; //被代理的对象

    public BrokerHandler(Object target) {
        this.target = target;
    }
    // 实现 java.lang.reflect.InvocationHandler.invoke()方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        if(method.getName().equals("sing")){ //判断当前调用的哪个方法
            System.out.println("准备话筒"); // 添加自定义的委托逻辑
        }else if (method.getName().equals("dance")){
            System.out.println("准备舞台");
        }
        // 其他不需要代理的方法
        return method.invoke(target, args); //利用反射机制,调用委托类的方法
    }
}

修改main函数如下:

package com.Anchor;

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

public class Main {
    public static void start(Star_impl subject, String song){
        subject.sing(song);
        System.out.println(subject.dance());
    }

    public static void main(String[] args) {
        Star_impl star = new Star("一个不知名歌手"); //获取委托类的实例对象
        String song = "一个不知名的歌曲";
        System.out.println("-----使用代理之前-----");
        start(star, song);
        System.out.println("-----使用静态代理之后-----");
        start(new Broker(star), song);
        System.out.println("-----使用动态代理之后-----");
        ClassLoader classLoader = star.getClass().getClassLoader(); // 通过委托类的实例对象获取相应的类加载器
        Class[] interfaces = star.getClass().getInterfaces(); //获取所有的接口
        InvocationHandler invocationHandler = new BrokerHandler(star); //获取调用处理器
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); // 将jdk动态代理生成的类保存为.class文件,方便查看
        Star_impl broker = (Star_impl) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        start(broker, song);
    }
}

结果2

可以看到达到的效果和使用静态代理是差不多的。此外,可以看见我们生成的动态代理类的字节码文件,放置在程序根目录下的com.sun.proxy.$Proxy0.class文件中。

com.sun.proxy.$Proxy0.class

其源码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import com.Anchor.Star_impl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Star_impl {
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sing(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String dance() throws  {
        try {
            return (String)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.Anchor.Star_impl").getMethod("sing", Class.forName("java.lang.String"));
            m4 = Class.forName("com.Anchor.Star_impl").getMethod("dance");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

在这里生成的$Proxy0代理类中,我们可以清楚知道动态代理的实现过程。实际上我们在创建代理对象时,就是通过反射来获取这个类的构造方法,然后来创建的代理实例。

动态代理在很多框架中都用到了吗,比如spring,mybatis,以及我们在java安全中使用的ysoserial工具等,学习动态代理有助于看懂这些框架。

小结

关于java动态代理机制这块,网上的很多文章都是一笔带过,但是这一块内容又是java漏洞分析中不可缺少的一部分,在很多知识点中都会有所涉及,这篇文章就先系统了解java动态代理,为以后的学习做铺垫。

参考文章:

JAVA安全基础(三)– java动态代理机制


文章作者: anch0r
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 anch0r !
  目录