1.责任链模式
首先简单介绍一下责任链模式。
- 定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。将所有处理者形成一条链,在链中决定哪个对象能够处理请求,并返回结果,不能处理则继续向下传递请求。
- 优点:
- 将请求和处理分开,请求者不需要知道是谁处理的,处理者可以不用知道请求的全貌。
- 缺点:
- 性能问题,每个请求从链头遍历到链尾,如果链比较长则性能低下。
- 调试问题,属于递归调用,调试不方便。
- 在这里不对责任链模式做过多的介绍,简单说明,然后阐述在项目中的应用以及代码的实现。
2.实际业务场景
在我公司的项目中,有一些需要过期处理的东西,比如,订单未支付过期处理,发红包未领取自动退还,代金券未支付过期处理等。实现方式都是采用我之前所写的一篇文章,基于Redis实现订单倒计时自动关闭——Java。在存入redis的时候,会通过key来区分是什么业务,比如订单的key为AA开头,红包的key为BB开头,代金券的key为CC开头。那么,如果监听到redis中有过期的键值对的话,程序会得到键值对的key,通过key来判断属于哪部分业务,然后进行相应的处理。传统做法为,将所有的处理业务都写在一个类,或者一个方法中,通过多个if判断执行不同的逻辑,比如,如果是AA开头,那么执行订单的过期处理逻辑等等。
这样的做法并没有问题,我们讨论的只是一种更好的处理方式。
传统的做法会使处理逻辑耦合在一起,变得比较臃肿,复杂,难以维护。而大部分设计模式都是为了增加程序的可维护性以及可扩展性,或者说解耦。
在这个场景中,应用了责任链模式,相当于我把每一个处理逻辑都看作是一个独立的处理者,然后将他们形成一个处理链,这样当一个请求进来之后,通过遍历这个处理链来处理请求。在遍历链时,通过判断,如果能处理该请求,则处理并返回,不在继续向下遍历,如果不能处理则继续向下遍历,交个下一个处理者。
3.具体代码实现
通过上面的描述,我们知道,所有的处理者都是独立的,并且每个处理者应该都具备相同的行为----处理过期逻辑,首先我通过一个抽象类,定义出抽象的处理者。
public abstract class AbstractRedisExpireHandle {
//key的前缀,用于区分是什么业务
private String prefix;
//构造,子类必须实现
public AbstractRedisExpireHandle(){}
/**
* 操作前缀属性的公开方法
* @return
*/
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
//抽象的,所有具体处理者应该实现的处理逻辑
abstract void expireHandle(String redisKey);
}
接下来看具体的处理者,所有的具体处理者都要继承抽象处理者,具体处理者之间是完成独立的,解耦的。
/**
* 商品订单过期
*/
@Component
public class GoodsOrderExpireHandle extends AbstractRedisExpireHandle {
//设置该业务的前缀
private static final String PREFIX = "dd";
public GoodsOrderExpireHandle(){
setPrefix(PREFIX);
}
@Reference(version = "1.0.0")
private TaskService taskService;
//实现具体处理逻辑
@Override
void expireHandle(String redisKey) {
taskService.goodsOrderExpireHandle(redisKey);
}
}
/**
* 红包过期
*/
@Component
public class RedPacketExpireHandle extends AbstractRedisExpireHandle {
//设置业务前缀
private static final String PREFIX = "kb";
@Reference(version = "1.0.0")
private TaskService taskService;
public RedPacketExpireHandle(){
setPrefix(PREFIX);
}
//实现具体处理逻辑
@Override
void expireHandle(String redisKey) {
taskService.redPacketExpireHandle(redisKey);
}
}
所有的具体处理者定义完后,需要将他们组成一个处理链,并且进行工作,接下来看如何将所有的相关具体处理者组成一个处理链,因为项目是基于spring的,所以这里使用了spring提供的几个相关接口,并且在spring容器启动的时候完成处理链的创建。
@Component
public class ExecuteHandle implements ApplicationContextAware,InitializingBean {
//spring容器
private ApplicationContext context;
//具体处理者的集合
private List<AbstractRedisExpireHandle> expireHandles = new ArrayList<>();
//该方法会在容器启动的时候被调用
@Override
public void afterPropertiesSet() throws Exception {
//从容器中找到所有继承了抽象处理者的类,并加入到集合中,从而形成处理链
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,AbstractRedisExpireHandle.class);
for(int i=0;i<beanNames.length;i++){
expireHandles.add((AbstractRedisExpireHandle)context.getBean(beanNames[i]));
}
}
//该方法会将spring的当前容器传递进来
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
//遍历处理链,通过前缀来判断,能否处理逻辑,如果不能则继续遍历
public void handle(String redisKey){
if(expireHandles.size() > 0){
for(AbstractRedisExpireHandle abstractRedisExpireHandle : expireHandles){
if(redisKey.startsWith(abstractRedisExpireHandle.getPrefix())){
abstractRedisExpireHandle.expireHandle(redisKey);
}
}
}
}
}
上述代码比较简单,该类实现了spring的两个接口,一个是ApplicationContextAware和InitializingBean。
ApplicationContextAware为容器感知接口,实现这个接口需要实现setApplicationContext方法,spring会将容器当做参数传递到这个方法中,我们就可以使用当前容器了。
InitializingBean接口为初始化Bean的接口,实现这个接口需要实现afterPropertiesSet方法,该方法会在spring实例化这个类的时候被调用,可以做一些初始化工作,我就是在这个方法中将所有的具体处理者形成处理链的。
最后,通过遍历整个处理链,来处理相关的逻辑,结合之前所写的文章,使用该处理链处理请求的代码也十分简单,只需要调用上面的handle方法即可。
@Component
public class RedisExpireListener implements MessageListener {
@Autowired
private ExecuteHandle executeHandle;
@Override
public void onMessage(Message message, byte[] bytes) {
byte[] body = message.getBody();
String redisKey = new String(body);
executeHandle.handle(redisKey);
}
}
4.总结
这样做之后,我们将所有具体处理者都进行了解耦,互相之间毫无关系。那么后续如果在有新的处理逻辑需要加入进来的话,只需要继承抽象处理者,实现相关的处理逻辑,具体处理者就可以进行工作了,无需修改其他任何代码。
这种方式个人觉得还是比较好的,当然肯定会有更好的处理方式,只不过目前我还没有发掘到,大家如果有其他的好的建议可以一起讨论。