一、概念
代理模式Proxy,就是在访问原始的对象的时候,引入一定的简介手段,附加更多的功能,这样保证不影响原始代理核心功能的基础上可以增加额外功能(AOP思想)。
/**
* 定义一个政策接口
*/
public interface IPolicy {
/**
* 执行政策方法
*/
void executePolicy();
}
/**
* 实现政策接口,进行业务逻辑处理
*/
public class Policy implements IPolicy {
@Override
public void executePolicy() {
System.out.println("执行政策!");
}
}
根据需求,我们在政策执行之前需要进行校验,那么我们一般的做法就是继承Policy,重写父类方法
**
* 继承的方式,添加普通类型政策的校验逻辑
*/
public class CommonPolicy extends Policy {
private String policyId;
/*省略get,set方法*/
@Override
public void executePolicy() {
System.out.println("校验普通政策ID, 政策id = " + policyId);
System.out.println("执行政策!");
}
}
这样做其实是违背了“开闭原则”,虽然拓展,但对修改开放了,为了解决这个问题,我们可以采取代理模式,主要分为静态代理和动态代理
二、静态代理
代理者和原始对象继承或者遵从同一个接口,但是原始对象中值操作核心功能,而代理者则将附加功能写入,并在类内新建原始对象,然后调用其核心方法,这样就在核心功能继承上附加了额外功能,外部只需要调取代理。
/**
* 改进:与Policy一样遵从同一套接口,同时将Policy实现类传入,调用其方法
* 这样可以保证对原方法的修改关闭,而增加了拓展(切面增强功能,开闭原则)
* JDK代理方式
*/
public class SpecialPolicy implements IPolicy {
private String policyId;
private Policy policy;
/*get\set*/
@Override
public void executePolicy() {
System.out.println("校验特殊政策ID, 政策id = " + policyId);
policy.executePolicy();
}
}
当然,我们还可以使用继承
/**
* 改进:也可以使用继承来实现同样的功能(CgLib代理方式)
*/
public class SpecialPolicy2 extends Policy {
private String policyId;
private Policy policy;
/*get\set*/
@Override
public void executePolicy() {
System.out.println("校验特殊政策ID, 政策id = " + policyId);
policy.executePolicy();
}
}
但是这种方式还有个问题,如果政策量很大,那岂不是我的每个政策都要加一个“执行政策!”
这个业务逻辑?!
有没有什么办法能自动生成代理对象,这就是动态代理思想
三、动态代理
本质就是利用Java反射技术将零散的目标类各个属性重组,并改写其中的方法,返回一个与原始类属性一模一样的代理类。
public class SomePolicy implements IPolicy {
private String policyId;
/*get/set*/
public void executePolicy() {
System.out.println("政策校验,policyId = " + policyId);
}
}
/*使用JDK反射包中的代理接口*/
public class ProxyHandler implements InvocationHandler {
//目标:被代理类
private Object target;
public Object getProxy(Object target) {
this.target = target;
/**
* 将目标对象传入,反射获取目标对象的类加载器、所有接口
* this=当前对象,该对象实现了InvocationHandler接口所以有invoke方法,通过invoke方法可以调用被代理对象的方法
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/**
* 代理类执行的方法
* @param proxy “被”代理的对象(原始对象)
* @param method 需要执行的方法
* @param args 方法执行的参数
* @return
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
//AOP-before:业务增强
result = method.invoke(target, args);
//AOP-after:业务增强
System.out.println("执行政策!");
return result;
}
}
我们测试一下:
@Test
public void testProxyHandler() {
ProxyHandler proxyHandler = new ProxyHandler();
SomePolicy somePolicy = new SomePolicy();
somePolicy.setPolicyId(UUID.randomUUID().toString());
//必须强转为接口对象,即后面somePolicy实现类的父对象
IPolicy iPolicy = (IPolicy) proxyHandler.getProxy(somePolicy);
iPolicy.executePolicy();
}
//打印结果
政策校验,policyId = 38c90df8-4dde-44ba-bacb-6990fc34bb05
执行政策!
这里要注意:
1、当调用“executePolicy()
”方法的时候,其实调用的不是真正的方法,而是将方法名和参数传入到了InvocationHandler
的invoke()
方法,进行代理增强
2、进行对象强转的时候,必须转为接口对象,而不能是实现对象,否则会抛出不能同级转换的异常
java.lang.ClassCastException: com.sun.proxy.$Proxy6 cannot be cast to com.flight.carryprice.rjmodel.impl.SomePolicy
三、代理模式应用
1、业务系统非功能性需求开发
业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。
2、缓存应用
比如,针对获取用户个人信息的需求,我们可以开发两个接口,一个支持缓存,一个支持实时查询。如果是基于Spring 框架来开发的话,那就可以在 AOP 切面中完成接口缓存的功能。在应用启动的时候,我们从配置文件中加载需要支持缓存的接口,以及相应的缓存策略(比如过期时间)等。当请求到来的时候,我们在 AOP 切面中拦截请求,如果请求中带有支持缓存的字段,我们便从缓存(内存缓存或者 Redis 缓存等)中获取数据直接返回。