一、什么是责任链模式
该模式可以让多个具体处理者组成请求处理链,通过请求处理链依次对请求进行处理或者将其传递给下一个请求处理者。具体处理者需实现相同的处理者接口、相同的处理方法,实现了发送类和处理对象的解耦。
二、责任链模式结构
二、使用场景+案例分析
1、场景1
(1)场景介绍
在元数据管理项目中具有对表元数据具有逻辑权限管控的功能,先有以需求需要对库下的表进行整库授权,整库授权需要进行需要处理流程:
- 对授权的源、库、项目等信息进行校验,判断是否存在
- 记录授权的源、库、项目等授权信息
- 对库下的表进行授权操作
(2)以责任链的方式处理
类结构的设计如下图所示:
- 责任链接口
public interface IHandler {
void handle(DbGrantRightParam dbGrantRightParam);
}
- 责任链接口实现1
public class DbGrantRightHandler implements IHandler{
private final DbGrantRightHelperImpl dbGrantRightHelperImpl;
public DbGrantRightHandler (DbGrantRightHelperImpl dbGrantRightHelperImpl) {
this.dbGrantRightHelperImpl = dbGrantRightHelperImpl;
}
@Override
public void handle(DbGrantRightParam dbGrantRightParam) {
//业务处理
}
}
- 责任链接接口实现2
public class TableGrantRightHandler implements IHandler{
@Override
public void handle(DbGrantRightParam dbGrantRightParam) {
//业务处理
}
}
- 定义对象链式处理接口
public interface IHandlerChain {
void handle(DbGrantRightParam dbGrantRightParam);
}
- 定义对象链式处理接口实现
public class DefaultHandlerChain implements IHandlerChain{
private final List<IHandler> handlerList;
public DefaultHandlerChain (List<IHandler> handlerList) {
this.handlerList = handlerList;
}
@Override
public void handle(DbGrantRightParam dbGrantRightParam) {
handlerList.forEach(handler -> handler.handle(dbGrantRightParam));
}
}
(3)代码扩展性介绍
整库授权会对库下的所有表进行授权操作,这是授权操作的性能受到表数量的影响,在代码的设计上分别实现了同步单线程授权和异步多线程授权,可以根据情况灵活选择,如果在不考虑授权操作即使生效的情况下还可以使用生产-消费的形式进行异步解耦处理等等。
提高代码扩展性的基本原则:面向接口编程,而不是面向具体的实现类编程。具体业务逻辑依赖的是接口,而不是实现类。面向接口编程可以在不改变接口的情况下切换实现类,也可以新增接口实现类。
代码结构设计:
定义了一个通用的业务异步和同步处理框架,可以通过实现IExcute接口来扩展业务的处理形式。
代码实现:
- IException接口
public interface IExecute {
void execute(Collection<Runnable> tasks);
}
- SynExecute类
@Component
@ConditionalOnProperty(name = "task.batch-execute.type", havingValue = "syn", matchIfMissing = true)
public class SynExecute implements IExecute{
@Override
public void execute(Collection<Runnable> tasks) {
for (Runnable task : tasks) {
task.run();
}
}
}
- AsynExecute 类
@Component
@ConditionalOnProperty(name = "task.batch-execute.type", havingValue = "asyn")
public class AsynExecute implements IExecute {
@Override
public void execute(Collection<Runnable> tasks) {
throw new UnsupportedOperationException();
}
}
实现类上使用了springboot的@ConditionalOnProperty注解,通过该注解业务上可以根据配置来加载该接口的具体的实现类,如果不加配置默认加载SynExecute类(实现默认类:matchIfMissing = true)。
(4)防止重复提交
需求:当具体执行者还未执行完,不允许再次重复提交。
方案:有序服务的部署是双活,因此选用了redis加锁方式
代码实现:
public class RepeatSubmitServer {
private final RedisUtil redisUtil;
private final DbGrantRedisLockConfig dbGrantRedisLockConfig;
public RepeatSubmitServer(RedisUtil redisUtil, DbGrantRedisLockConfig dbGrantRedisLockConfig) {
this.redisUtil = redisUtil;
this.dbGrantRedisLockConfig = dbGrantRedisLockConfig;
}
public void execute( String redisLockKey, Runnable runnable) {
String redisLockValue = String.valueOf(System.currentTimeMillis());
if (redisUtil.setExIfAbsent(redisLockKey, redisLockValue, dbGrantRedisLockConfig.getLockExpireTime(), dbGrantRedisLockConfig.getUnit())) {
log.info("-- 开始执行整库授权责任链 --");
try {
runnable.run();
log.info("-- 整库授权执行结束 --");
} finally {
log.info("-- 整库授权结束, 删除锁: {} --", redisLockKey);
redisUtil.deleteIfEq(redisLockKey, redisLockValue);
}
} else {
log.warn("请稍后重试!, {} 正在执行中", redisLockKey);
throw new RuntimeException(String.format("请稍后重试!, %s 正在执行中", redisLockKey));
}
}
}
使用redis的锁工具类实现:
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public boolean tryLock(String key, String UUID, Long timeout) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, UUID, timeout, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
public void unLock(String key, String UUID) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//删除锁
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), UUID);
}
}