责任链模式定义
责任链模式(Chain of Responsibilty Pattern)避免请求发送者与接收者耦合在一起,让多个对象处理器都有可能接收请求,将这些对象处理器连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。责任链模式是属于行为型模式。责任链模式的核心就是设计好一个请求链以及链结束的标识。 下面先看一个责任链的demo。
责任链模式的demo
日志框架中,日志按照级别分为,控制台日志(DEBUG), 文件日志(INFO)以及错误日志 (ERROR)。每个级别的日志需要在其本级和下级打印,例如:ERROR级别的日志可以在控制台和日志文件中输出。这个需求我们就可以选用责任链模式来实现。
类图
首先我们画好一个类图
如上图,日志抽象类AbstractLogger主要是定义一些公共的方法逻辑,定义了每个子类需要实现的抽象方法,以及每个子类的下一个处理器。ConsoleLogger,FileLogger和ErrorLogger则是三个具体的处理器。其级别分别对应DEBUG,INFO和ERROR级别,每个具体处理器都实现了write
方法。接着我们来看看上面类图的具体代码实现。首先来看看AbstractLogger。
代码实现
代码语言:javascript复制public abstract class AbstractLogger {
protected static int DEBUG = 1;
protected static int INFO = 2;
protected static int ERROR = 3;
protected int level;
protected AbstractLogger nextLogger;
//设置下一个处理器的方法
public void setNextLogger(AbstractLogger nextLogger) {
this.nextLogger = nextLogger;
}
//公共的输出日志的方法
public void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextLogger != null) {
nextLogger.logMessage(level, message);
}
}
//抽象方法,每个具体的处理器都要重写
protected abstract void write(String message);
}
然后就是三个具体的处理器,篇幅限制,在此只列举一个ConsoleLogger类,其他的两个处理器类也类似。
代码语言:javascript复制public class ConsoleLogger extends AbstractLogger {
public ConsoleLogger() {
//设置本处理器处理的日志级别
this.level = DEBUG;
//设置下一个处理器
setNextLogger(new FileLogger());
}
@Override
protected void write(String message) {
System.out.println("**********Console logger:" message);
}
}
最后看看测试类ChainPatternDemo
代码语言:javascript复制public class ChainPatternDemo {
public static void main(String[] args) {
AbstractLogger consoleLogger = new ConsoleLogger();
consoleLogger.logMessage(3, "错误信息");
System.out.println();
consoleLogger.logMessage(2, "文件信息");
System.out.println();
consoleLogger.logMessage(1, "控制台信息");
}
}
运行结果如下:
通过lambda来实现
上面是通过常规的方式来实现的责任链模式,那么我们能不能通过lambda来实现类,通过lambda来实现的话,我们只需要定义一个函数接口,然后通过lambda来构造具体的实现类。代码如下:
代码语言:javascript复制@FunctionalInterface
public interface LambdaLogger {
int DEBUG = 1;
int INFO = 2;
int ERROR = 3;
/**
* 函数式接口中唯一的抽象方法
*
* @param message
*/
void message(String message,int level);
default LambdaLogger appendNext(LambdaLogger nextLogger) {
return (msg,level)->{
//前面logger处理完才用当前logger处理
message(msg, level);
nextLogger.message(msg, level);
};
}
static LambdaLogger logMessage(int level, Consumer<String> writeMessage){
//temLevel是日志处理器的级别,level是要打印的日志级别
return (message, temLevel) -> {
if (temLevel >= level) {
writeMessage.accept(message);
}
};
}
//调试日志
static LambdaLogger consoleLogMessage(int level1) {
return logMessage(level1, (message) -> System.out.println("**********Console logger:" message));
}
//ERROR日志
static LambdaLogger errorLogMessage(int level) {
return logMessage(level, message -> System.out.println("***********Error logger: " message));
}
//INFO日志
static LambdaLogger fileLogger(int level) {
return logMessage(level, message -> System.out.println("*******File Logger: " message));
}
static void main(String[] args) {
LambdaLogger logger = consoleLogMessage(1).appendNext(fileLogger(2)).appendNext(errorLogMessage(3));
//consoleLogger
logger.message("控制台信息", 1);
logger.message("文件信息", 2);
logger.message("错误信息", 3);
}
}
说完了责任链demo,接下来我们来总结下责任链模式的优缺点。
优缺点
优点
1.降低耦合度,它将请求的发送者和接收者解耦,请求只管发送,不管谁来处理。
1.简化了对象,使得对象不需要知道链的结构2.增强给对象指派的灵活性,通过改变链内的成员或者调动他们的次序,允许动态地新增或删除责任3.增加新的请求处理类很方便
缺点
4.不能保证请求一定被接收5.系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用
应用场景
6.有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。7.在不明确指定接收者的情况下,向多个对象中的一个提交一个请求8.可动态指定一组对象处理请求
小结
前面介绍了责任链模式的定义,优缺点,应用场景,并且实现了一个责任链的demo。
那么在实际的开发中,我们有没有碰到责任链模式呢?
答案是有的,请看下面。
Servlet中运用Filter
不管是用SpringMVC还是用SpringBoot,我们都绕不开过滤器Filter,同时,我们也会自定义一些过滤器,例如:
权限过滤器,字符过滤器。
不管是何种过滤器,我们都需要实现过滤器接口Filter,并且重写doFilter方法
代码语言:javascript复制public interface Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
}
如下:我们自定义了一个字符过滤器XssFilter,用来过滤字符,防止XSS注入。我们也是重写了doFilter
方法,对黑名单中的字符进行过滤,对不在黑名单中的请求直接转给下一个过滤器。
@Component
@WebFilter(urlPatterns = "/*",filterName = "XssFilter")
public class XssFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//在黑名单中
if(allUrl(url)){
//走xxs过滤
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
(HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}else{
//走原先方法
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
通过debug模式调试我们看到如下调用栈。如下图所示:
红框中标记的内容是Tomcat容器设置的责任链,从Engine到Context再到Wrapper都是通过责任链的方式来调用的。它们都是继承了ValueBase抽象类,实现了Value接口。
不过此处我们要重点关注下与我们过滤器XssFilter息息相关的ApplicationFilterChain类,这是一个过滤器的调用链的类。在该类中定义了一个ApplicationFilterConfig数组来保存所有需要调用的过滤器Filters。
代码语言:javascript复制public final class ApplicationFilterChain implements FilterChain {
/**
* Filters.
*/
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
}
定义了一个初始大小为0的ApplicationFilterConfig数组,那么ApplicationFilterConfig是个啥呢?
代码语言:javascript复制/**
* Implementation of a <code>javax.servlet.FilterConfig</code> useful in
* managing the filter instances instantiated when a web application
* is first started.
*
* @author Craig R. McClanahan
*/
public final class ApplicationFilterConfig implements FilterConfig, Serializable {
/**
* The application Filter we are configured for.
*/
private transient Filter filter = null;
/**
* The <code>FilterDef</code> that defines our associated Filter.
*/
private final FilterDef filterDef;
}
ApplicationFilterConfig类是在web应用第一次启动的时候管理Filter的实例化的,其内部定义了一个Filter类型的全局变量。那么ApplicationFilterConfig数组的大小又是在啥时候被重新设置的呢?答案是在ApplicationFilterChain调用addFilter
方法的时候,那么我们来看看addFilter
方法
/**
* The int which gives the current number of filters in the chain.
* 当前链中过滤器的数量,默认是0
*/
private int n = 0;
/**
* Add a filter to the set of filters that will be executed in this chain.
* 将一个过滤器添加到过滤器链中
* @param filterConfig The FilterConfig for the servlet to be executed
*/
void addFilter(ApplicationFilterConfig filterConfig) {
// Prevent the same filter being added multiple times
for(ApplicationFilterConfig filter:filters)
if(filter==filterConfig)
return;
if (n == filters.length) {
ApplicationFilterConfig[] newFilters =
new ApplicationFilterConfig[n INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters;
}
filters[n ] = filterConfig;
}
如上代码注释:n
是用来记录当前链中过滤器的数量,默认为0,ApplicationFilterConfig对象数组的长度也等于0,所以应用第一次启动时if (n == filters.length)
条件满足,添加完一个过滤器之后将n加1。那么addFilter
方法是在什么时候调用的呢?答案就是在ApplicationFilterFactory类的createFilterChain
方法中。
public final class ApplicationFilterFactory {
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
....... 省略部分代码
for (int i = 0; i < filterMaps.length; i ) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
...... 省略部分代码
}
}
从名称来看ApplicationFilterFactory是一个工厂类,那么ApplicationFilterFactory类的createFilterChain
方法又是何时候调用的呢?这就需要回到最顶端的StandardWrapperValve类的invoke
方法。
final class StandardWrapperValve extends ValveBase {
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Create the filter chain for this request
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
......
filterChain.doFilter(request.getRequest(), response.getResponse());
}
}
最后我们就来看看 ApplicationFilterChain的doFilter
方法。其内部的核心逻辑在internalDoFilter
方法中。
/**
* The int which is used to maintain the current position
* in the filter chain.
*/
private int pos = 0;
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos ];
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
}
servlet.service(request, response);
}
pos 变量用来标记filterChain执行的当期位置,然后调用filter.doFilter(request, response, this)
传递this(ApplicationFilterChain)进行链路传递,直至pos>n
的时候(类似于击鼓传话中的鼓声停止),即所有的拦截器都执行完毕。
总结
应用责任链模式主要有如下几个步骤
1.设计一条链条和抽象处理方法 如Servlet的ApplicationFilterChain和Filter2.将具体处理器初始化到链条中,并做抽象方法的具体实现,如自定义过滤器XssFilter3.具体处理器之间的引用和处理条件的判断4.设计链条结束标识,如通过pos游标。实际的应用中我们可以将责任链模式应用到流程审批(例如:请假的审批)的过程中,因为每个审批人都可以看成一个具体的处理器,上一个审批人审批完成之后需要将请求转给下一个审批人,直到审批完成。同时需要注意的时候,如果请求链过长, 则可能会非常的影响系统性能,也要防止循环调用的情况。
参考
什么是责任链设计模式? 责任链模式[1]
References
[1]
责任链模式: https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html