责任链如何设计?Tomcat和Netty分别是如何实现过滤器链和事件链?

导读:本篇文章讲解 责任链如何设计?Tomcat和Netty分别是如何实现过滤器链和事件链?,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

在业务开发中,对于同一类逻辑处理,一般都是都是归类为一组集合,类似于一条业务链来处理,流程如下:

责任链如何设计?Tomcat和Netty分别是如何实现过滤器链和事件链?

举个例子,在下单过程中,会对生成订单进行很多条件判断。通常,会定义一个校验接口Validator定义生成订单的条件判断业务的抽象;在下单过程因为有很多不同的条件判断,所以,就会在下单的服务中,定义类似于一组的校验业务,例如,定义集合List<Validator> validators。

如果,在链式业务处理过程中,对于不同的订单会有校验逻辑需要满足条件才能判断,这时如何进行过滤呢?如果在不同的下单场景需要动态的添加或者修改一些校验条件,应该如何操作呢?有的业务有不同的优先级和先后顺序,应该如何设计?下面以tomcat中Filter过滤器链,Netty的事件链式处理为例,来介绍常见的链式设计。tomcat的filter过滤器链基于List集合,Netty的事件链表基于链表实现。

tomcat中Filter过滤器的实现

在tomcat中,对于Http请求通常会进行用户身份的校验,权限的验证,日志处理等等业务的拦截和过滤。模仿tomcat实现的伪代码,定义Filter接口实现过滤业务的封装,CompositeFilter定义一组的过滤逻辑处理集合,FilterChain决定每次执行的FIlter的选择逻辑,每次执行时,从条件过滤组中筛选满足条件的过滤器Filter进行。过滤器链执行流程的大致如下:

责任链如何设计?Tomcat和Netty分别是如何实现过滤器链和事件链?

CompositeFilter以List集合存储所有Filter,如果需要对Filter进行排序,可以继承Ordered接口。FilterChain根据List集合的index,从0到index-1的顺序来获取Filter。Filter,CompositeFilter,FilterChain伪代码定义如下:

/**
 * 过滤器,继承Ordered自定义过滤器的优先级
 **/
public interface Filter extends Ordered{
    /**
     * 过滤
     */
    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
}

/**
 * 过滤器每次执行的筛选定义
 **/
public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}


/**
 * 过滤器集合定义,使用List集合进行封装
 **/
public class CompositeFilter implements Filter {

    // 过滤器列表
    private List<? extends Filter> filters = new ArrayList();

    public CompositeFilter() {
    }

    public void setFilters(List<? extends Filter> filters) {
        // 根据定义的顺序排序
        this.filters =  AnnotationAwareOrderComparator.sort(new ArrayList(filters));
    }

    /**
     * 过滤逻辑
     **/
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        (new CompositeFilter.VirtualFilterChain(chain, this.filters)).doFilter(request, response);
    }

  
    /**
	 * 内部类实现自定义的FilterChain,根据list集合的index,从0开始顺序的选择Filter进行处理
	 */
    private static class VirtualFilterChain implements FilterChain {
        private final FilterChain originalChain;
        private final List<? extends Filter> additionalFilters;
        // 起始筛选的index
        private int currentPosition = 0;

        public VirtualFilterChain(FilterChain chain, List<? extends Filter> additionalFilters) {
            this.originalChain = chain;
            this.additionalFilters = additionalFilters;
        }

        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            if (this.currentPosition == this.additionalFilters.size()) {
                this.originalChain.doFilter(request, response);
            } else {
                ++this.currentPosition;
                // 根据index从过滤器集合中选择Filter
                Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
                nextFilter.doFilter(request, response, this);
            }
        }
    }
}

Netty中事件过滤器链的处理

在基于Socket的网络编程中,当Socket绑定接口时,连接或断开,读取或者写入数据时,都会有不同的业务需要处理。例如,从Socket读取数据,或者写人数据时,通常会进行数据的编码节码,数据的业务处理。Netty是对网络编程的封装,定义了ChannelHandler实现对各种事件的触发的时的业务逻辑,定义ChannelPipeline基于链表集合实现对所有的事件处理逻辑封装,ChannelHandlerContext基于前后事件的绑定,实现了对于每次执行事件触发时,事件的选择逻辑。其执行流程如下:

责任链如何设计?Tomcat和Netty分别是如何实现过滤器链和事件链?

 

ChannelPipeline的默认实现类是DefaultChannelPipeline 在添加具体的ChannelHandler实现类时,会把ChannelHandler封装为AbstractChannelHandlerContext ,通过AbstractChannelHandlerContext 指定ChannelHandler前后的关联节点。ChannelHandler,ChannelPipeline,ChannelHandlerContext接口源码如下:

/**
 * 各类Socket事件的具体业务定义的父接口
 */
public interface ChannelHandler {

    /**
     * 新增handler
     */
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;

    /**
     *删除handler
     */
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;

    /**
     * 异常的处理
     */
    @Deprecated
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}


/**
 * 基于链表实现AbstractChannelHandlerContext的封装,基于头部和尾部的数据数据添加,各类事件监听的统一调用
 */
public class DefaultChannelPipeline implements ChannelPipeline {

    /**
     * 链表head与tail节点定义
     */
    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;

    /**
     * 链表首部插入逻辑
     */
    private void addFirst0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext nextCtx = head.next;
        newCtx.prev = head;
        newCtx.next = nextCtx;
        head.next = newCtx;
        nextCtx.prev = newCtx;
    }

   /**
     * 链表首部插尾部入逻辑
     */
    private void addLast0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext prev = tail.prev;
        newCtx.prev = prev;
        newCtx.next = tail;
        prev.next = newCtx;
        tail.prev = newCtx;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception { }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            onUnhandledInboundException(cause);
        }

   /**
     * 读取数据事件逻辑
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            onUnhandledInboundMessage(msg);
        }

   /**
     * 写入数据事件逻辑
     */
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            unsafe.write(msg, promise);
        }

}

/**
  * 链表事件筛选逻辑
  */
abstract class AbstractChannelHandlerContext extends DefaultAttributeMap
        implements ChannelHandlerContext, ResourceLeakHint {


  /**
    * 读取数据逻辑
    */
    @Override
    public ChannelHandlerContext fireChannelRead(final Object msg) {
        invokeChannelRead(findContextInbound(), msg);
        return this;
    }

 /**
    * 选取next指定的AbstractChannelHandlerContext执行
    */
 private AbstractChannelHandlerContext findContextInbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.next;
        } while (!ctx.inbound);
        return ctx;
    }

}

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13616.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!