JAVA安全基础之动态代理

2021-10-14
2 min read

代理对象,是我们为了在原有对象的基础上实现功能的增强、修改等而为原有对象新建立的对象,比如,我们有一个对象A,现在我们试图在对象A进行操作之前添加时间记录,那么我们就可以给对象A创建一个代理对象Aplus,它的方法调用和A没有任何区别,只是会输出时间记录。那么实现了时间记录的对象Aplus就是代理对象,原对象A就是目标对象。

一、静态代理

想要实现一个简单的代理,我们最直观的想法是直接为目标类建立一个代理类,它接受一个目标类对象,并且可以实现增强操作。

比如:

image.png

但是这样很显然有一个问题,那就是有多少个目标类就需要创建多少个对应的代理类,并且要实现同样的接口,非常不方便。

二、动态代理

如果抛开实现细节,简单地来说的话,动态代理实现了根据目标对象接口来新建对象,并且可以将新建对象与(代理对象)与目标联系起来。

JDK提供了java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类配合完成动态代理功能。

我们知道,接口是无法新建对象的(它没有构造方法),所以我们需要使用java.lang.reflect.Proxy类得到代理对象:

Proxy有个静态方法:getProxyClass(ClassLoader, interfaces),只要你给它传入类加载器和一组接口,它就给你返回代理Class对象

由此该Class对象是从接口对象实现的,作为接口无法新建实例的替代

image.png

所以Proxy.getProxyClass()这个方法的本质就是:以Class造Class

在造出原有接口的构造方法以实现了可以新接口对象之后,还要考虑新的对象如何去实现方法的调用,这里的调用肯定是对目标对象的方法调用,所以有了InvocationHandler类,它可以通过反射去调用方法。

在$Proxy0 类中,是存在一个字段handler的,它就是InvocationHandler对象,每一次对

$Proxy0对象(也就是代理对象)调用某个方法的时候,它就会被导向handler.invoke方法。

image.png

以下是一个demo,实现了一个简单的动态代理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package org.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.lang.reflect.InvocationHandler;
/**
 * Hello world!
 *
 */
//interface RMIServerTest extends Remote {String Pring() throws RemoteException; }
//class RMIServerTestServer implements RMIServerTest {
//    @Override
//    public String Pring() throws RemoteException {
//        System.out.println("hello");
//        return "this is a test";
//    }
//}
public class App 
{
    public static void main( String[] args ) throws Exception
    {
        //参数1是随意的类加载器,参数2是代理对象的接口。hellProxyClazz 是org.example.$Proxy0类对象。
       Class helloProxyClazz= Proxy.getProxyClass(hello.class.getClassLoader(),HELLOINT.class);
        //得到构造器与创建实例,从这两步可以看出,$Proxy0的Class是复制了接口的Class的,但是存在一个可以接受InvocationHandler类的构造方法,并且可以强行转化为目标对象接口。
        //从InvocationHandler 可见,$Proxy0类对象中实现了接口的所有方法,实现的方式,是通过调用InvocationHandler对象的invoke方法进行的。
        Constructor cons = helloProxyClazz.getConstructor(InvocationHandler.class);
        HELLOINT hello1 = (HELLOINT) cons.newInstance(new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method,Object[] args) throws Exception{
                hello hello2 = new hello();
                return method.invoke(hello2,args);
            }
        });
        //

       System.out.println("end!");
       hello1.start();
    }

}

interface HELLOINT{
    public void start();
}

class hello implements HELLOINT{
    public void start(){
        System.out.println("start");
    }
}


当然,从这里我们可以看出,目标对象位置是在InvocationHandler中的,每次调用都需要新建对象,这不优雅也不实用。

所以我们通常定义一个方法来获得代理对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

private static Object getProxy(final Object target) throws Exception {
        Object proxy = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),/*类加载器*/
                target.getClass().getInterfaces(),/*让代理对象和目标对象实现相同接口*/
                new InvocationHandler(){/*代理对象的方法最终都会被JVM导向它的invoke方法*/
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(method.getName() + "方法开始执行...");
                        Object result = method.invoke(target, args);
                        System.out.println(result);
                        System.out.println(method.getName() + "方法执行结束...");
                        return result;
                    }
                }
        );
        return proxy;
    }

在该方法中,我们使用了newProxyInstance静态方法来获得代理对象,并且在InvocationHandler中覆写了invoke方法,通过传入不同的target,我们就可以实现对任意接口下的对象完成动态地、即时地代理。

image.png

三、动态代理总结与思考

动态代理需要目标类实现某个接口,然后代理类才能根据该接口实现代理,那么自然而然地,代理对象与目标对象形成了多态,也由此可知,代理对象在某些时候是无法实现目标对象的所有功能的,只有接口的定义,才是代理的实现范畴。

在代理对象的强制转型的过程中,必然只能得到一个接口的信息,如果一个类实现了多个接口,那么一次转型只能代理该接口的所有功能。

image.png

参考资料:

https://www.zhihu.com/question/20794107/answer/658139129

https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984

Avatar
Oulaa 黑白之色,无间之守