代理模式
定义:为其他对象提供一种代理以控制这个对象的访问
参考文章:
https://zhuanlan.zhihu.com/p/72644638
例子:Github
我们需要访问Github,在国内需要挂proxy
走向是:
电脑->proxy节点->Github服务器
代理模式可以在不修改被代理对象的基础上,通过拓展代理类,进行一些功能的附加与增强,值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类
首先是静态代理
以租房为例,我们一般用租房软件、找中介或者找房东。这里的中介就是代理者。
首先定义一个提供了租房方法的接口
public interface IRentHouse {
void rentHouse();
}
定义一个类去实现租房方法(相当于房东)
public class RentHouse implements IRentHouse {
@Override
public void rentHouse() {
System.out.println("租了一间房子。。。");
}
}
再定义一个代理类(中介)
房东把租房这件事交给中介去办,同时,中介还能负责其他业务(维修/管理)
public class IntermediaryProxy implements IRentHouse {
private IRentHouse rentHouse;
public IntermediaryProxy(IRentHouse irentHouse){
rentHouse = irentHouse;
}
@Override
public void rentHouse() {
System.out.println("交中介费");
rentHouse.rentHouse();
System.out.println("中介负责维修管理");
}
}
public class Main {
public static void main(String[] args){
//定义租房
IRentHouse rentHouse = new RentHouse();
//定义中介
IRentHouse intermediary = new IntermediaryProxy(rentHouse);
//中介租房
intermediary.rentHouse();
}
}
这里主要运用了多态的特性
在main中我们写了
IRentHouse rentHouse = new RentHouse();
IRentHouse intermediary = new IntermediaryProxy(rentHouse);
intermediary.rentHouse();
写法如下:
接口类型 名字 = new 接口实现类的构造方法;
分析一下作用:
IRentHouse是我们定义的接口,指定一个变量名rentHouse
表示它是接口类型的变量
但是右边可以看到,new RentHouse()
调用了RentHouse的构造方法,而RentHouse是IRentHouse接口的实现类
右边构造方法返回的是一个RentHouse类型的对象
而左边预期接收的是一个IRentHouse接口类型的对象
为什么不报错呢?左右两边的对象类型完全都不一样了
看起来就好比:
我要一杯7分糖的冷饮,然后服务器给我一杯咕嘟冒泡的开水
这就涉及到java多态的特性了
右边的类实际是接口的实现类,和接口是有关系的
左边的接口类型是可以接收其实现类对象的
具体细节可以从这个网站学习
https://how2j.cn
这样创建的变量
能够访问到实现类里面实现的接口抽象方法
其他方法无法访问
也就是rentHouse变量能够访问RentHouse类里面写好的那个rentHouse方法
来到下一步
//定义中介
IRentHouse intermediary = new IntermediaryProxy(rentHouse);
创建一个代理类(中介)
看一下具体怎么实现的
public class IntermediaryProxy implements IRentHouse {
private IRentHouse rentHouse;
public IntermediaryProxy(IRentHouse irentHouse){
rentHouse = irentHouse;
}
@Override
public void rentHouse() {
System.out.println("交中介费");
rentHouse.rentHouse();
System.out.println("中介负责维修管理");
}
}
同样实现了IRentHouse
声明了一个属性,暂时没赋值
private IRentHouse rentHouse;
定义了一个构造方法,接收外部传入的IRentHouse类型参数
然后将该参数赋值给rentHouse
然后在重写的rentHouse方法中,既调用了自己的部分(收中介费&管理),又调用了传入对象自身的rentHouse(租房)
静态方法有什么缺点?
如果方法接口不止一个方法
那么被代理类就要逐个实现方法
并且代理类也需要逐个实现方法
这样就会很麻烦
可不可以自己在代理类写一个方法类似
public void execute(){
method.invoke(user,args)
}
执行这个方法就能调用被代理的接口实现类的实现方法
但是这个方法咋获取?method从哪拿到接口实现类的实现方法?
这个我们自行开发是很困难的,但是JDK提供了官方的实现
出现了动态代理
代理类不用挨个实现,而是接收一切方法然后完成反射调用
首先第一步:
实现InvocationHandler接口,这是JDK内置的动态代理接口
public class IntermediaryProxy implements InvocationHandler {
private Object obj;
public IntermediaryProxy(Object object){
obj = object;
}
/**
* 调用被代理的方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(this.obj, args);
return result;
}
}
对比静态代理
public class IntermediaryProxy implements IRentHouse {
private IRentHouse rentHouse;
public IntermediaryProxy(IRentHouse irentHouse){
rentHouse = irentHouse;
}
@Override
public void rentHouse() {
System.out.println("交中介费");
rentHouse.rentHouse();
System.out.println("中介负责维修管理");
}
}
动态代理接收任意Object,静态代理接收固定对象
动态代理不再逐个重写方法,而是写了一个任意方法的反射调用
动态代理的实现首先需要一个Proxy对象

可以看到,Proxy类的构造方法是private私有的,说明不能直接调构造方法创建对象
但是它提供了一个方法
Proxy.newProxyInstance()
通过这个方法即可获取到Proxy实例

这个方法需要三个参数
1.类加载器(需要该代理对象代理什么实现类)
2.需要实现的接口类(需要该代理对象代理什么接口)
3.动态代理的调用处理器(要做的事情)
为什么是这三个
仔细思考一下,这是一个代理类
从静态代理去看
1.需要知道接口类型
对应左边
IRentHouse rentHouse = new RentHouse();
2.需要知道实现类
对应右边
IRentHouse rentHouse = new RentHouse();
3.知道了代理的对象,要干啥
对应
IRentHouse intermediary = new IntermediaryProxy(rentHouse);
前两个很容易理解,稍微学过一点反射的都会
第三个咋实现呢?
这里JDK就已经提供了接口,我们只需要实现方法体即可
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class IntermediaryProxy implements InvocationHandler {
private Object obj;
public IntermediaryProxy(Object object){
obj = object;
}
/**
* 调用被代理的方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(this.obj, args);
return null;
}
}
如上,重写invoke方法即可,这就是调用处理器
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args){
//定义租房
IRentHouse rentHouse = new RentHouse();
//定义中介(创建调用处理器,处理rentHouse这个被代理的对象)
IntermediaryProxy intermediary = new IntermediaryProxy(rentHouse);
IRentHouse o = (IRentHouse) Proxy.newProxyInstance(rentHouse.getClass().getClassLoader(), rentHouse.getClass().getInterfaces(), intermediary);
o.rentHouse();
}
}
如上即可完成动态代理调用
在反序列化漏洞中有啥用
理想情况:
假如在B类的f方法触发了危险方法
且A的readObject方法接收一个对象,并且调用该类的f方法
此时传入B的实例化对象即可
但是现实情况是:
A的readObject方法接收一个对象(假设为另一个对象O),并且调用该类的abc方法
就是不让你调用f,气死你
假如,O类是一个动态代理,它的invoke里面调用了f方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(this.obj, args);
obj.f;
return null;
}
它对外接收一个obj对象
那么此时对O类传入B
由于是动态代理
外面不管调用什么,invoke方法都会被触发(jvm底层机制)
链子:
A::readObject(O)触发O.abc
O类在创建对象的构造方法时接收一个另外的对象
O o1 = new O(B)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(B, args);
B.f;
return null;
}
从而完成危险方法调用
评论(0)
暂无评论