✨这里是第七人格的博客✨小七,欢迎您的到来~✨
🍅系列专栏:设计模式🍅
✈️本篇内容: 代理模式✈️
🍱本篇收录完整代码地址:gitee.com/diqirenge/d… 🍱
楔子
如果你是JavaWeb开发者,那代理模式对你一定不陌生。为什么这么说呢?因为Spring框架里大量使用了代理模式,而JavaWeb开发,很大程度上就是面向Spring编程,但是虽然用到了代理模式,但是我们却不自知,甚至让我们自己实现一个简单的代理,我们也完成不了。
需求背景
我们有一个网关系统,在接收到外部请求的时候,需要对http请求进行鉴权解密,并且转为dubbo协议,然后调用下层业务系统,现在让你来设计一下这个系统。
分析设计
我们简单拆分一下需求,其实大的功能就2点:
1、在调用真实的方法前,我们需要先鉴权解密
2、http协议转dubbo协议
针对鉴权解密,你可能第一个想到的就是AOP,使用切面来完成,可以当然可以。但是为了加深我们对代理模式的理解,这里我们不使用第三方包,直接用Java来编写一层代理。
针对协议转换,像dubbo这种rpc框架,一般都会提供泛化调用的扩展点,我们只需要调用对应的方法即可,但是可以多考虑一点,比如后期要接入其他的rpc框架怎么办?这里我们可以做简单一点,结合上面的代理类,做一个代理工厂,方便外部调用。
(泛化调用是一种不依赖服务提供方客户端JAR包的RPC调用方式,它允许中间节点在没有接口信息的情况下传递调用信息。)
UML图
根据分析设计,我们可以先画一个简单的UML图,后面通过UML图编码
模块名称
flyweight
模块地址
模块描述
代理模式代码示例
代码实现
1、抽象一个泛化调用接口
/**
* 代理模式 - 抽象出来的接口
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/12/21
*/
public interface GenericService {
Object genericMethod(String methodName, Object[] args);
}
2、编写被代理的类
实现我们的泛化接口
/**
* 代理模式 - 被代理的类
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/12/21
*/
public class DubboServiceImpl implements GenericService {
@Override
public Object genericMethod(String methodName, Object[] args) {
// 在这里实现具体的业务逻辑
System.out.println("[" + methodName + "]调用 Dubbo 服务成功");
return null;
}
}
3、编写代理类
这里需要实现我们的代理逻辑
/**
* 代理模式 - 代理类
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/12/21
*/
public class GenericProxy implements GenericService {
private GenericService target;
public GenericProxy(GenericService target) {
this.target = target;
}
@Override
public Object genericMethod(String methodName, Object[] args) {
// 在这里实现代理逻辑,例如日志记录、性能监控、鉴权解密等
System.out.println("[" + methodName + "]进入到代理类中,开始执行代理逻辑...");
System.out.println("[" + methodName + "]代理逻辑:鉴权");
System.out.println("[" + methodName + "]代理逻辑:解密");
System.out.println("[" + methodName + "]代理逻辑执行完毕,开始执行目标方法...");
return target.genericMethod(methodName, args);
}
}
4、编写代理工厂
这个代理工厂只是一个示例,真正生产当中,一般都是通过url来的
/**
* 代理模式 - 代理工厂
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/12/21
*/
public class GenericProxyFactory {
GenericService genericService;
public GenericProxyFactory(String methodName) {
this.genericService = new DubboServiceImpl();
}
private final Map<String, GenericService> genericReferenceCache = new ConcurrentHashMap<>();
public GenericService newInstance(String methodName) {
System.out.println("[" + methodName + "]通过代理工厂创建代理");
return genericReferenceCache.computeIfAbsent(methodName, k -> new GenericProxy(genericService));
}
}
5、编写测试类
/**
* 代理模式
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2023/12/21
*/
public class ProxyTest {
@Test
public void test_proxy() {
String methodName = "querySomething";
new GenericProxyFactory(methodName)
.newInstance(methodName)
.genericMethod(methodName, new Object[]{});
}
}
4、测试结果
[querySomething]通过代理工厂创建代理
[querySomething]进入到代理类中,开始执行代理逻辑...
[querySomething]代理逻辑:鉴权
[querySomething]代理逻辑:解密
[querySomething]代理逻辑执行完毕,开始执行目标方法...
[querySomething]调用 Dubbo 服务成功
实现要点
代理模式的结构相对简单,它主要包含以下角色:
1、抽象主题(Subject)类,例子中为GenericService
2、真实主题(Real Subject)类,例子中为DubboServiceImpl
3、代理主题(Proxy)类,例子中为GenericProxy
根据创建代理的方式,代理模式可以被分为静态代理和动态代理。其中,静态代理是由程序员在编译期间就已经完成代理类的创建,而动态代理则是在程序运行期间通过Java反射机制动态生成代理对象。动态代理又可以细分为JDK提供的动态代理和CGLIB动态代理,JDK实现的代理和被代理者都需要实现接口,本质是面向接口编程,而CGLib则可以不需要接口。
总结
本文从网关需求出发,使用静态代理的方式,为读者实现了需求,并总结了实现要点。读者可以尝试使用动态代理改写代码,多写多练,才能把知识变成自己的。