jdk动态代理介绍及invoke方法自动运行原因


什么是代理?
在生活中,当我们有访问Google的需求的时候,就需要通过代理服务器进行访问。而代码中的”代理”也与之相似,当我们访问某个对象的时候,我们也可以通过”代理”来对其进行访问。

代理模式

学习java的动态代理以前,要明白java的代理模式,下面是从知乎专栏找到的一张图。

java中的静态代理

以买房子为例我们可以去找中介,那么这里的中介就是代理者。
写个domo更方便理解一下:
先定义一个IUser接口:

public interface IUser {
    void show();
}

实现类:

public class UserImpl implements IUser{
    public UserImpl() {

    }
    @Override
    public void show() {
        System.out.println("高楼,中楼,别墅");
    }
}

代理类

import java.text.SimpleDateFormat;
import java.util.Date;

public class StaProxy implements IUser{

    IUser user;

    public StaProxy(IUser user){
        this.user=user;
    }

    @Override
    public void show() {
        this.show();


        Date date = new Date();
        SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd :hh:mm:ss");
        System.out.println("您在"+dateFormat.format(date)+"观看了房源,是否想进行购买?");

    }
}

main

public class ProxyTest {
    public static void main(String[] args) {
            IUser user = new UserImpl();
            IUser sp = new StaProxy(user);
            sp.show();

    }
}

运行结果:

这样我们就通过我们写的静态代理来在调用show方法展示的同时,还记录了时间,并且还进行了进一步的询问,但是使用静态代理有一个大大的弊端,就是当我们在接口中想增加新的方法时不仅我们的实现类里面要有大的改动,我还要改变我们的代理类,这是极不方便的,并且我们的重复代码也是要写很多。为了解决这种情况,就要学习Java的动态代理

java中的动态代理

先写一个demo在进行分析
Iuser接口

public interface IUser {
    void show();
}

实现类

public class UserImpl implements IUser{
    public UserImpl() {

    }
    @Override
    public void show() {
        System.out.println("高楼,中楼,别墅");
    }
}

处理器

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

public class UserInvocationHanlder implements InvocationHandler {

    IUser user;
    public UserInvocationHanlder(IUser user) {
        this.user=user;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        method.invoke(user,args);
        return null;
    }
}

main

import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {

        IUser user = new UserImpl();

        UserInvocationHandler han = new UserInvocationHandler(user);

        IUser iUser = (IUser)  Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(),han);
        iUser.show();

    }
}

运行结果


其实这里就是在我们调用我们的某个方法时候,会进行动态获取我们的方法名,在通过我们传入的实现类对象,来进行动态调用我们对象的方法,我们上面写的处理器其实就是发挥着”动态代理”的作用。(提到动态大家估计一定会猜到用到了反射,所以明白一些底层原理还是很重要的)

动态代理在反序列化攻击中的作用

为什么要讲动态代理这个机制呢?

  1. 动态代理类接收的参数都是Object
  2. 当我们创建的动态代理对象调用任意一个方法时,都会调用我们自定义的继承于InvocationHandler的类的invoke方法。这就为我们挖掘gadget时会多出一点路来。

jdk动态代理为何会自动运行invoke

我们首先来看一下,我们的 iUser 是一个什么样的对象:


可以看到我们的生成的代理对象iUser是属于com.sun.proxy.$Proxy0这个类,我们在代码中加上System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);这样一段代码,执行后,就会把我们的$Proxy0.class 生成出来:


可以直接看到反编译后的代码:


完整代码:

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

package com.sun.proxy;

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 IUser {
    private static Method m1;
    private static Method m3;
    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 show() throws  {
        try {
            super.h.invoke(this, m3, (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("IUser").getMethod("show");
            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());
        }
    }
}

我们大致知道 Proxy.newProxyInstance 生成的是个”什么东西”以后,我们去看下这个newProxyInstance方法来了解它是如何生成的。

newProxyInstance方法代码:

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    &#123;
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) &#123;
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        &#125;

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try &#123;
            if (sm != null) &#123;
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            &#125;

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) &#123;
                AccessController.doPrivileged(new PrivilegedAction<Void>() &#123;
                    public Void run() &#123;
                        cons.setAccessible(true);
                        return null;
                    &#125;
                &#125;);
            &#125;
            return cons.newInstance(new Object[]&#123;h&#125;);
        &#125; catch (IllegalAccessException|InstantiationException e) &#123;
            throw new InternalError(e.toString(), e);
        &#125; catch (InvocationTargetException e) &#123;
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) &#123;
                throw (RuntimeException) t;
            &#125; else &#123;
                throw new InternalError(t.toString(), t);
            &#125;
        &#125; catch (NoSuchMethodException e) &#123;
            throw new InternalError(e.toString(), e);
        &#125;
    &#125;

这个函数他有三个参数,第一个参数是类加载器,第二个参数是一个接口数组,第三个参数是InvocationHandler接口的子类,我们来看下这个newProxyInstance函数代码的第一个关键部分:

在上图的方法getProxyClass0 中 会创建出一个类Proxy0,这个就是我们前面的那个Proxy0.class对应的类。在我们调用getProxyClass0 时,它的第二个参数是我们传入的接口,他会将我们传入的借口进行遍历,并且将这些接口中定义的方法进行实现。这里实现的方法的代码可以在上文说过的那个class文件上看到:

我们再接着来看下这个newProxyInstance函数代码的第二个关键部分:


这里是来获取proxy0这个类的构造器,参数是InvocationHandler。接着他又将h变量赋值给了别的变量,这个h变量就是我们传进去的第三个参数,即一个InvocationHandler接口的子类。


newProxyInstance函数代码的第三个关键部分,也是最后的地方,通过反射的方式创建proxy0对象并返回,至此,newProxyInstance方法执行完毕

可以看一下Proxy0的构造方法的代码,实际上是调用了他父类就是proxy的构造方法:

了解完大概的过程以后,我们在看之前的问题invoke方法是怎么自动运行的?

当我们通过代理对象调用show方法时:

因为iUser是Proxy0的实例对象,所以调用的show方法就是Proxy0类中的show方法。此时就相当于是proxy0.show。我们去看一眼对应的代码:

此时便会进到show方法,去执行 super.h.invoke ,这里的super是父类即proxy类,这里的h就是我们父类中的变量h,即是我们传进去的那个InvocationHandler的子类。所以super.h.invoke实际上就是在调用我们自定义的InvocationHandler的子类 UserInvocationHanlder的invoke方法:

以上就是invoke方法会自动运行的原因。


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