责任链模式

  • 一、介绍
  • 二、实战日志记录系统
  • 三、Servlet 过滤器源码分析

一、介绍

责任链模式是一种行为设计模式,它允许你将请求沿着处理者链进行传递,直到有一个处理者能够处理该请求。在这个模式中,每个处理者都包含对下一个处理者的引用,当收到请求时,它可以选择自行处理请求或者将请求传递给下一个处理者。

Java 中的责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过给多个对象处理请求的机会,从而避免了请求的发送者与接收者之间的耦合关系。该模式将接收对象的对象连接成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

责任链模式主要包含以下几个角色:

  1. 抽象处理者(Handler): 定义一个处理请求的接口,并且包含一个指向下一个处理者的引用。
  2. 具体处理者(ConcreteHandler): 实现抽象处理者的接口,并在需要时处理请求或将请求传递给下一个处理者。
  3. 客户端(Client): 创建请求,并将其传递给链上的第一个具体处理者。

优点:

  • 降低了请求发送者与接收者之间的耦合度。
  • 可以动态地添加或修改处理者。
  • 符合开放-封闭原则,可以在不修改现有代码的情况下扩展功能。

缺点:

  • 如果处理者的数量过多,可能会影响性能。
  • 调试可能会比较麻烦,因为请求可能会在链中传递多次。

二、实战日志记录系统

需求:开发一个日志记录系统,它需要处理三种类型的日志:错误日志、警告日志和信息日志。对于不同类型的日志,我们需要采取不同的行动,例如将错误日志记录到数据库、将警告日志发送电子邮件通知管理员,而对于信息日志只需要简单地打印到控制台即可。

// 抽象处理者interface LogHandler {void setNextHandler(LogHandler nextHandler);void handleLog(Log log);}// 具体处理者 - 错误日志处理者class ErrorLogHandler implements LogHandler {private LogHandler nextHandler;@Overridepublic void setNextHandler(LogHandler nextHandler) {this.nextHandler = nextHandler;}@Overridepublic void handleLog(Log log) {if (log.getLogType() == LogType.ERROR) {// 将错误日志记录到数据库System.out.println("Error log recorded in database: " + log.getMessage());} else if (nextHandler != null) {nextHandler.handleLog(log);}}}// 具体处理者 - 警告日志处理者class WarningLogHandler implements LogHandler {private LogHandler nextHandler;@Overridepublic void setNextHandler(LogHandler nextHandler) {this.nextHandler = nextHandler;}@Overridepublic void handleLog(Log log) {if (log.getLogType() == LogType.WARNING) {// 发送警告邮件给管理员System.out.println("Warning email sent to admin: " + log.getMessage());} else if (nextHandler != null) {nextHandler.handleLog(log);}}}// 具体处理者 - 信息日志处理者class InfoLogHandler implements LogHandler {private LogHandler nextHandler;@Overridepublic void setNextHandler(LogHandler nextHandler) {this.nextHandler = nextHandler;}@Overridepublic void handleLog(Log log) {if (log.getLogType() == LogType.INFO) {// 打印信息日志到控制台System.out.println("Info log: " + log.getMessage());} else if (nextHandler != null) {nextHandler.handleLog(log);}}}// 日志对象class Log {private LogType logType;private String message;public Log(LogType logType, String message) {this.logType = logType;this.message = message;}public LogType getLogType() {return logType;}public String getMessage() {return message;}}// 日志类型枚举enum LogType {ERROR, WARNING, INFO}// 客户端代码public class Client {public static void main(String[] args) {// 创建处理者链LogHandler errorHandler = new ErrorLogHandler();LogHandler warningHandler = new WarningLogHandler();LogHandler infoHandler = new InfoLogHandler();errorHandler.setNextHandler(warningHandler);warningHandler.setNextHandler(infoHandler);// 发送不同类型的日志Log errorLog = new Log(LogType.ERROR, "Error occurred in database connection.");Log warningLog = new Log(LogType.WARNING, "Disk space is running low.");Log infoLog = new Log(LogType.INFO, "Application started successfully.");errorHandler.handleLog(errorLog);errorHandler.handleLog(warningLog);errorHandler.handleLog(infoLog);}}
  1. LogHandler 接口定义了处理日志的方法 handleLog。它还包含一个 setNextHandler 方法,用于设置链中的下一个处理者。
  2. ErrorLogHandlerWarningLogHandlerInfoLogHandler 是三个具体的处理者类,分别用于处理错误日志、警告日志和信息日志。每个处理者首先检查日志类型是否与自己负责的类型匹配,如果匹配则执行相应的操作;否则,将请求传递给链中的下一个处理者(如果存在)。
  3. Log 类表示一个日志对象,包含日志类型和日志消息。
  4. LogType 枚举定义了三种日志类型:错误、警告和信息。
  5. 在客户端代码中,我们创建了三个具体处理者的实例,并将它们连接成一条链。然后,我们发送了三种不同类型的日志对象,这些日志对象将沿着链传递,直到被相应的处理者处理。

通过使用责任链模式,我们将日志处理的逻辑分散到不同的处理者中,每个处理者只需关注自己负责的日志类型。这种设计模式提高了代码的可维护性和可扩展性。如果需要添加新的日志类型或修改现有的处理逻辑,只需创建或修改相应的处理者类,而不需要修改其他代码。

此外,责任链模式还能够动态地组合和重组处理者链,以满足不同的需求。例如,在某些情况下,我们可能希望将某些类型的日志记录到文件中,而不是发送电子邮件或打印到控制台。在这种情况下,我们只需创建一个新的处理者类,并将其插入到处理者链中即可。

三、Servlet 过滤器源码分析

责任链模式在 Java 中有很多应用场景,例如 Servlet 过滤器、Java Web 开发中的拦截器(Interceptor)、安全框架中的身份验证等。Spring 框架的异常处理机制 HandlerExceptionResolver 就是一个典型的责任链模式的应用。

Servlet 过滤器(Filter)在 Servlet 规范中就采用了责任链模式的设计。当一个请求到达 Web 应用程序时,它会被传递给一系列的过滤器进行处理,每个过滤器都有机会对请求进行处理,或者将其传递给下一个过滤器。这种设计模式使得过滤器可以被动态地添加或删除,而不会影响其他过滤器的执行。

下面我们来分析一下 Servlet 过滤器是如何实现责任链模式的。

在 Servlet 规范中,Filter 接口定义了过滤器的基本方法,其中最重要的方法是 doFilter()。该方法的代码如下:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 过滤器的自定义处理逻辑...// 将请求传递给下一个过滤器或目标资源chain.doFilter(request, response);}

在这个方法中,我们可以看到两个重要的部分:

  1. 过滤器自己的处理逻辑。
  2. 调用 FilterChaindoFilter() 方法,将请求传递给下一个过滤器或目标资源。

FilterChain 接口定义如下:

public interface FilterChain {public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException;}

这个接口只有一个 doFilter() 方法,用于将请求传递给下一个过滤器或目标资源。

在 Servlet 容器中,FilterChain 的实现类 ApplicationFilterChain 维护了一个过滤器列表,并负责按顺序调用每个过滤器的 doFilter() 方法。ApplicationFilterChain 的部分代码如下:

public final class ApplicationFilterChain implements FilterChain {private int pos = 0;private int n;private ApplicationFilterConfig[] filterConfigs;// ...public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {if (pos < n) {ApplicationFilterConfig filterConfig = filterConfigs[pos++];Filter filter = filterConfig.getFilter();filter.doFilter(request, response, this);} else {// 如果没有更多过滤器,则执行目标资源servlet.service(request, response);}}}

在上面的代码中,ApplicationFilterChain 维护了一个 filterConfigs 数组,存储了所有需要执行的过滤器。在 doFilter() 方法中,它会依次调用每个过滤器的 doFilter() 方法,并将自身作为 FilterChain 对象传递给过滤器。

当一个过滤器执行完自己的逻辑后,它会调用 FilterChaindoFilter() 方法,将请求传递给下一个过滤器。如果所有过滤器都执行完毕,则执行目标资源(如 Servlet)。

通过这种设计,过滤器就形成了一个责任链。每个过滤器都有机会对请求进行处理,并决定是继续执行下一个过滤器还是终止过滤器链。这种模式使得过滤器能够被动态地添加、删除或重新排序,而不会影响其他过滤器的执行。