责任链模式定义
责任链模式(Chain of Responsibilty Pattern)避免请求发送者与接收者耦合在一起,让多个对象处理器都有可能接收请求,将这些对象处理器连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。责任链模式是属于行为型模式。责任链模式的核心就是设计好一个请求链以及链结束的标识。
下面先看一个责任链的demo。
责任链模式的demo
日志框架中,日志按照级别分为,控制台日志(DEBUG), 文件日志(INFO)以及错误日志 (ERROR)。每个级别的日志需要在其本级和下级打印,例如:ERROR级别的日志可以在控制台和日志文件中输出。这个需求我们就可以选用责任链模式来实现。
类图
首先我们画好一个类图
如上图,日志抽象类AbstractLogger主要是定义一些公共的方法逻辑,定义了每个子类需要实现的抽象方法,以及每个子类的下一个处理器。ConsoleLogger,FileLogger和ErrorLogger则是三个具体的处理器。其级别分别对应DEBUG,INFO和ERROR级别,每个具体处理器都实现了write方法。
接着我们来看看上面类图的具体代码实现。首先来看看AbstractLogger。
代码实现
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类,其他的两个处理器类也类似。
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
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来构造具体的实现类。代码如下:
@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 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.降低耦合度,它将请求的发送者和接收者解耦,请求只管发送,不管谁来处理。
2. 简化了对象,使得对象不需要知道链的结构
3. 增强给对象指派的灵活性,通过改变链内的成员或者调动他们的次序,允许动态地新增或删除责任
4. 增加新的请求处理类很方便
缺点
1.不能保证请求一定被接收
2.系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用
应用场景
有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
2.在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
3.可动态指定一组对象处理请求
小结
前面介绍了责任链模式的定义,优缺点,应用场景,并且实现了一个责任链的demo。那么在实际的开发中,我们有没有碰到责任链模式呢?答案是有的,请看下面。
Servlet中运用Filter
不管是用SpringMVC还是用SpringBoot,我们都绕不开过滤器Filter,同时,我们也会自定义一些过滤器,例如:权限过滤器,字符过滤器。不管是何种过滤器,我们都需要实现过滤器接口Filter,并且重写doFilter方法
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。
public final class ApplicationFilterChain implements FilterChain { /** * Filters. */ private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; }
定义了一个初始大小为0的ApplicationFilterConfig数组,那么ApplicationFilterConfig是个啥呢?
/** * Implementation of a javax.servlet.FilterConfig 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 FilterDef 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和Filter
2.将具体处理器初始化到链条中,并做抽象方法的具体实现,如自定义过滤器XssFilter
3.具体处理器之间的引用和处理条件的判断
4.设计链条结束标识,如通过pos游标。
实际的应用中我们可以将责任链模式应用到流程审批(例如:请假的审批)的过程中,因为每个审批人都可以看成一个具体的处理器,上一个审批人审批完成之后需要将请求转给下一个审批人,直到审批完成。
同时需要注意的时候,如果请求链过长, 则可能会非常的影响系统性能,也要防止循环调用的情况。