责任链模式
行为型设计模式,与结构型设计模式不同的是,其主旨在于优化流程。
在实际开发中,如果遇到多个对象可以处理一个请求的时候,就可以考虑责任链模式。
责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
标准的责任链模式,个人总结下来有如下几个特点:
- 链上的每个对象都有机会处理请求
- 链上的每个对象都持有下一个要处理对象的引用
- 链上的某个对象无法处理当前请求,那么它会把相同的请求传给下一个对象
用一张图表示以下使用了责任链模式之后的架构:
也就是说,责任链模式满足了请求发送者与请求处理者之间的松耦合,抽象非核心的部分,以链式调用的方式对请求对象进行处理。
场景示例:
在单独部署的算法系统中,一个请求过来,想在调用算法前执行一些操作;如校验权限,日志记录,缓存请求参数等,该请求必须按照指定顺序,把上述操作完成后才可以执行算法逻辑。
不使用责任链模式
首先定义一个操作清单列表对象:
package NotChainResponsibility;
/**
* 算法调用前的操作清单对象
*
*/
public class PreparationList {
/**
* 是否鉴权
*/
private boolean authorization;
/**
* 是否记录日志
*/
private boolean byLog;
/**
* 是否缓存请求参数
*/
private boolean toRedies;
public boolean isAuthorization() {
return authorization;
}
public void setAuthorization(boolean authorization) {
this.authorization = authorization;
}
public boolean isByLog() {
return byLog;
}
public void setByLog(boolean byLog) {
this.byLog = byLog;
}
public boolean isToRedies() {
return toRedies;
}
public void setToRedies(boolean toRedies) {
this.toRedies = toRedies;
}
}
package NotChainResponsibility;
/**
* 算法预测类;按要求把清单中的操作都完成后再调用算法
*
*/
public class Forecast {
public void doForecast(PreparationList preparationList) {
if (preparationList.isAuthorization()) {
System.out.println("鉴权");
}
if (preparationList.isByLog()) {
System.out.println("保存日志");
}
if (preparationList.isToRedies()) {
System.out.println("参数放入缓存");
}
System.out.println("调用算法");
}
}
这个例子实现了我们的需求,但是不够优雅,我们的主流程是调用算法预测,但是把要准备做的事情这些动作耦合在算法预测中,这样有两个问题:
- PreparationList中增加一件事情的时候,比如发送请求通知监控程序计数,或者想去掉缓存参数时,必须修改doForecast方法进行适配
- 当这些事情的顺序需要发生变化的时候,必须修改Forecast方法,比如先记录日志,那么代码必须互换位置
最糟糕的写法,只是为了满足功能罢了,违背开闭原则,即当我们扩展功能的时候需要去修改主流程,无法做到对修改关闭、对扩展开放。
使用责任链模式
创建责任链
package finalChainResponsibility;
import finalChainResponsibility.filter.PrepareFilter;
import java.util.ArrayList;
import java.util.List;
/**
* 责任链,是用于串起所有的责任对象
*/
public class FilterChain implements PrepareFilter {
private int pos = 0;
private Forecast forecast;
private List<PrepareFilter> prepareFilterList;
public FilterChain(Forecast forecast) {
this.forecast = forecast;
}
public void addFilter(PrepareFilter prepareFilter) {
if (prepareFilterList == null) {
prepareFilterList = new ArrayList<PrepareFilter>();
}
prepareFilterList.add(prepareFilter);
}
@Override
public void doFilter(PreparationList thingList, FilterChain filterChain) {
// 所有过滤器执行完毕
if (pos == prepareFilterList.size()) {
forecast.doForecast();
} else {
prepareFilterList.get(pos++).doFilter(thingList, filterChain);
}
}
}
package finalChainResponsibility;
/**
* 算法调用前的操作清单对象
*
*/
public class PreparationList {
/**
* 是否鉴权
*/
private boolean authorization;
/**
* 是否记录日志
*/
private boolean byLog;
/**
* 是否缓存请求参数
*/
private boolean toRedies;
public boolean isAuthorization() {
return authorization;
}
public void setAuthorization(boolean authorization) {
this.authorization = authorization;
}
public boolean isByLog() {
return byLog;
}
public void setByLog(boolean byLog) {
this.byLog = byLog;
}
public boolean isToRedies() {
return toRedies;
}
public void setToRedies(boolean toRedies) {
this.toRedies = toRedies;
}
}
package finalChainResponsibility;
public class Forecast {
public void doForecast() {
System.out.println("算法调用逻辑");
}
}
package finalChainResponsibility.filter;
import finalChainResponsibility.PreparationList;
import finalChainResponsibility.FilterChain;
public interface PrepareFilter {
void doFilter(PreparationList preparationList, FilterChain filterChain);
}
package finalChainResponsibility.filter;
import finalChainResponsibility.PreparationList;
import finalChainResponsibility.FilterChain;
/**
* 权限校验
*/
public class AuthorizationFilter implements PrepareFilter {
@Override
public void doFilter(PreparationList preparationList, FilterChain filterChain) {
if (preparationList.isAuthorization()) {
System.out.println("鉴权");
}
filterChain.doFilter(preparationList, filterChain);
}
}
package finalChainResponsibility.filter;
import finalChainResponsibility.PreparationList;
import finalChainResponsibility.FilterChain;
/**
* 日志记录
*/
public class ByLogFilter implements PrepareFilter {
@Override
public void doFilter(PreparationList preparationList, FilterChain filterChain) {
if (preparationList.isByLog()) {
System.out.println("日志记录");
}
filterChain.doFilter(preparationList, filterChain);
}
}
package finalChainResponsibility.filter;
import finalChainResponsibility.PreparationList;
import finalChainResponsibility.FilterChain;
/**
* 缓存请求
*/
public class ToRediesFilter implements PrepareFilter {
@Override
public void doFilter(PreparationList preparationList, FilterChain filterChain) {
if (preparationList.isToRedies()) {
System.out.println("缓存请求");
}
filterChain.doFilter(preparationList, filterChain);
}
}
测试伪代码
package finalChainResponsibility;
import finalChainResponsibility.filter.AuthorizationFilter;
import finalChainResponsibility.filter.ByLogFilter;
import finalChainResponsibility.filter.PrepareFilter;
import finalChainResponsibility.filter.ToRediesFilter;
import javax.security.*;
public class Test {
public static void main(String[] args) {
PreparationList preparationList = new PreparationList();
preparationList.setAuthorization(true);
preparationList.setByLog(true);
preparationList.setToRedies(true);
Forecast forecast = new Forecast();
PrepareFilter authorizationFilter = new AuthorizationFilter();
PrepareFilter byLogFilter = new ByLogFilter();
PrepareFilter toRediesFilter = new ToRediesFilter();
FilterChain chain = new FilterChain(forecast);
chain.addFilter(authorizationFilter);
chain.addFilter(byLogFilter);
chain.addFilter(toRediesFilter);
chain.doFilter(preparationList, chain);
}
}
在上述的demo中可以将FilterChain做成一个Spring Bean,所有的Filter具体实现也都是Spring Bean,入住到PrepareFilterList就好。如此以来增加or修改操作不用再改动调用端代码。
例如:
<bean id="filterChain" class="xxx.xxx.xxx.FilterChain">
<property name="PrepareFilterList">
<list>
<ref bean="authorizationFilter" />
<ref bean="byLogFilter" />
<ref bean="toRediesFilter" />
</list>
</property>
</bean>
如此一来新增、减少Filter,或者修改Filter顺序,只需要修改.xml文件即可,不仅核心逻辑符合开闭原则,调用方也符合开闭原则。
责任链模式实际应用
Servlet的Filter
责任链模式最典型的就是Servlet中的Filter。上文的升级版责任链是基于Servlet的应用场景修改而来。
Servlet中使用责任链模式的优点:
- 使过滤器是可插拔的,我们不需要某个过滤器时,直接删掉不会影响程序的运行。
- 一个过滤器不依赖于另一个资源
- 维护少,容易维护
Spring Boot的拦截器
spring boot中使用的拦截器也是责任链模式的一种实现。其最底层依旧是使用了Servelt Filter。
通过FilterRegistrationBean注册入自定义的拦截器。如母线系统用户登录加入登录用户id放入session中缓存的拦截器:
/**
* Copyright (C), 2015‐2021, 北京清能互联科技有限公司 Author: wangfeng Date: 2021/4/13 10:47 History:
* <author> <time> <version> <desc>
*/
package com.tsintergy.buslf.web.base.common.interceptor;
import com.tsieframework.cloud.security.serviceapi.system.api.SecurityService;
import com.tsieframework.cloud.security.serviceapi.system.bean.TokenBean;
import com.tsieframework.cloud.security.serviceapi.system.pojo.TsieMenuVO;
import com.tsieframework.cloud.security.serviceapi.system.pojo.TsieUserVO;
import com.tsieframework.cloud.security.web.common.security.TokenManager;
import com.tsieframework.core.base.dao.hibernate.DBQueryParam;
import com.tsieframework.core.base.dao.hibernate.DBQueryParamBuilder;
import com.tsieframework.core.base.dao.hibernate.QueryOp;
import java.io.IOException;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.jasig.cas.client.validation.Assertion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Description: <br>
*
* @author wangfeng
* @create 2021/4/13
* @since 2.0.0
*/
public class LoginInitFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LoginInitFilter.class);
protected SecurityService securityService;
protected TokenManager tokenManager;
public LoginInitFilter(SecurityService securityService, TokenManager tokenManager) {
this.securityService = securityService;
this.tokenManager = tokenManager;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse res = (HttpServletResponse) servletResponse;
logger.info("Cas request URL:" + req.getRequestURI());
String username = getUserName(req);
//用户名在前置的过滤器中有校验,不存在为空的可能
logger.info("request username:" + username);
if(StringUtils.isBlank(username)) {
filterChain.doFilter(req, res);
}else {
initUserInfo(filterChain, req, res, username);
}
}
private void initUserInfo(FilterChain filterChain, HttpServletRequest req, HttpServletResponse res, String username) throws IOException, ServletException {
//获取请求携带的token
TokenBean bean = tokenManager.getTokenBeanFromCache(username);
//token不存在,或者token过期了,走后台登录方法
if (bean == null) {
DBQueryParam param = DBQueryParamBuilder.create().where(QueryOp.StringEqualTo, "username", username).queryDataOnly().build();
try {
//用户权限初始化
TsieUserVO user = securityService.queryTsieUserVo(param);
String token = tokenManager.setCookieToken(user);
List<TsieMenuVO> menuList = securityService.queryTsieMenuAllByUserId(user.getId(), null);
TokenBean tokenBean = TokenManager.createTokenBean(token, user, menuList);
tokenManager.addTokenBeanToCache(user.getUsername(), tokenBean);
} catch (Exception e) {
logger.error("单点登录,初始化用户权限及组织架构信息异常...");
e.printStackTrace();
}
filterChain.doFilter(req, res);
} else {
//设置session缓存
req.getSession().setAttribute(tokenManager.getLoginTokenKey(), bean.getToken());
filterChain.doFilter(req, res);
}
}
public String getUserName(HttpServletRequest request){
//获取cas传递过来的username
Object object = request.getSession().getAttribute("_const_cas_assertion_");
String username = null;
if (object != null) {
Assertion assertion = (Assertion) object;
username = assertion.getPrincipal().getName();
} else {
username = (String) request.getSession().getAttribute("edu.yale.its.tp.cas.client.filter.user");
}
return username;
}
@Override
public void destroy() {
}
}
通过FilterRegistrationBean包装成spring对象加入到spring上下文中:
@Bean
public FilterRegistrationBean<LoginInitFilter> casLoginInitFilter(){
final FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new LoginInitFilter(securityService, tokenManager));
// 设定匹配的路径
registration.addUrlPatterns("/*");
// 设定加载的顺序
registration.setOrder(99);
return registration;
}
在spring boot启动后 在某个时间点,会从FilterRegistrationBean中获取到拦截器,并根据设置的order进行排序,放到上下文中,随后通过FilterChain的doFilter方法依次执行逻辑。
注意:
与示例的责任链模式不通的Spring Boot中的Filter对象和FilterChain对象不是继承自一个对象。
spring boot中(即为servlet):
在Filter中有三个参数,包含了责任链。在FilterChain中只有两个参数,没有责任链。此处与demo中不同。在责任链模式中,最显著的特征是责任链的链状调用而实现类似循环的行为。
此处设计考虑可能是为了从环境变量中统一获取某个方法排过序的拦截器列表因此无需在责任链中传入责任链对象。追踪spring源码后发现却是如此,感兴趣的小伙伴可以追一下。
责任链的作用即为串起所有的流程。通过demo可知,所有的流程串起是通过filterChain.doFilter实现。在demo中filterChain与filter同源不必区分,在spring boot中请区分。
随便找一FilterChain实现类:例如ApplicationFilterChain
可见其doFilter方法中调用internalDoFilter随后从上下文中Filter filter = filterConfig.getFilter()
;获取到拦截器,自此调用拦截器的传入责任链方法filter.doFilter(request, response, this);
进入到对应的流程。
总结:
spring boot 启动某个节点1通过FilterRegistrationBean先放拦截器进去到环境变量中。
在某个节点2执行责任链FilterChain的doFilter()方法,方法中会从环境变量中获取排序第一的Filter,
随后调用Filter的doFilter()方法,并将责任链对象作为参数传递。(责任链对象内状态可能有变化例如计数+1以判断Filter是否都执行完,因此要对象要传递)。
在Filter的doFilter()方法的最后调用filterChain.doFilter(request, response);来让责任链传递到下一个Filter。方法中会从环境变量中获取排序第二的Filter...以此循环。
一直到所有的Filter都执行完,开始执行最后的逻辑。
spring boot的节点1和节点2涉及spring启动流程以及上下文初始化顺序,此处不再赘述。
满足大家好奇心:
如果自定义的Filter逻辑最后不写filterChain.doFilter(request, response);则后续的过滤器不会执行。
类似疑问:
了解到过滤器用到的设计模式后很好理解。