java反序列化入门篇-03-动态代理
在这一篇中想记录一下学习代理的原理和作用,但在基础篇-01中已经用代码有一个大致的了解了,后面尽可能减少重复
java的代理模式
先简单来说一下什么是代理吧,代理的本质是不修改目标类原有代码的前提下,在目标方法执行前后增加额外逻辑(增强)。 有一个代理对象,持有目标对象引用;外部调用者只和代理对象打交道,由代理对象转发请求给真实目标。而在java的代理模式中存在静态代理和动态代理两种代理模式。
静态代理
我们来以租客找中介从房东手里租房为例子来去理解静态代理

想要在java中实现上面的逻辑,我们就需要4个文件,所代表的分别是租客,中介,房东和房源,其中房源应该是接口,其他三个为具体类。
有些师傅可能疑惑为什么房源应该是接口,对于房源来说我们要携带的方法是租房,但具体实现肯定是由房东和中介去实现的,所以房源中只需要放定义即可。虽然使用继承我们看上去也可以实现这种功能,但就不符合我们的编程思维了,继承应该表示子类是父类下的一个小分支,二者根源应该相同,且java使用的是单继承制,从使用和概念上都不太合适。而接口表达的是具备某种能力,我们也可以实现多个接口,更适合我们的利用。
Rent.java
1 2 3 4
| public interface Rent { public void rent(); }
|
Host.java
1 2 3 4 5 6
| public class Host implements Rent { public void rent(){ System.out.println("房东要出租房子"); } }
|
Proxy.java
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Proxy { private Host host; public Proxy(){} public Proxy(Host host){ this.host = host; } public void rent(){ host.rent(); } }
|
Client.java
1 2 3 4 5 6 7
| public class Client { public static void main(String[] args) { Host host = new Host(); Proxy proxy = new Proxy(host); proxy.rent(); } }
|
返回
在上面的文件中我们Rent.java定义为接口,也就是我们上面的房源;而Host.java,Proxy.java,Client.java分别对应房东,中介和租客。但中介的职责显然不可能和房东完全一样,他还要收中介费呢,谁白干活呢。此时只需要在Proxy.java内修改即可。
改进Proxy.java
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
| package src.JdkProxy.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("签租赁合同"); } }
|
返回
优点:
- 职责清晰,解耦
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 。
- 公共业务拓展时无需修改真实角色,可以集中处理
缺点:
- 每一个目标类,都要单独手写一个代理类。代码数量庞大。
- 代理逻辑复用差
那就有人在想了,有没有什么办法可以保留静态代理的优点但又抛弃其缺点,于是动态代理就诞生了
动态代理
我们原本在静态代理中是写死了一个类一个代理,但在动态代理的概念中,我们的代理并不专门为某一个类服务而是动态定义的。那这个时候我们应该代理什么呢,代理的是每个类都要实现的接口,不得不佩服前人的思维。
UserService.java
1 2 3 4 5 6
| public interface UserService { public void add(); public void delete(); public void update(); public void query(); }
|
UserServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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("查询了一个用户"); } }
|
UserProxyInvocationHandler.java
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
| 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.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import src.JdkProxy.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(); } }
|
总结篇中也有通过代码中的动态代理进行讲述,这里就不再详细讲述了
动态代理的意义
有了动态代理,我们就不需要给每一位房东都配备一个中介,而是哪位房东需要中介我们就为哪位房东服务,运行时由 JVM 动态生成代理字节码,内存中创建代理 Class,不需要提前写代理.java文件。大大减少了我们的代码量。同时我们对于真实类的改动频率可以极大被减少,公共方法只需要在代理类进行添加或修改就好。
并且一个代理还可以实现多个接口,动态代理其实远不止眼前这点知识,但我们学反序列化到这足以,讲多容易偏题,后面会单开一篇来写一下。
动态代理在java反序列化中的作用
动态代理最妙的地方就在于invoke 方法在动态代理当中会自动执行。
我们假设一下如果存在一个能够利用漏洞的类B.f,我们将入口类定为A。最为理想的情况是A[O],存在O.f,那么此时我们只需传入B赋值给O,即可调用B.f,当然这只是我们说的最为理想的情况,现实中几乎不可能遇见。
比较可能出现的情况是A[O],存在O.abc。如果O是一个代理类的话,且O的invoke方法中存在.f,那么此时我们就有用武之地了。给大家详细列一下整个调用逻辑
1 2 3 4
| A[O] -> O.abc O[M] invoke -> M.f ---传入B---- O[B] invoke -> B.f
|
动态代理的出现为反序列化找寻触发链提供了新方案和新思路,让其不必完全拘泥于同名函数的调用,把触发点放在 readObject 或者 invoke 两个位置。且JDK原生代理类大多实现Serialize,对于我们的反序列化研究提供了更多便利。