定义
责任链模式:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
小编这里理解的,主要目的就是解耦合。
需求
这里小编先讲自己做项目实战过程中用到该模式的场景,目前在做的是电商平台,电商平台中所涉及的模块,主要为商品,用户,营销,订单等等。以商品创建过程为例,这边设计方案,是逐步创建,也就是说从商品的基本信息(名字,类型),商品价格,商品属性,商品上下架状态时间,商品限购条件等等,但管理商品的时候可以有删除按钮(这边不知道各位有没有这个功能),则删除的时候需要逐步删除表结构数据,从基本信息表,价格表商品属性表等等删除,如果都做在一个方法中当然也是可以的,但是因为商品是逐步创建的,这样我们可以使用责任链,将其解耦合。将商品相关编号或id传入,逐个进行删除即可。同时营销活动也一样,基本信息,可购买商品,可参与的用户,送的奖品等等,在删除时也可以使用责任链模式。这样代码在使用者面前简洁有效。
当然这里不可能贴出项目代码,并且项目代码用springIoc管理,大家可以想象怎么让链条单例并扔进去(其实很简单),小编这里有另一个需求,即 博客的评论;那么先阐述一下需求:
博客上大家都可以评论,但是不乏有键盘侠或者对博主单方面有意见的用户进行恶意评论等等,处于这种考虑,系统对评论做了一系列的评论检查功能,比方说评论过长(测试同学经常将一篇小说拷贝进来),评论敏感字符,评论的特殊字符(防止sql注入攻击等)等等,这主要做了评论的风控。
- 字数限制
- 敏感字符的限定
- 特殊字符的排除
话不多说先上代码
评论类,只包含评论内容,和一个用户标示
@Data
@AllArgsConstructor
public class Comment {
private String msg;
private String user;
}
评论过滤器,包含频率过滤的方法
public interface CommentFilter {
/**
* 评论过滤方法
* @param comment 评论
*/
void doFilter(Comment comment);
}
三个评论过滤器的实现类,大家不用较真这些过滤方法啊
//长度过滤
public class CommentLengthFilter implements CommentFilter {
@Override
public void doFilter(Comment comment) {
comment.setMsg(comment.getMsg().substring(0,100));
}
}
//敏感词过滤
public class CommentSensitiveFilter implements CommentFilter {
@Override
public void doFilter(Comment comment) {
comment.setMsg(comment.getMsg().replaceAll("性爱", "**"));
}
}
//非法字符过滤
public class CommentIllegalFilter implements CommentFilter {
@Override
public void doFilter(Comment comment) {
comment.setMsg(comment.getMsg().replaceAll(">", ""));
}
}
过滤器链类编写,这里说明一下为什么链路也要实现过滤器,主要在过滤器链可以加其他过滤器链,
比方说A链有B和C两个过滤器,F有D和E过滤器,则A 可以加入F,同样F可以加入A。使得更加灵活可扩展。
public class CommentFilterChain implements CommentFilter{
private List<CommentFilter> commentFilterList = new ArrayList<>();
public void addFilter(CommentFilter filter){
commentFilterList.add(filter);
}
@Override
public void doFilter(Comment comment){
for (CommentFilter commentFilter : commentFilterList) {
commentFilter.doFilter(comment);
}
}
}
接下来咱们编写测试类,这边过滤器链可以提前组装好,很多源码中也会提前写好过滤链路顺序
public class CommentCheckTest {
private CommentFilterChain chain;
@Before
public void initChainLit() {
chain = new CommentFilterChain();
chain.addFilter(new CommentLengthFilter());
chain.addFilter(new CommentIllegalFilter());
chain.addFilter(new CommentSensitiveFilter());
}
@Test
public void commentChainTest() {
Comment comment = new Comment("理性爱戴国产产品>.........省略500字","lin");
chain.doFilter(comment);
System.out.println(comment.getMsg());
}
}
讲到这里一个完整的责任链就出来了,如果平常的话,我们可以将三个方法写到一个方法中去,很多小伙伴觉得没必要啊,全部写入更加直观更容易理解,但是随着需求的不断增多,这个方法也会不多扩大。接着我们加入一个新的需求
** 加入黑名单过滤 ,假如发现黑名单则后续的过滤器不需要再次执行,那目前的过滤器链可以抛异常中断接下来的操作即可,但是不优雅切得捕获异常,当然也可以过滤后返回值来判断,但是仍然不优雅,还得改原有逻辑 **
思考一下,继续上代码。
黑名单过滤器类
public class CommentBlackFilter implements CommentFilter {
private CommentFilter filter;
public CommentBlackFilter(CommentFilter filter) {
this.filter = filter;
}
@Override
public void doFilter(Comment comment) {
if ("Bob".equals(comment.getUser())) {
return;
}
filter.doFilter(comment);
}
}
然后测试类修改,这样后续内容就没有过滤了,这边再次提醒小伙伴,测试尽量用断言啊。控制台打印可以自己看看,也可以集成log(题外话)
@Test
public void commentChainBlackTest() {
Comment comment = new Comment("理性爱戴国产产品>.........省略500字","Bob");
CommentBlackFilter commentBlackFilter = new CommentBlackFilter(chain);
System.out.println(comment.getMsg());
}
这样在不破坏原有的基础上,咱们扩展了责任链,当然也可以增加额外的逻辑,包括对传入的参数修改等等。
_ 到这里,我们的责任链模式基本讲完了,值得注意的是责任链实现方法有两种,一种为数组形式,一种为引用形式,各位觉得哪一种比较适合好,可以在评论区留言,当然运用得当可以将两种形式互相结合起来使用。接下来是在源码框架中的运用场景,大家如果不关心的话可以直接跳到总结了。_
在源码框架中的使用
Tomcat中的使用:首先大家猜一下tomcat的责任链模式是使用数组的方式还是引用的形式?
首先tomcat 责任链结构图
这边主要的责任链为ApplicationFilterChain,里面自己实现了一个数组扩容,也可以用ArrayList实现,可能年代比较久远tomcat自己实现了一套,包括过滤器总数,当当前位置小于过滤器总是即(pos<n)时则执行逻辑。
具体实现逻辑扩容
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;
}
具体链条实现逻辑
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++];
try {
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); //重要步骤
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
servlet.service(request, response); //重要步骤
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
通过以上源代码可以看到其实是使用数组形式实现的。
接下来我们介绍用引用类型实现的框架源码 :MyBatis
二级缓存责任链图
查看源码在CacheBuild中先进行配置,然后使用put,get方法直接使用缓存即可
public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
cache = new SerializedCache(cache);
}
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
其他表现形式
- 过滤器链 (上面tomcat等已经举例)
- 拦截器链 (springMVC拦截器即可,小伙伴有兴趣了解一下,小编一直觉得过滤器和拦截器差不多,原来是设计模式一样,拦截器同样可以用过滤器链来实现。差不多,只是职责不一样,业务场景不同,因为拦截器在业务逻辑之前和之后,包括返回结果之前还会过滤,dubbo同时也一样。)
- 管道流 (netty的pipeline,包括java8的stream流)
- 经典责任链(钉钉请假流程)
以上内容大家可以自行阅读,这边不在复述了。
总结
责任链模式是一种对象行为型模式,其主要优点如下。
1、降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
2、增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
3、增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
4、责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
5、责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
其主要缺点如下。
1、不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
2、对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
3、职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
参考和感谢
感谢源码阅读网的鲁班大叔,讲解的责任链模式。
参考网址:http://c.biancheng.net/view/1397.html