-

java 静态代理与动态代理学习

0x01 意义

在生产环境中,代理模式有许多实际的好处,能够提升系统的可维护性、扩展性和性能。当然这是 GPT 的回答,可能有点宽泛。举个例子吧,客户希望租一套房子,但由于没有时间和经验,不想自己去寻找和联系房主。中介公司提供服务,帮助客户找到符合要求的房子,安排看房、谈判租金、处理租赁合同等事务。房主通过中介公司租出房子,避免了自己直接接触大量客户的麻烦。

这里说的中介就相当于是 Java 里的代理,Java中的代理模式类似于上面的代理,我们也是为一个类(委托类)创建一个代理类,来代表它来对外提供功能。

0x02 静态代理

代理类在编译时就已经确定,代理类和目标类都需要实现一致的接口。代理类直接持有目标类的引用,通过接口调用目标类的方法。

现在我们通过静态代理来实现上面说的找房的例子,中介代理房主,拥有租房,签合同的权限。

先实现同一个接口,代理类和目标类都需要实现同一个接口。在这里代理类就是中介,目标类就是房主。

我们先定义一个租房接口

1
2
3
public interface RentService{
void rent();
}

然后考虑实现房主类和中介类,这里房主只要考虑租房就行了,而中介要考虑的事就很多了(沟槽的鸣式

房主类

1
2
3
4
5
6
public static class HouseOwner implements RentService{
@Override
public void rent() {
System.out.println("房东出租房子");
}
}

中介类

1
2
3
4
5
6
7
8
9
10
11
12
public static class HouseProxy implements RentService{
private HouseOwner houseOwner;
public HouseProxy(HouseOwner houseOwner){
this.houseOwner = houseOwner;
}
@Override
public void rent() {
System.out.println("中介帮房东出租房子");
houseOwner.rent();
System.out.println("中介签合同");
System.out.println("中介收取中介费");
}

测试

1
2
3
4
5
public static void main(String[] args) {
HouseOwner houseOwner = new HouseOwner();
HouseProxy houseProxy = new HouseProxy(houseOwner);
houseProxy.rent();
}

image-20240718101937524

0x03 动态代理

为什么有静态代理了还要用动态代理呢?动态代理相比于静态代理有什么优势呢?当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

  1. 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:

    • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大

    • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类

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

实现

首先代理类不用再继承目标类的所有接口了,统一继承 InvocationHandler 类,还要重写 invoke 方法

1
2
3
4
5
6
7
8
9
10
public static class HouseProxy implements InvocationHandler{
private HouseOwner houseOwner;
public HouseProxy(HouseOwner houseOwner){
this.houseOwner = houseOwner;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(houseOwner, args);
return null;
}
}

可以看到利用了反射技术,难怪可以在类的创建阶段开始代理,然后利用 Proxy.newProxyInstance 创建代理

1
2
3
4
5
RentService proxyInstance = (RentService) Proxy.newProxyInstance(
houseOwner.getClass().getClassLoader(),
houseOwner.getClass().getInterfaces(),
new HouseProxy(houseOwner)
);

调用代理方法

1
proxyInstance.rent();//

断点调试发现 proxyInstance.rent() 就会进入代理类 HouseProxy 的 invoke 方法,我们可以打印一下方法名

1
System.out.println(method.getName() + "方法被调用");

image-20240718111353831

可以发现代理类的实现确实抽象出来了,只要维护目标类就行了。

0x05 思考

那么动态代理和反序列化漏洞又能扯上什么关系呢?

我们都知道 URLDNS 利用的是 HashMap 调用了 hash 方法然后就会触发传入对象 key hash的 HashCode 方法,从而传入 URL 对象来触发 URL 的 hashCode 方法。假如 key 没有调用 hashCode 方法怎么办呢?比如调用的是 key.aaa 方法

那我们就可以找一个动态代理类,我们可以把动态代理类传给 key ,不过动态代理类执行的是什么方法都会执行 public Object invoke 函数,可能这个 invoke 函数实现了 hashCode 方法,并且这个动态代理类也接受一个类作为参数,invoke 调用了我们传给这个动态代理类的参数(这个参数就是我们传的类)的 hashCode 方法,那我们就可以把 URL 类传给 这个动态代理。