所谓代理模式就是当对象A无法直接引用对象B时,引入对象C作为中间人,对象A调用对象C,对象C调用对象B,这个中间对象C就是代理。
代理模式
代理模式应用场景
- 隔离变化:当对象B发生变化是,只需要修改代理对象C, 对象A不受影响。
- 一些“横切”功能可以放到代理对象中实现,如安全认证,鉴权、日志打印等。
代理模式在软件开发中应用非常广泛,Srping AOP, gRPC远程调用都使用了代理模式。
java中的代理分为静态代理 和 动态代理。
静态代理
静态代理就是代理类是写死的,代理关系和代理类处理逻辑是“静态”的,无法在运行时更改
代码实现
- 先定义一个接口Movable,表示可以移动行驶的物体。
public interface Movable
{
void move() throws Exception;
}
- 小汽车是可以行驶的,所以Car可以实现Movable接口
public class Car implements Movable
{
@Override
public void move()
{
System.out.println("汽车行驶中…,模拟核心业务");
}
}
- 在main方法中执行代码
public static void main(String[] args) throws Exception
{
Movable car = new Car();
car.move();
}
控制台会输出
汽车行驶中…
这时候有新需求,希望在move方法前后分别记录汽车启动和停止的时间,该如何实现呢?
第一种方法:直接修改Car类的代码
在move方法的第一行和最后一行记录汽车开始和停止时间
public class Car implements Movable
{
@Override
public void move()
{
System.out.println("汽车启动:" + System.currentTimeMillis());
System.out.println("汽车行驶中…,模拟核心业务");
System.out.println("汽车停止:" + System.currentTimeMillis());
}
}
这样做有两个缺点:
一是违反了“对扩展开放,对修改封闭的”开闭原则,我们修改了类Car的move方法,无法保证这个修改不会影响既有功能,move方法相关的业务逻辑都需要重新测试。
二是“记录启动时间”不属于核心业务,应该从核心类Car中分离
第二种方法是静态代理模式
- 首先定义一个静态代理类CarProxy,CarProxy记录汽车的启动和停止时间,真正的业务调用委托给realMovable
public class CarProxy implements Movable {
private Movable realMovable;
public CarProxy(Movable realMovable){
this.realMovable = realMovable;
}
@Override
public void move() throws Exception {
//前置代理逻辑
System.out.println("汽车启动:" + System.currentTimeMillis());
//真实对象调用
realMovable.move();
//后置代理逻辑
System.out.println("汽车停止:" + System.currentTimeMillis());
}
}
- 执行代码
public static void main(String[] args) throws Exception {
Movable realCar=new Car(); //创建真实业务对象,真正执行业务操作
Movable proxyLogCar = new CarProxy(realCar); //创建代理对象,真实对象作为参数传入
proxyLogCar.move();
}
执行代码结果如下
汽车启动:1609428420257
汽车行驶中…,模拟核心业务
汽车停止:1609428420258
我们通过使用静态代理模式,在不修改Car类的情况下加入“记录汽车启动停止时间”的业务逻辑。
![](https://img.haomeiwen.com/i15976124/d9c13b06b9c3b7ef.png)
上面的例子中,Car和CarProxy要实现共同的接口Movable,意味着CarProxy只能代理实现了Movable接口的对象,不能代理其他接口。 而实际项目中的接口成百上千,为每个接口都创建一个代理类工作量太大,于是就有了动态代理。
动态代理
代理类不是预先写好的,而是在程序运行时动态创建。相比静态代理,动态代理更加灵活高效。
代码实现
JDK通过实现InvocationHandler接口定义代理逻辑,通过Proxy类来生成动态代理对象。
- 首先定义代理逻辑
public class LogHanlder implements InvocationHandler
{
private Object target;
//构造函数中传入要代理的对象
public LogHanlder(Object object)
{
this.target = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
//前置代理逻辑
System.out.println("汽车启动:" + System.currentTimeMillis());
//真实对象调用
method.invoke(target, args);
//后置代理逻辑
System.out.println("汽车停止:" + System.currentTimeMillis());
return null;
}
}
- 定义代理类
public static void main(String[] args) throws Exception
{
//要被代理的真实对象是car
Movable car = new Car();
//定义代理逻辑处理类,通过代理调用,会转发到这里
InvocationHandler handler = new LogHanlder(car);
//通过java反射机制,生成动态代理对象
Movable proxyCar = (Movable) Proxy.newProxyInstance(
car.getClass().getClassLoader(), //指定动态代理类的classloader
new Class[]{Movable.class}, //动态代理类要实现的接口
handler //代理逻辑处理类
);
proxyCar.move();
}
运行代码
汽车启动:1609429682889
汽车行驶中…,模拟核心业务
汽车停止:1609429682890
原理分析
这些类是怎么协同工作的呢?动态代理类在哪里呢?
我们在上述代码main函数中加入如下代码
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
重新运行程序,就能在java工程的 ./sun/proxy目录下看到 $Proxy0.class文件,反编译可以看到$Proxy0实现了Movable接口,在move方法中调用了InvocationHandler 实例的invoke方法,是不是和静态代理类长得很像呀??
public final class $Proxy0 extends Proxy implements Movable {
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void move() throws Exception {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (Exception | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
- java动态代理就是在程序运行时生成代理类的class文件
- JVM加载动态class文件
- 通过反射生成动态代理对象。
基于jdk实现动态代理,要求被代理类必须实现一个接口,这个条件大多数情况下都可以满足,因为“面向接口编程”要求每个业务类都要实现一个接口,但是个别场景下一些类可能没有实现任何接口,该如何实现动态代理呢?
这个时候就需要借助cglib, asm,javassist等第三方库直接操作字节码生成class文件。
总结
代理模式应用广泛,经常用于隔离变化,以及在代码中增加横切功能。本文介绍了java实现静态代理和动态代理的方法。动态代理的实现原理就是动态生成class文件,利用反射实例化代理对象。实现动态代理可以使用jdk自带的库,也可以借助于cglib, asm,javassist等第三方库。
网友评论