09. 责任链模式
什么是责任链设计模式?
责任链设计模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求沿着处理者对象组成的链进行传递,直到有一个处理者对象能够处理该请求为止。这种模式的目的是解耦请求的发送者和接收者,使得多个对象都有机会处理请求,从而增强了系统的灵活性。
责任链模式通常包含以下几个角色:
- 请求者(Client):发起请求的对象。
- 抽象处理者(Handler):定义一个处理请求的接口,通常包含一个方法用于处理请求,以及一个指向下一个处理者的引用。
- 具体处理者(Concrete Handler):实现抽象处理者接口的具体类,负责处理它所负责的请求,并决定是否将请求传递给链中的下一个处理者。
- 链(Chain):包含多个处理者对象,负责将请求沿着链传递。
责任链模式的工作原理如下:
-
请求者创建一个请求并将其发送给链的起始处理者。
-
每个处理者对象检查请求是否由自己处理。
-
- 如果能够处理,则处理请求并结束责任链。
- 如果不能处理,则将请求传递给链中的下一个处理者。
-
这个过程一直持续,直到请求被处理或传递到链的末端。
责任链模式的优点包括:
- 增强了系统的灵活性和可扩展性,因为可以动态地添加或移除处理者。
- 降低了对象之间的耦合度,因为发送者和接收者不需要直接交互。
- 允许多个对象处理同一个请求,增加了处理请求的灵活性。
责任链模式的缺点包括:
- 请求的传递路径可能难以跟踪,尤其是在链很长或处理者逻辑复杂的情况下。
- 责任链可能会导致系统性能问题,因为请求需要在多个对象之间传递。
责任链模式在实际应用中非常广泛,例如在GUI编程中处理事件、在网络编程中处理请求、在工作流系统中处理任务等场景。
举个简单的需求:
假如我们有个登录的场景,在登录处理流程中,需要校验参数、填充参数、登录判断、登录日志记录。我们每步都是环环相扣,此时就可以使用责任链模式。
有几个重要角色:
- **Action:**责任链中的一个执行动作,主要定义具体执行动作,以及是否需要跳过。
- **ActionChain:**责任链,用于定义添加执行动作方法,以及调度整条链路动作执行。
- **ActionContext:**执行动作上下文,定义一个上下文对象,用来在链路执行过程中存储和传输数据。
将上面的步骤看做一个一个执行动作,建立对应的action,使用 chain 将多个 action 进行串联,同时我们可以定义一个context 上下文,用来在各个action之间传输数据。
除此之外,我们也可以通过配置中心,来定义哪些步骤需要执行,哪些可以跳过。
类图如下:
代码编写:
1、定义顶级接口
(1)定义责任链执行动作上下文抽象类,用于责任链上下文之间数据传输。
@Data
public abstract class ActionContext implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
/**
* 执行链名称,用于获取配置
*/
private String actionChainName;
/**
* 跳到结果处理
*/
private boolean isSkipToResult = false;
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
(2)定义责任链执行动作基类
public interface IAction<T extends ActionContext> {
/**
* 是否需要跳过
* @param context 上下文
* @return true/false
*/
default boolean isSkippered(T context) throws Exception{
if (context.isSkipToResult()) {
return true;
}
// 通过配置中心获取是否需要执行
List<String> config = ConfigServer.getConfig(context.getActionChainName());
if (config.contains(getName())) {
return false;
}
return true;
}
/**
* 执行
* @param context 上下文
*/
void execute(T context) throws Exception;
/**
* 获取执行动作名称,用于和配置中心进行匹配
* @return
*/
String getName();
}
(3)定义Action 执行链接口
public interface IActionChain<T extends ActionContext> {
/**
* 添加一个Action
* @param action 上下文
* @return action链
*/
IActionChain<T> appendAction(Class<? extends IAction<T>> action);
IActionChain<T> appendActions(List<Class<? extends IAction<T>>> actions);
IActionChain<T> appendAction(IAction<T> action);
/**
* 执行动作
* @param context 上下文
*/
void execute(T context) throws Exception;
}
2、实现接口,定义具体的执行
(1)登录上下文
/**
* 登录上下文
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class LoginActionContext extends ActionContext {
/**
* 失败日志
*/
private String failMsg;
private String userName;
private String password;
private String token;
private String ip;
private String device;
private Boolean isLoginFlag = true;
}
(2)定义责任链通用模版类
public class RouteActionChain<T extends ActionContext> implements IActionChain<T> {
private List<IAction<T>> actionChain = new ArrayList<IAction<T>>();
@Override
public IActionChain<T> appendAction(Class<? extends IAction<T>> action) {
actionChain.add(getActionInstance(action));
return this;
}
@Override
public IActionChain<T> appendActions(List<Class<? extends IAction<T>>> actions) {
if (CollectionUtils.isEmpty(actions)) {
return this;
}
for (Class<? extends IAction<T>> clazz : actions) {
actionChain.add(getActionInstance(clazz));
}
return this;
}
@Override
public IActionChain<T> appendAction(IAction<T> action) {
actionChain.add(action);
return this;
}
@Override
public void execute(T context) throws Exception {
for (IAction<T> action : actionChain) {
//如果跳过 就不需要继续执行,这里顺序不能改变
if (action.isSkippered(context)) {
continue;
}
action.execute(context);
}
}
public static <T extends ActionContext> IAction<T> getActionInstance(Class<? extends IAction<T>> clazz) {
Collection<? extends IAction<T>> s = BeanUtil.getBeans(clazz);
if (s != null && s.size() == 1) {
return s.iterator().next();
} else {
throw new RuntimeException("action is not found");
}
}
}
(3)定义执行动作
- 校验参数执行动作
@Slf4j
@Component
public class CheckParamAction implements IAction<LoginActionContext> {
@Override
public void execute(LoginActionContext context) {
// do something
log.info("CheckParamAction execute......");
// 使用断言,判断用户名不为空
try {
Assert.isTrue(!StringUtils.isEmpty(context.getUserName()), "用户名不能为空");
Assert.isTrue(!StringUtils.isEmpty(context.getPassword()), "密码不能为空");
} catch (Exception e) {
context.setIsLoginFlag(false);
context.setSkipToResult(true);
context.setFailMsg(e.getMessage());
}
}
@Override
public String getName() {
return "CheckParamAction";
}
}
填充参数执行动作
@Slf4j
@Component
public class FullParamAction implements IAction<LoginActionContext> {
@Autowired
private ConfigServer configServer;
@Override
public void execute(LoginActionContext context) throws Exception {
log.info("FullParamAction execute......");
// 使用断言,判断用户名不为空
try {
// do something
context.setIp("127.0.0.1");
context.setDevice("PC");
context.setToken("123456");
} catch (Exception e) {
context.setIsLoginFlag(false);
context.setSkipToResult(true);
context.setFailMsg(e.getMessage());
}
}
@Override
public String getName() {
return "FullParamAction";
}
}
- 登录判断执行动作
@Slf4j
@Component
public class LoginAction implements IAction<LoginActionContext> {
@Override
public void execute(LoginActionContext context) throws Exception {
// 模拟登录
log.info("LoginAction execute......");
try {
// do something
if(context.getUserName().equals(context.getPassword())) {
context.setIsLoginFlag(true);
} else {
context.setIsLoginFlag(false);
context.setFailMsg("用户名或密码输入错误");
}
} catch (Exception e) {
context.setIsLoginFlag(false);
context.setSkipToResult(true);
context.setFailMsg(e.getMessage());
}
}
@Override
public String getName() {
return "LoginAction";
}
}
- 登录日志记录执行动作
@Slf4j
@Component
public class LogAction implements IAction<LoginActionContext> {
@Override
public void execute(LoginActionContext context) throws Exception {
log.info("FullParamAction execute......");
// 使用断言,判断用户名不为空
try {
// do something
log.info("数据库插入登录日志:{}", JSONObject.toJSONString(context));
} catch (Exception e) {
context.setIsLoginFlag(false);
context.setSkipToResult(true);
context.setFailMsg(e.getMessage());
}
}
@Override
public String getName() {
return "LogAction";
}
}
(4)模拟配置中心
配置需要的执行动作,没有配置的自动跳过
@Component
@Data
public class ConfigServer {
private static Map<String, List<String>> configMap = new HashMap<>();
@PostConstruct
public void init()
{
ArrayList<String> configList = new ArrayList<>();
configList.add("CheckParamAction");
configList.add("FullParamAction");
configList.add("LogAction");
configList.add("LoginAction");
configMap.put("login", configList);
}
/**
* 获取配置列表
* @param actionChainName
* @return
*/
public static List<String> getConfig(String actionChainName) {
return configMap.getOrDefault(actionChainName, new ArrayList<>());
}
}
3、测试
定义测试接口
@Service
public class LoginServiceImpl implements LoginService {
@Override
public boolean login(String userName, String password) {
// 创建上下文
LoginActionContext loginActionContext = new LoginActionContext();
loginActionContext.setActionChainName("login");
loginActionContext.setUserName(userName);
loginActionContext.setPassword(password);
// 构建责任链
RouteActionChain<LoginActionContext> chain = new RouteActionChain<>();
chain.appendAction(CheckParamAction.class);
chain.appendAction(FullParamAction.class);
chain.appendAction(LoginAction.class);
chain.appendAction(LogAction.class);
try {
chain.execute(loginActionContext);
} catch (Exception e) {
throw new RuntimeException(e);
}
return loginActionContext.getIsLoginFlag();
}
}
(1)测试正常情况,传输正确的 username 和 password。
所有执行动作正常执行。
(2)测试异常情况, 传输错误的 username 和 password。
中间 LoginAction 执行失败,自动跳出责任链,后续执行动作未执行。
到此,一个简单的责任链设计模式的 demo 就已完成。
拓展点:
• 可以对接配置中心,动态定义不同业务逻辑中需要执行的动作。
• 可以将幂等性校验,添加到判断动作是否执行逻辑中。