Contents

Java的责任链设计模式

1. 简介

在本文中,我们将了解一种广泛使用的设计模式责任链

我们可以在之前的文章 中找到更多的设计模式。

2. 责任链

维基百科 将责任链定义为由“一个命令对象源和一系列处理对象”组成的设计模式。

链中的每个处理对象负责某种类型的命令,处理完成后,将命令转发给链中的下一个处理器。

责任链模式适用于:

  • 解耦命令的发送者和接收者
  • 在处理时选择处理策略

所以,让我们看一个简单的模式示例。

3. 例子

我们将使用责任链来创建一个用于处理身份验证请求的链。

因此,输入身份验证提供程序将是command,每个身份验证处理器将是一个单独的Processor对象。

让我们首先为我们的处理器创建一个抽象基类:

public abstract class AuthenticationProcessor {
    public AuthenticationProcessor nextProcessor;
    
    // standard constructors
    public abstract boolean isAuthorized(AuthenticationProvider authProvider);
}

接下来,让我们创建扩展AuthenticationProcessor的具体处理器:

public class OAuthProcessor extends AuthenticationProcessor {
    public OAuthProcessor(AuthenticationProcessor nextProcessor) {
        super(nextProcessor);
    }
    @Override
    public boolean isAuthorized(AuthenticationProvider authProvider) {
        if (authProvider instanceof OAuthTokenProvider) {
            return true;
        } else if (nextProcessor != null) {
            return nextProcessor.isAuthorized(authProvider);
        }

        return false;
    }
}
public class UsernamePasswordProcessor extends AuthenticationProcessor {
    public UsernamePasswordProcessor(AuthenticationProcessor nextProcessor) {
        super(nextProcessor);
    }
    @Override
    public boolean isAuthorized(AuthenticationProvider authProvider) {
        if (authProvider instanceof UsernamePasswordProvider) {
            return true;
        } else if (nextProcessor != null) {
            return nextProcessor.isAuthorized(authProvider);
        }
    return false;
    }
}

在这里,我们为传入的授权请求创建了两个具体的处理器:UsernamePasswordProcessorOAuthProcessor

对于每一个,我们都覆盖了isAuthorized方法。

现在让我们创建几个测试:

public class ChainOfResponsibilityTest {
    private static AuthenticationProcessor getChainOfAuthProcessor() {
        AuthenticationProcessor oAuthProcessor = new OAuthProcessor(null);
        return new UsernamePasswordProcessor(oAuthProcessor);
    }
    @Test
    public void givenOAuthProvider_whenCheckingAuthorized_thenSuccess() {
        AuthenticationProcessor authProcessorChain = getChainOfAuthProcessor();
        assertTrue(authProcessorChain.isAuthorized(new OAuthTokenProvider()));
    }
    @Test
    public void givenSamlProvider_whenCheckingAuthorized_thenSuccess() {
        AuthenticationProcessor authProcessorChain = getChainOfAuthProcessor();
 
        assertFalse(authProcessorChain.isAuthorized(new SamlTokenProvider()));
    }
}

上面的示例创建了一个身份验证处理器链:UsernamePasswordProcessor -> OAuthProcessor。在第一个测试中,授权成功,在另一个测试中,它失败。

首先,UsernamePasswordProcessor检查身份验证提供程序是否是UsernamePasswordProvider的实例。

不是预期的输入,UsernamePasswordProcessor委托给OAuthProcessor

最后,OAuthProcessor处理命令。在第一个测试中,有一个匹配并且测试通过。第二,链中没有更多的处理器,因此测试失败。

4. 实施原则

在实施责任链时,我们需要牢记一些重要原则:

  • 链中的每个处理器都有其处理命令的实现
    • 在我们上面的示例中,所有处理器都有其isAuthorized的实现
  • 链中的每个处理器都应该引用下一个处理器
    • 上面,UsernamePasswordProcessor委托给OAuthProcessor
  • 每个处理器负责委派给下一个处理器,因此请注意丢失的命令
    • 同样在我们的示例中,如果命令是SamlProvider的实例,则该请求可能不会被处理并且将是未经授权的
  • 处理器不应形成递归循环
    • 在我们的示例中,我们的链中没有循环:UsernamePasswordProcessor -> OAuthProcessor。但是,如果我们明确地将UsernamePasswordProcessor设置为OAuthProcessor 的下一个处理器,那么我们的链中就会出现一个循环UsernamePasswordProcessor -> OAuthProcessor -> UsernamePasswordProcessor。在构造函数中使用下一个处理器可以帮助解决这个问题
  • 链中只有一个处理器处理给定的命令
    • 在我们的示例中,如果传入的命令包含OAuthTokenProvider的实例,那么只有OAuthProcessor会处理该命令

5. 在现实世界中的使用

在 Java 世界中,我们每天都受益于责任链。一个这样的经典示例是Java中的 Servlet 过滤器,它允许多个过滤器处理一个 HTTP 请求。尽管在这种情况下,每个过滤器都会调用链而不是下一个过滤器。

让我们看一下下面的代码片段,以便更好地理解 Servlet Filters 中的这种模式:

public class CustomFilter implements Filter {
    public void doFilter(
      ServletRequest request,
      ServletResponse response,
      FilterChain chain)
      throws IOException, ServletException {
        // process the request
        // pass the request (i.e. the command) along the filter chain
        chain.doFilter(request, response);
    }
}

如上面的代码片段所示,我们需要调用FilterChaindoFilter方法,以便将请求传递给链中的下一个处理器。

6. 缺点

现在我们已经看到了责任链是多么有趣,让我们记住一些缺点:

  • 大多数情况下,它很容易被破坏:
    • 如果一个处理器未能调用下一个处理器,则该命令将被丢弃
    • 如果一个处理器调用了错误的处理器,它可能会导致一个循环
  • 它可以创建深度堆栈跟踪,这会影响性能
  • 它可能导致跨处理器重复代码,增加维护