设计模式的应用——《职责链模式》
一、职责链模式基础知识(What、Why、How)
1、什么是职责链模式?
职责链模式,是一种行为型设计模式。它的目标在于将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
2、如何使用职责链模式?——它的原理和实现
在我们实际的应用过程中,职责链模式有如下两种:
- 将所有处理者连成一条链,并沿着这条链传递请求,直到有对象处理它为止。
- 请求会被所有的处理器都处理一遍,不存在中途终止的情况。
如上两类职责链,仅仅是在处理方式上不一样,其他的都是一样的。我们的职责链模式主要包含两个角色:抽象处理者、具体处理者
我们要实现一个职责链模式,首先,我们需要创建一个抽象处理者类(Handler),它包含一个指向下一个处理者的引用。然后,我们创建具体处理者类(ConcreteHandlerA和ConcreteHandlerB),它们分别实现抽象处理者类的方法。最后,我们创建一个客户端类(Client),它负责将请求传递给处理者链中的下一个处理者。
以下是第一类情况的示例代码:
import java.util.ArrayList;
import java.util.List;
// 抽象处理者类
abstract class Handler {
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public abstract void handleRequest(String request);
}
// 具体处理者A类
class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("A")) {
System.out.println("ConcreteHandlerA处理请求");
} else {
if (successor != null) {
successor.handleRequest(request);
} else {
System.out.println("没有合适的处理器处理请求");
}
}
}
}
// 具体处理者B类
class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("B")) {
System.out.println("ConcreteHandlerB处理请求");
} else {
if (successor != null) {
successor.handleRequest(request);
} else {
System.out.println("没有合适的处理器处理请求");
}
}
}
}
// 客户端类
class Client {
private List<Handler> handlers = new ArrayList<>();
public Client() {
handlers.add(new ConcreteHandlerA());
handlers.add(new ConcreteHandlerB());
for (int i = 0; i < handlers.size() - 1; i++) {
handlers.get(i).setSuccessor(handlers.get(i + 1));
}
}
public void sendRequest(String request) {
for (Handler handler : handlers) {
handler.handleRequest(request);
}
}
}
public class Main {
public static void main(String[] args) {
Client client = new Client();
client.sendRequest("A"); // 输出:ConcreteHandlerA处理请求
client.sendRequest("B"); // 输出:ConcreteHandlerB处理请求
client.sendRequest("C"); // 输出:没有合适的处理器处理请求
}
}
3、为什么用职责链模式?
既然我们用它,那我们就要讲讲它的优点啦!职责链模式有如下优点:
- 请求与处理解耦
在职责链模式中,处理者只需关心自己的处理逻辑,如果不是自己的处理的请求直接转发,降低了对象间的耦合性。
- 链式传递功能
一个请求可以在一条链上进行传递,直到链上的某一个节点决定处理此请求。这种结构使得请求者无须了解链路的结构,只需要将请求发送出去,等待结果返回即可。
- 易于维护和拓展
由于职责链模式遵循开闭原则,可以灵活地修改链路结构,比如新增或删除环境。另外, 可以灵活动态的选择需要哪些职责。
- 增强系统的可扩展性
符合开闭原则,当我们需要添加新的职责时,我们只需添加新的职责类,并且添加到执行链中去即可。而无需修改其他代码。
二、什么场景下使用它?——职责链模式在框架和实际工作中的应用
职责链在很多框架中都有使用,下面仅介绍在Servlet Filter、Spring Interceptor以及dubbo Filter中的使用。
Servlet Filter
Servlet Filter是Java Web开发中用于对请求和响应进行处理的过滤器。它可以在请求到达目标资源之前或响应返回客户端之前执行一些操作,例如验证用户身份、记录日志、压缩响应等。Servlet Filter通常通过实现javax.servlet.Filter接口来定义。
以下是一个简单的Servlet Filter示例:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化过滤器时执行的操作,例如加载配置文件
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 在请求到达目标资源之前执行的操作
System.out.println("请求被MyFilter处理");
// 继续执行过滤器链中的下一个过滤器或目标资源
chain.doFilter(request, response);
// 在响应返回客户端之后执行的操作
System.out.println("响应被MyFilter处理");
}
@Override
public void destroy() {
// 销毁过滤器时执行的操作,例如释放资源
}
}
要使用这个过滤器,需要在web.xml文件中进行配置:
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.example.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Spring Interceptor
在Spring框架中,拦截器(Interceptor)是一种用于处理请求和响应的机制。通过使用拦截器,我们可以实现职责链模式,将多个拦截器按照顺序组合起来,以便在一个请求到达目标资源之前或之后执行特定的操作。
以下是一个简单的示例,展示了如何在Spring拦截器中使用职责链模式:
1、首先,创建一个拦截器接口HandlerInterceptor,并定义一个方法preHandle,该方法将在请求到达目标资源之前执行:
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler);
}
2、然后,创建一个具体的拦截器类MyInterceptor,实现HandlerInterceptor接口,并在preHandle方法中实现职责链模式的逻辑:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
private HandlerInterceptor nextInterceptor;
public MyInterceptor(HandlerInterceptor nextInterceptor) {
this.nextInterceptor = nextInterceptor;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 在这里实现你的职责链逻辑
System.out.println("MyInterceptor: preHandle");
// 如果当前拦截器没有处理完请求,将请求传递给下一个拦截器
if (nextInterceptor != null) {
return nextInterceptor.preHandle(request, response, handler);
}
// 如果当前拦截器已经处理完请求,返回true
return true;
}
}
3、接下来,创建一个配置类WebMvcConfig,用于注册拦截器并配置拦截器的执行顺序:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册第一个拦截器,并设置其执行顺序为1
registry.addInterceptor(new MyInterceptor(null)).addPathPatterns("/**").order(1);
// 注册第二个拦截器,并设置其执行顺序为2
registry.addInterceptor(new MyInterceptor(new MyInterceptor(null))).addPathPatterns("/**").order(2);
}
}
在这个示例中,我们创建了两个拦截器MyInterceptor,并将它们按照顺序添加到拦截器注册表中。当一个请求到达目标资源时,Spring会按照拦截器的执行顺序依次调用它们的preHandle方法。如果某个拦截器没有处理完请求,它会将请求传递给下一个拦截器。
Dubbo Filter
Dubbo中的过滤器(Filter)是一种用于处理请求和响应的中间件。它可以在服务提供者和消费者之间对请求进行预处理、后处理或者拦截,从而实现对请求和响应的定制化处理。Dubbo支持多种类型的过滤器,如:
协议过滤器(Protocol Filter):用于处理请求和响应的序列化和反序列化,例如将Java对象转换为JSON字符串,或将JSON字符串转换为Java对象。
路由过滤器(Routing Filter):用于根据条件选择不同的服务提供者或消费者,例如基于权重、随机、轮询等策略进行负载均衡。
指标过滤器(Metrics Filter):用于收集和统计服务的调用次数、响应时间等信息,以便进行性能监控和优化。
限流过滤器(RateLimiting Filter):用于限制服务的调用速率,以防止系统过载。
安全过滤器(Security Filter):用于验证请求的身份信息,例如使用OAuth2、JWT等认证机制。
要使用过滤器,需要在Dubbo配置文件中进行配置。例如,使用dubbo:protocol标签配置协议过滤器:
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="http" port="8080" filter="statistic" />
在这个例子中,我们为Dubbo协议配置了一个名为statistic的过滤器,该过滤器会在请求和响应之间收集统计信息。
下面是Dubbo中RandomRouterFilterWrapper的源码, 该类继承自AbstractRouter,并实现了route方法。在route方法中,首先通过扩展点获取随机路由过滤器,如果存在则调用其route方法进行路由;如果不存在,则使用默认的路由规则。同时,还实现了getAlgorithmName和compare方法,分别返回算法名称和比较结果。
import org.apache.dubbo.common.Constants;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.router.AbstractRouter;
import org.apache.dubbo.rpc.router.RouteRule;
import java.util.List;
import java.util.Random;
public class RandomRouterFilterWrapper extends AbstractRouter {
private final Random random = new Random();
@Override
public List<RouteRule> route(List<RouteRule> rules, RpcContext context) {
// 获取扩展点中的随机路由过滤器
RandomRouterFilter filter = ExtensionLoader.getExtensionLoader(RandomRouterFilter.class).getExtension(Constants.DEFAULT_EXTENSION_NAME);
if (filter != null) {
return filter.route(rules, context);
}
// 如果没有配置随机路由过滤器,则使用默认的路由规则
return super.route(rules, context);
}
@Override
public String getAlgorithmName() {
return "random";
}
@Override
public int compare(String o1, String o2) {
return random.nextInt(2) - 1;
}
}
三、总结与思考
职责链设计模式常常在开发框架中使用,用来实现框架的过滤器、拦截器功能,让框架的使用者在不需要对源码进行修改的情况下,添加新的过滤拦截功能,体现了开闭原则。但具体每个框架的实现会根据实际的需求来设计。下面是关于职责链模式的一些思考,分享给大家:
1、Filter、Interceptor、AOP 都可以实现访问控制功能,它们之间的不同点是什么?
Filter、Interceptor和AOP都可以实现访问控制功能,这是它们的相同点。然而,它们在实现和使用上存在一些不同点。
-
过滤器(Filter)属于Servlet规范,主要拦截web访问的url地址。其工作原理基于Tomcat提供的接口,直接对请求和响应进行处理。
-
拦截器(Interceptor)依赖于Spring框架,主要拦截以.action结尾的url,以及Action的访问。拦截器关注的是接口执行前和执行后要做的事,并且只能拦截controller的请求。
-
Spring AOP拦截器是基于Spring AOP实现的,它只能拦截Spring管理Bean的访问,如业务层Service。与过滤器和拦截器不同的是,Spring AOP关注的是方法执行前后要做的事情。
该三者的拦截顺序是分级的,优先级从高到低依次是:Filter、Interceptor、AOP。但在某些特殊情况下,也可以通过调整配置来改变这个顺序。例如,在Spring Boot中,可以通过配置文件来改变拦截器的执行顺序。
2、和职责链模式相似的设计模式有哪些?
-
命令模式(Command Pattern):此模式也是一种行为型模式,类似于职责链模式,它也通过将请求封装成对象来达到解耦的目的。不过命令模式中的命令对象会包含与请求相关的信息,比如请求的接收者、方法名等。
-
解释器模式(Interpreter Pattern):该模式同样属于行为型模式,它通过定义语言的文法以及解析规则来解析语言。这与职责链模式中多个处理者按顺序处理请求的方式相似。
-
中介者模式(Mediator Pattern):这是一种结构型模式,它通过引入一个中介者对象来解除原有对象之间的依赖关系,从而降低系统的耦合度。这与职责链模式中的处理者链条有异曲同工之妙。
-
观察者模式(Observer Pattern):这也是一种行为型模式,它通过定义一对多的通知机制来实现当一个对象状态改变时,所有依赖于它的对象都会得到通知并自动更新。这和职责链模式中每个处理者都只负责处理自己感兴趣的请求,然后将其转发给后续的处理者的思想很相似。
3、职责链模式经常与哪些设计模式配合使用?
- 简单工厂模式:在创建处理者对象时,可以使用简单工厂模式来封装对象的创建过程和逻辑。
- 观察者模式:可以在处理者中加入对其他处理者的引用,然后动态的插入和移除后续处理者,以改变处理顺序。
- 单例模式:对于某些需要保证唯一性的处理者,可以采用单例模式来确保在整个系统中只有一个实例存在。
- 模板方法模式:可以在一个处理者的基类中定义一些算法的框架,而将具体实现延迟到子类中。这样,客户端可以通过调用基类的算法接口来达到处理请求的目的,而不需要直接访问子类。