Java的责任链模式在项目中的使用
责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许通过多个处理对象来传递请求,直到一个处理对象处理该请求为止。责任链模式的主要优点是可以解耦请求发送者和请求接收者,客户端无需明确指定哪个对象负责处理请求,只需要将请求传递给链中的第一个处理对象即可。
责任链模式的应用场景:
- 请求处理的多个阶段:当一个请求需要多个处理步骤时,每个步骤可以作为责任链中的一个节点进行处理。
- 请求处理顺序不固定:请求的处理顺序不固定,具体由系统的需要来决定,通常由责任链中的对象决定是否继续传递请求。
- 链中对象可动态添加:责任链中的处理对象可以动态调整。
Spring Web MVC (HandlerInterceptor)
HandlerInterceptor
是一个非常有用的接口,它允许我们在请求到达 Controller
之前和之后,甚至在视图渲染之后执行一些处理。HandlerInterceptor
是基于责任链模式(Chain of Responsibility Pattern)实现的,多个拦截器可以按顺序执行,每个拦截器可以在请求的生命周期中执行不同的任务。
- preHandle:请求到达
Controller
之前调用,通常用于进行权限检查、日志记录等操作。如果返回false
,请求将终止,后续的拦截器和Controller
不会被执行。 - postHandle:请求完成
Controller
执行后,视图渲染之前调用。可以用于在视图渲染之前对模型数据进行处理(例如,修改模型数据或添加属性等)。 - afterCompletion:视图渲染完成后调用,可以用于清理资源(例如,关闭数据库连接,日志记录等)。
public interface HandlerInterceptor {
// 在Controller处理请求之前进行调用
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
// 在Controller处理请求之后,视图渲染之前调用
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
// 视图渲染完成之后进行调用
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
}
如何在 Spring MVC 中使用 HandlerInterceptor
1. 创建自定义的 HandlerInterceptor 实现
首先,我们需要实现 HandlerInterceptor
接口,并重写 preHandle
、postHandle
和 afterCompletion
方法。例如:
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
// 在Controller之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Pre Handle: Request intercepted before reaching the Controller");
// 返回true表示继续处理请求,返回false表示请求被拦截,不再传递给Controller
return true;
}
// 在Controller之后,视图渲染之前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("Post Handle: Request intercepted after the Controller has finished processing");
}
// 视图渲染完成之后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("After Completion: Request has completed, used for cleanup");
}
}
2. 拦截器链的顺序
如果注册了多个拦截器,拦截器会按照注册顺序执行 preHandle
方法,并且按相反顺序执行 afterCompletion
方法。例如:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**");
}
}
MyInterceptor1
的 preHandle
会在 MyInterceptor2
的 preHandle
之前执行,afterCompletion
会按相反顺序执行。preHandle就组成了一条责任链,afterCompletion又是一条责任链。
常见用途
- 权限验证:可以使用
preHandle
检查请求是否具有合法的权限(例如用户是否登录)。 - 日志记录:使用
preHandle
记录请求的 URL 和时间,使用postHandle
记录响应结果。 - 性能监控:通过
preHandle
和postHandle
计算请求处理的时间。 - 修改模型数据:在
postHandle
中修改ModelAndView
,比如添加全局的属性或对模型数据进行修改。 - 清理工作:在
afterCompletion
中进行清理工作,比如释放数据库连接、线程池资源等。
开发中责任链的运用
1.校验参数
在创建商品信息的时候需要去校验很多的各种各样的信息,而且每次需要增加新的条件都需要去修改代码(不符合软件设计的开闭原则):
public void createGoods(GoodsSaveReqDTO requestParam) {
// 验证请求参数是否正确
if (ObjectUtil.equal(requestParam.getTarget(), DiscountTargetEnum.PRODUCT_SPECIFIC)) {
// 验证商品是否存在,如果不存在抛出异常
// ......
}
if (StrUtil.isEmpty(requestParam.getName())) {
throw new ClientException("名称不能为空");
}
if (ObjectUtil.isEmpty(requestParam.getSource())) {
throw new ClientException("来源不能为空");
}
//...
// 新增信息到数据库
// ...
}
2.用责任链模式来修改上面的代码:
public void createGoods(GoodsSaveReqDTO requestParam) {
// 通过责任链验证请求参数是否正确
ChainContext.handler(CREATE_Goods_KEY.name(), requestParam);
// 信息到数据库
// ...
}
@Component
public final class ChainContext<T> implements ApplicationContextAware, CommandLineRunner {
private final Map<String, List<ChainHandler>> ChainHandlerContainer = new HashMap<>();
/**
* 责任链组件执行
*
* @param mark 责任链组件标识
* @param requestParam 请求参数
*/
public void handler(String mark, T requestParam) {
// 根据 mark 标识从责任链容器中获取一组责任链实现 Bean 集合
List<ChainHandler> ChainHandlers = ChainHandlerContainer.get(mark);
ChainHandlers.forEach(each -> each.handler(requestParam));
}
@Override
public void run(String... args) throws Exception {
// 从 Spring IOC 容器中获取指定接口 Spring Bean 集合
Map<String, abstractChainHandler> chainFilterMap = applicationContext.getBeansOfType(abstractChainHandler.class);
chainFilterMap.forEach((beanName, bean) -> {
// 判断 Mark 是否已经存在抽象责任链容器中,如果已经存在直接向集合新增;如果不存在,创建 Mark 和对应的集合
List<ChainHandler> ChainHandlers = ChainHandlerContainer.getOrDefault(bean.mark(), new ArrayList<>());
ChainHandlers.add(bean);
ChainHandlerContainer.put(bean.mark(), abstractChainHandlers);
});
abstractChainHandlerContainer.forEach((mark, unsortedChainHandlers) -> {
// 对每个 Mark 对应的责任链实现类集合进行排序,优先级小的在前
unsortedChainHandlers.sort(Comparator.comparing(Ordered::getOrder));
});
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
要实现具体的handle方法就继承abstractChainHandler
public interface AbstractChainHandler<T> extends Ordered {
/**
* 执行责任链逻辑
*
* @param requestParam 责任链执行入参
*/
void handler(T requestParam);
/**
* @return 责任链组件标识
*/
String mark();
}
@Component
public class GoodsCreateParamNotNullChainFilter implements AbstractChainHandler<GoodsSaveReqDTO> {
@Override
public void handler(GoodsSaveReqDTO requestParam) {
if (StrUtil.isEmpty(requestParam.getName())) {
throw new ClientException("名称不能为空");
}
if (ObjectUtil.isEmpty(requestParam.getSource())) {
throw new ClientException("来源不能为空");
}
//...其它条件
}
@Override
public String mark() {
return CREATE_Goods_KEY.name();
}
@Override
public int getOrder() {
return 0;
}
}
这样就实现了解耦,想要添加新的判断条件,只要再写一个继承abstractChainHandler的类就可以了。