静态代理&动态代理
1. Java的代理模式
先说说什么是代理模式,要说代理模式,得从代理说起。举我们生活中一个非常熟悉的例子,租房大部分情况下都不是直接找房东,而是通过中介代理租房的操作。Java中的代理模式也是这个道理。
Java中的代理模式又分为静态代理与动态代理。
静态代理
静态代理是指在编译时期就已经确定了代理类的实现。代理类需要实现与目标对象相同的接口,并持有目标对象的引用,通过代理对象调用目标对象的方法。
依旧以租客找中介向房东租房为例子。想要用代码实现它,就必须有4个文件,一个是房源定义了实现租房的接口,二是房东实现了租房的接口,三是中介代理租房,四是租户来租房。
这里感谢Drun1baby师傅提供的代码:
Rent.java:这是一个接口,可以抽象的理解为房源,作为房源,它有一个方法rent()为租房
package StaticProxy;
// 租房的接口
public interface Rent {
public void rent();
}
- Host.java:实现Rent接口
package StaticProxy;
public class Host implements Rent {
public void rent(){
System.out.println("房东要出租房子");
}
}
Proxy.java:这是一个类,这个类是中介,也就是代理,他需要有房东的房源,然而我们通常不会继承房东,而会将房东作为一个私有的属性host,我们通过host.rent()来实现租房的方法。并且中介肯定海货收取中介费,也就是说代理是可以扩展自己的方法的,避免修改了原有的类。
package StaticProxy;
// 中介
public class Proxy {
private Host host;
public Proxy(){}
public Proxy(Host host){
this.host = host;
}
public void rent(){
host.rent();
contract();
fare();
}
// 看房
public void seeHouse(){
System.out.println("中介带你看房");
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
// 签租赁合同
public void contract(){
System.out.println("签租赁合同");
}
}
- Client.java:租客,实现找中介看房
package StaticProxy;
// 启动器
public class Client {
public static void main(String[] args) {
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
这样基本的看房便完成了,我们可以运行一下main方法

优点:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
缺点 :
- 一个真是类对应一个代理角色,代码量翻倍,开发效率降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
动态代理
动态代理是指在运行时动态生成代理类,而不需要提前定义实现类。它的实现主要依赖于Java的java.lang.reflect.Proxy和InvocationHandler。
前面所讲的静态代理中,每多一个房东就需要多一个中介,这显然不符合生活认知(对于租客来说,如果是用静态代理模式,每当想要换一个房东,那就必须要再换一个中介,在开发中,如果有多个中介代码量就更大了)
动态代理的出现就是为了解决上面静态代理的缺点。
动态代理的实现步骤
- 定义接口。
- 创建
InvocationHandler接口的实现类,用来处理方法调用。 - 使用
Proxy.newProxyInstance()生成动态代理对象。
实例代码:
首先是接口类UserService.java
package DynamicProxy;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
然后是实现接口的类UserServiceImpl.java
package DynamicProxy;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("更新了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
接着,是动态代理的实现类
package DynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
// 动态生成代理类实例
public Object getProxy(){
Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), userService.getClass().getInterfaces(), this);
return obj;
}
// 处理代理类实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method);
Object obj = method.invoke(userService, args);
return obj;
}
//业务自定义需求
public void log(Method method){
System.out.println("[Info] " + method.getName() + "方法被调用");
}
}
- 最后编写我们的 Client,也就是启动器
package DynamicProxy;
import DynamicProxy.UserServiceImpl;
public class Client {
public static void main(String[] args) {
// 真实角色
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 代理角色,不存在
UserProxyInvocationHandler userProxyInvocationHandler = new UserProxyInvocationHandler();
userProxyInvocationHandler.setUserService((UserService) userServiceImpl); // 设置要代理的对象
// 动态生成代理类
UserService proxy = (UserService) userProxyInvocationHandler.getProxy();
proxy.add();
proxy.delete();
proxy.update();
proxy.query();
}
}
我们先运行看看效果,再解释代码

代码的解释
我们从Client.java,也就是启动器来说
-
首先创建了一个
UserServiceImpl对象,实现了UserService接口。 -
然后创建了
UserProxyInvocationHandler并把真实对象传进去:setUserService(...)- 注意到这里使用了
(UserService)强制类型转换为接口类了,因为我多态代理其实是对接口进行代理,不同于静态代理是对实现类进行代理。
- 注意到这里使用了
-
再接着通过
getProxy()(内部调用Proxy.newProxyInstance(...))得到代理对象proxy(类型是UserService)。- 这里需要重点说一下getProxy方法,我们可以在代理类中看对应的实现代码
public Object getProxy(){ Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), userService.getClass().getInterfaces(), this); return obj; }注意到这里调用了Proxy类中的
newProxyInstance方法,方法中一共有三个参数。其中this.getClass().getClassLoader()是为了获取类的加载器。
userService.getClass().getInterfaces()则是获取实现类实现的所有接口。this则代指调用此方法的对象。最终是返回了生成的代理对象。 -
然后通过代理对象调用
add(),delete()等方法- 在代理对象中便会触发invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method); Object obj = method.invoke(userService, args); return obj; }其中参数中的
proxy便是指的代理对象,Method则是代指方法对象(这里便指的是add(),delete()等方法),Object[] args是方法的参数数组。在函数体中,调用了
log(method);这是业务自定义需求。public void log(Method method){ System.out.println("[Info] " + method.getName() + "方法被调用"); }接着调用method对象的invoke方法,传入的参数则是userService(注意:这里的userService其实是实现类UserServiceImpl的实例对象向上转型的,由于多态的特性,所以执行的其实是UserServiceImpl类的方法)
通过以上的代码最终的效果便是,通过动态生成的代理类调用了实现类的方法,并且允许自定义的扩展。
一句话总结:JDK 动态代理就是在运行时生成一个实现了目标接口的“替身”对象,把方法调用统一交给 InvocationHandler,由它在调用真实对象前后插入额外逻辑,从而实现横切关注点的统一管理。
2. 动态代理在反序列化中的作用
讲了一大堆开发里面的内容,那么这个动态代理可以怎么利用到反序列化里面呢?
我们前面提到过要利用反序列化漏洞,我们首先是需要一个入口类的。
这里我们假设存在一个可以被漏洞利用的类方法为B.f,比如Runtime.exec这种,我们将入口类定义为A,我们最理想的情况便是
A(Object) -> Object.f -> 传入B -> B.f
但是实际情况下这种情况比较少见。
回到实战情况。比如我们的入口类A存在O.abc这个方法,也就是A[O] -> O.abc;但是,重点来了,O是一个动态代理的类,调用O的abc方法其实会执行O的invoke方法,如果invoke方法中存在.f方法,便可以漏洞利用了
利用流程如下:
A[O] -> O.abc
O[O2] invoke -> O2.f // 此时将 B 去替换 O2
最后 ---->
O[B] invoke -> B.f // 达到漏洞利用效果
动态代理在反序列化当中的利用和 readObject 是异曲同工的。
readObject 方法在反序列化当中会被自动执行。
而 invoke 方法在动态代理当中会自动执行。
