设计模式之美笔记13

记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步

策略模式

实际项目开发中,策略模式较为常用,利用它避免冗长的if-else或者switch分支判断,还包括提供框架的扩展点等。

策略模式的原理和实现

策略模式,Strategy Design Pattern。定义: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. 定义一族算法类,将每个算法分别封装起来,让他们可以互相替换,策略模式可使得算法的变化独立于使用它的客户端(指的是使用算法的代码)。

工厂模式是解耦对象的创建和使用,观察者模式是解耦观察者和被观察者。策略模式类似,也起到解耦的作用,解耦的是策略的定义、创建、使用三部分。

1. 策略的定义

较为简单,包含一个策略接口和一组实现该接口的策略类。因为所有的策略类都实现相同的接口,所有,客户端代码基于接口而非实现编程,可灵活的替换不同的策略。

public interface Strategy {
    void algorithmInterface();
}

public class ConcreteStrategyA implements Strategy {
    @Override
    public void algorithmInterface() {
        //...具体的算法...
    }
}

public class ConcreteStrategyB implements Strategy {
    @Override
    public void algorithmInterface() {
        //...具体的算法...
    }
}
2. 策略的创建

策略模式会包含一组策略,使用时,通过类型type判断创建哪种策略使用,为封装创建逻辑,需要对客户端代码屏蔽创建细节,可把根据type创建策略的逻辑抽离出来,放到工厂类。

public class StrategyFactory {
    private static final Map<String, Strategy> strategies = new HashMap<>();
    static {
        strategies.put("A",new ConcreteStrategyA());
        strategies.put("B",new ConcreteStrategyB());
    }
    public static Strategy getStrategy(String type){
        if (type == null || type.isEmpty()){
            throw new IllegalArgumentException("type should not be empty");
        }
        return strategies.get(type);
    }
}

如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可被共享使用的,不需要再每次调用getStrategy()的时候,都创建一个新的策略对象,针对这种情况,可使用上面的这种工厂类的实现方式,事先创建好每个策略对象,缓存到工厂类,用的时候直接返回;相反,如果策略类是有状态的,根据业务场景的需要,希望每次从工厂方法中,获得的是新创建的策略对象,而不是缓存好可共享的策略对象,代码如下:

public class StrategyFactory {
    public static Strategy getStrategy(String type){
        if (type == null || type.isEmpty()){
            throw new IllegalArgumentException("type should not be empty.");
        }
        if (type.equals("A")){
            return new ConcreteStrategyA();
        }else if (type.equals("B")){
            return new ConcreteStrategyB();
        }
        return null;
    }
}
3. 策略的使用

客户端代码如何确定使用哪个策略?最常见的是运行时动态确定使用哪种策略,这也是策略模式最典型的应用场景。

运行时动态指的是,事先不知道会使用哪种策略,而是在程序运行时,根据配置、用户输入、计算结果等不确定因素,动态决定使用哪种策略。

public class UserCache {
    private Map<String, User> cacheData = new HashMap<>();
    private EvictionStrategy eviction;
    public UserCache(EvictionStrategy eviction){
        this.eviction = eviction;
    }
    //...
}

public class Application {
    //运行时动态确定,根据配置文件的配置决定使用哪种策略
    public static void main(String[] args) throws Exception {
        EvictionStrategy evictionStrategy = null;
        Properties properties = new Properties();
        properties.load(new FileInputStream("./config.properties"));
        String type = properties.getProperty("eviction_type");
        evictionStrategy = EvictionStrategyFactory.getEvictionStrategy(type);
        UserCache userCache = new UserCache(evictionStrategy);
        //...
    }
}

从上面代码看出,非运行时动态确定,也就是第二个Application中的使用方式,并不能发挥策略模式的优势,这种应用场景下,策略模式退化为面向对象的多态特性 或 基于接口而非实现编程原则。

如何利用策略模式避免分支判断

能移除分支判断逻辑的模式不仅有策略模式,还有状态模式,对于使用哪种模式,具体看应用场景。策略模式适用于根据不同类型的状态,决定使用哪种策略这种应用场景。

先看if-else货switch是如何产生的,如下:

public class OrderService {
    public double discount(Order order){
        double discount = 0.0;
        OrderType type = order.getType();
        if (type.equals(OrderType.NORMAL)){
            //普通订单
            //...省略折扣计算算法代码
        }else if (type.equals(OrderType.GROUPON)){
            //团购订单  ...省略折扣计算算法代码
        }else if (type.equals(OrderType.PROMOTION)){
            //促销订单 ...省略折扣计算算法代码
        }
        return discount;
    }
}

那如何移除分支判断逻辑?策略模式,对代码重构,将不同类型的订单的打折策略设计为策略类,并由工厂类负责创建策略对象,具体代码:

public interface DiscountStrategy {
    double calDiscount(Order order);
}
//省略NormalDiscountStrategy GrouponDiscountStrategy PromotionDiscountStrategy的创建

//策略的创建
public class DiscountStrategyFactory {
    private static final Map<OrderType,DiscountStrategy> strategies = new HashMap<>();
    static {
        strategies.put(OrderType.NORMAL,new NormalDiscountStrategy());
        strategies.put(OrderType.GROUPON,new GrouponDiscountStrategy());
        strategies.put(OrderType.PROMOTION,new PromotionDiscountStrategy());
    }
    public static DiscountStrategy getDiscountStrategy(OrderType type){
        return strategies.get(type);
    }
}

public class OrderService {
    public double discount(Order order){
        OrderType type = order.getType();
        DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(type);
        return discountStrategy.calDiscount(order);
    }
}

在策略工厂,用map缓存策略,根据type直接从map中获取对应的策略,避免if-else分支判断逻辑。本质上是借助“查表法”,根据type查表替代根据type分支判断。

但是,如果业务场景需要每次都创建不同的策略对象,就要用另一种工厂类的实现方式

public class DiscountStrategyFactory {
    public static DiscountStrategy getDiscountStrategy(OrderType type){
        if (type == null){
            throw new IllegalArgumentException("type should not be null");
        }
        if (type.equals(OrderType.NORMAL)){
            return new NormalDiscountStrategy();
        }else if (type.equals(OrderType.GROUPON)){
            return new GrouponDiscountStrategy();
        }else if (type.equals(OrderType.PROMOTION)){
            return new PromotionDiscountStrategy();
        }
        return null;
    }
}

这种情况下,实际并未真正移除if-else逻辑,而是将其移到工厂类中。

文件排序

问题和解决思路

假设有个需求,希望写个小程序,实现对一文件进行排序的功能,文件只包含整型数,且相邻的数字通过逗号区分。如果你编写这个小程序,如何实现?

可能会觉得很简单,将文件的内容读出来,通过逗号分割为一个一个的数字,放到内存数组中,然后编写某种排序算法(如快排),或者直接用编程语言提供的排序函数。对数组排序,最后写入文件。

但如果文件很大呢?如有10GB,而内存有限(如只有8GB),没办法一次性加载文件的所有数据到内存,这时,利用外部排序算法了。

如果文件更大,如100GB,为利用CPU多核的优势,可在外排的基础上优化,加入多线程并发排序的功能,类似单机版的MapReduce。

如果文件非常大,如1TB,即便单机多线程程序,也算比较慢,这时可用MapReduce框架,利用多机处理能力,提高排序的效率。

代码实现和分析

先实现最简单直接的方式

public class Sorter {
    private static final long GB = 1000*1000*1000;
    public void sortFile(String filePath){
        //省略校验逻辑
        File file = new File(filePath);
        long fileSize = file.length();
        
        if (fileSize < 6*GB){
            quickSort(filePath);
        }else if (fileSize < 10*GB){
            externalSort(filePath);
        }else if (fileSize < 100*GB){
            concurrentExternalSort(filePath);
        }else {
            mapreduceSort(filePath);
        }
    }

    private void mapreduceSort(String filePath) {
        //利用MapReduce排序
    }

    private void concurrentExternalSort(String filePath) {
        //多线程外排
    }

    private void externalSort(String filePath) {
        //外部排序
    }

    private void quickSort(String filePath) {
        //快排
    }
}

public class SortingTool {
    public static void main(String[] args) {
        Sorter sorter = new Sorter();
        sorter.sortFile(args[0]);
    }
}

为避免sortFile()方法过长,把每种排序算法从sortFile()方法中抽离出来,分为4个独立的排序方法。

如果正在开发一个大型项目,排序文件只是其中一个功能模块,就要在代码设计和代码质量上下功夫。

刚如果自己实现算法的逻辑,会比较复杂,代码行数过多,所有排序算法的代码都堆在Sorter类,导致该类代码很多,此外,所有的排序算法都设计为Sorter的私有函数,也影响代码的复用性。

代码优化和重构

拆分重构后:

public interface ISortAlg {
    void sort(String filePath);
}
public class QuickSort implements ISortAlg {
    @Override
    public void sort(String filePath) {
        //...
    }
}

public class ExternalSort implements ISortAlg {
    @Override
    public void sort(String filePath) {
        //...
    }
}
public class ConcurrentExternalSort implements ISortAlg {
    @Override
    public void sort(String filePath) {
        //...
    }
}
public class MapReduceSort implements ISortAlg {
    @Override
    public void sort(String filePath) {
        //...
    }
}

public class Sorter {
    private static final long GB = 1000*1000*1000;
    public void sortFile(String filePath){
        //省略校验逻辑
        File file = new File(filePath);
        long fileSize = file.length();
        ISortAlg sortAlg;
        if (fileSize < 6*GB){
            sortAlg = new QuickSort();
        }else if (fileSize < 10*GB){
            sortAlg = new ExternalSort();
        }else if (fileSize < 100*GB){
            sortAlg = new ConcurrentExternalSort();
        }else {
            sortAlg = new MapReduceSort();
        }
        sortAlg.sort(filePath);
    }
}

拆分后,每个类的代码都不会太多,每个类的逻辑都不会太复杂,代码的可读性、可维护性提高。此外,排序算法设计为独立的类,跟具体的业务逻辑解耦,让排序算法可复用,实际是策略模式的第一步,解耦出策略的定义。

实际上,可继续优化,每种排序类都是无状态的,没必要每次使用时,重新创建一个新的对象。所有,可使用工厂模式对对象的创建进行封装,重构后:

public class SortAlgFactory {
    private static final Map<String, ISortAlg> algs = new HashMap<>();
    static {
        algs.put("QuickSort",new QuickSort());
        algs.put("ExternalSort",new ExternalSort());
        algs.put("ConcurrentExternalSort",new ConcurrentExternalSort());
        algs.put("MapReduceSort",new MapReduceSort());
    }
    
    public static ISortAlg getSortAlg(String type){
        if (type == null || type.isEmpty()){
            throw new IllegalArgumentException("type should not be empty");
        }
        return algs.get(type);
    }
}

public class Sorter {
    private static final long GB = 1000*1000*1000;
    public void sortFile(String filePath){
        //省略校验逻辑
        File file = new File(filePath);
        long fileSize = file.length();
        ISortAlg sortAlg;
        if (fileSize < 6*GB){
            sortAlg = SortAlgFactory.getSortAlg("QuickSort");
        }else if (fileSize < 10*GB){
            sortAlg = SortAlgFactory.getSortAlg("ExternalSort");
        }else if (fileSize < 100*GB){
            sortAlg = SortAlgFactory.getSortAlg("ConcurrentExternalSort");
        }else {
            sortAlg = SortAlgFactory.getSortAlg("MapReduceSort");
        }
        sortAlg.sort(filePath);
    }
}

两次重构后,代码已经符合策略模式的代码结构了,但Sorter类的sortFile()函数还是有一堆if-else逻辑,如果特别想一出,可基于查表法,其中algs是表。

public class Sorter {
    private static final long GB = 1000*1000*1000;
    private static final List<AlgRange> algs = new ArrayList<>();
    static {
        algs.add(new AlgRange(0,6*GB, SortAlgFactory.getSortAlg("QuickSort")));
        algs.add(new AlgRange(6*GB,10*GB, SortAlgFactory.getSortAlg("ExternalSort")));
        algs.add(new AlgRange(10*GB,100*GB, SortAlgFactory.getSortAlg("ConcurrentExternalSort")));
        algs.add(new AlgRange(100*GB,Long.MAX_VALUE, SortAlgFactory.getSortAlg("MapReduceSort")));
    }

    public void sortFile(String filePath) {
        //省略校验逻辑
        File file = new File(filePath);
        long fileSize = file.length();
        ISortAlg sortAlg = null;
        for (AlgRange algRange:algs){
            if (algRange.inRange(fileSize)){
                sortAlg = algRange.getAlg();
                break;
            }
        }
        sortAlg.sort(filePath);
    }
    
    private static class AlgRange{
        private long start;
        private long end;
        private ISortAlg alg;

        public AlgRange(long start, long end, ISortAlg alg) {
            this.start = start;
            this.end = end;
            this.alg = alg;
        }

        public ISortAlg getAlg() {
            return alg;
        }
        
        public boolean inRange(long size){
            return size >= start && size < end;
        }
    }
}

即便如此,当添加新的排序算法时,还要修改代码,不完全符合开闭原则,如何满足?通过反射来避免对策略工厂的类的修改。具体:通过一个配置文件或自定义的annotation标注有哪些策略类;策略工厂类读取配置文件或搜索被annotation标注的策略类,反射动态加载这些策略类、创建策略对象;当新添加一个策略的时候,只要将该策略类添加到配置文件或用annotation标注即可。

对sorter来说,同样避免修改,通过将文件大小区间和算法之间的对应关系放到配置文件。当添加新的排序算法,只需要改动配置文件,不用改代码。

责任链模式

模板模式、策略模式和责任链模式的相同作用:复用和扩展。

原理和实现

责任链,Chain Of Responsibility Design Pattern。定义:Avoid coupling the sender of a request to its receive by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. 将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,沿着这条链传递这个请求,直到链上的某个接收对象能处理它为止。

在责任链中,多个处理器(也就是定义中的接收对象)依次处理同一个请求。一个请求先经过A处理器处理,然后把请求传递给B处理器,B处理器处理完后再传递给C处理器,依次类推,形成链。链上的每个处理器各自承担各自的处理职责,所以叫责任链模式。

先看代码实现,有两种。

第一种实现

Handle是所有处理器的抽象父类,handle()是抽象方法,每个具体的处理器类(HandleA、HandleB)的handle()方法的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能,交由后面的处理器来处理(也就是调用successor.handle())。HandleChain是处理器链,从数据结构上看,就是个记录了链头、链尾的链表。其中,记录链尾是为了方便添加处理器。

public abstract class Handler {
    protected Handler successor = null;
    public void setSuccessor(Handler successor){
        this.successor = successor;
    }
    public abstract void handle();
}

public class HandlerA extends Handler {
    @Override
    public void handle() {
        boolean handled = false;
        //...
        if (!handled && successor != null){
            successor.handle();
        }
    }
}

public class HandlerB extends  Handler {
    @Override
    public void handle() {
        boolean handled = false;
        //...
        if (!handled && successor != null){
            successor.handle();
        }
        
    }
}

public class HandlerChain {
    private Handler head = null;
    private Handler tail = null;
    public void addHandler(Handler handler){
        handler.setSuccessor(null);
        
        if (head == null){
            head = handler;
            tail = handler;
            return;
        }
        
        tail.setSuccessor(handler);
        tail = handler;
    }
    public void handle(){
        if (head != null){
            head.handle();
        }
    }
}

public class Application {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handle();
    }
}

上面的代码实现不够优雅,处理器的handle()方法,不仅包含自身的业务逻辑,还包含对下一个处理器的调用,也即是代码中的successor.handle()。很可能其他人在handle()方法调用它,导致代码出现bug。

针对该问题,重构,将调用它的逻辑从具体的处理器类中剥离出来,放到抽象父类中,具体的处理器类只需实现自己的业务逻辑即可。

public abstract class Handler {
    protected Handler successor = null;
    public void setSuccessor(Handler successor){
        this.successor = successor;
    }
    public final void handle(){
        boolean handled = doHandle();
        if (!handled && successor != null){
            successor.handle();
        }
    }
    protected abstract boolean doHandle();
}
public class HandlerA extends Handler {
    @Override
    protected boolean doHandle() {
        boolean handled = false;
        //...
        return handled;
    }
}

public class HandleB extends Handler {
    @Override
    protected boolean doHandle() {
        boolean handled = false;
        //...
        return handled;
    }
}
第二种实现

更加简单,HandlerChain类用数组而非链表保存所有的处理器,并需要在HandlerChain的handle()方法,依次调用每个处理器的handle()方法。

public interface IHandler {
    boolean handle();
}
public class HandlerA implements IHandler {
    @Override
    public boolean handle() {
        boolean handled = false;
        //...
        return handled;
    }
}
public class HandlerB implements IHandler {
    @Override
    public boolean handle() {
        boolean handled = false;
        //...
        return handled;
    }
}

public class HandlerChain {
    private List<IHandler> handlers = new ArrayList<>();
    public void addHandler(IHandler handler){
        this.handlers.add(handler);
    }
    public void handle(){
        for (IHandler handler:handlers){
            boolean handled = handler.handle();
            if (handled){
                break;
            }
        }
    }
}

public class Application {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handle();
    }
}

在GoF的定义中,如果责任链上某个处理器能够处理该请求,不会继续往下传递请求。实际,还有一种变体,就是请求会被所有处理器都处理一遍,不会中途终止。这种变体也有两种实现方式:用链表存储处理器和用数组存储处理器,和上面的两种实现方式类似,只需稍微修改即可。

只给出第一种实现方式:

public abstract class Handler {
    protected Handler successor = null;
    public void setSuccessor(Handler successor){
        this.successor = successor;
    }
    public final void handle(){
        doHandle();
        if (successor != null){
            successor.handle();
        }
    }
    protected abstract void doHandle();
}
public class HandlerA extends Handler {

    @Override
    protected void doHandle() {
        //...
    }
}
public class HandlerB extends Handler {
    @Override
    protected void doHandle() {
        //...
    }
}
public class HandlerChain {
    private Handler head = null;
    private Handler tail = null;
    public void addHandler(Handler handler){
        handler.setSuccessor(null);

        if (head == null){
            head = handler;
            tail = handler;
            return;
        }

        tail.setSuccessor(handler);
        tail = handler;
    }
    public void handle(){
        if (head != null){
            head.handle();
        }
    }
}

public class Application {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handle();
    }
}

责任链模式的应用场景

实际案例,对于支持UGC(User Generated Content,用户生成内容)的应用(如论坛),用户生成的内容可能会包含一些敏感词(如涉黄、广告、反动等词汇),针对该应用场景,可利用责任链模式过滤这些敏感词。

对于包含敏感词的内容,有两种处理方式,一种是直接禁止发布,另一种是给敏感词打马赛克(如用***替换敏感词)之后再发布。第一种符合GoF的责任链的定义,第二种算是它的变体。

只给出第一种方式的代码案例,另外,只给出了代码实现的骨架,具体的敏感词过滤算法并没有给出。

public interface SensitiveWordFilter {
    boolean doFilter(Content content);
}
public class SexyWordFilter implements SensitiveWordFilter {
    @Override
    public boolean doFilter(Content content) {
        boolean legal = true;
        //...
        return legal;
    }
}
public class PoliticalWordFilter implements SensitiveWordFilter {
    @Override
    public boolean doFilter(Content content) {
        boolean legal = true;
        //...
        return legal;
    }
}
public class AdsWordFilter implements SensitiveWordFilter {
    @Override
    public boolean doFilter(Content content) {
        boolean legal = true;
        //...
        return legal;
    }
}
public class SensitiveWordFilterChain {
    private List<SensitiveWordFilter> filters = new ArrayList<>();
    public void addFilter(SensitiveWordFilter filter){
        this.filters.add(filter);
    }
    //return true if content doesn't contain sensitive words
    public boolean filter(Content content){
        for (SensitiveWordFilter filter:filters){
            if (!filter.doFilter(content)){
                return false;
            }
        }
        return true;
    }
}
public class ApplicationDemo {
    public static void main(String[] args) {
        SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();
        filterChain.addFilter(new AdsWordFilter());
        filterChain.addFilter(new SexyWordFilter());
        filterChain.addFilter(new PoliticalWordFilter());
        
        boolean legal = filterChain.filter(new Content());
        if (!legal){
            //不发表
        }else {
            //发表
        }
    }
}

如果像下面这种也能实现,而且更简单,为啥还要用责任链模式呢?

public class SensitiveWordFilter {
    //return true if content doesn't contain sensitive words.
    public boolean filter(Content content){
        if (!filterSexyWord(content)){
            return false;
        }
        if (!filterAdsWord(content)){
            return false;
        }
        if (!filterPoliticalWord(content)){
            return false;
        }
        return true;
    }
    private boolean filterSexyWord(Content content){
        //...
    }
    private boolean filterAdsWord(Content content){
        //...
    }
    private boolean filterPoliticalWord(Content content){
        //...
    }
}

应用设计模式主要是为了应对代码的复杂性,让其满足开闭原则,提高代码的扩展性。

  • 首先看,责任链模式如何应对代码的复杂性

将大块代码逻辑拆分为方法,将大类拆分为小类,第应对代码复杂性的常用方法,应用责任来呢模式,把各个敏感词过滤函数继续拆分,设计为独立的类,进一步简化SensitiveWordFilter类,让其代码不至于过多。

  • 其次,责任链模式如何让代码满足开闭原则,提高代码的扩展性

当要扩展新的过滤算法,如还要过滤特殊符号,按照非责任链模式的代码实现,需要修改SensitiveWordFilter的代码,违反开闭原则,而责任链模式更加优雅,只需添加一个filter类,且通过addFilter()方法将其添加到FilterChain即可,其他代码完全不用改。

即使用责任链模式,当新添加过滤算法,还要修改客户端代码ApplicationDemo,没有完全符合开闭原则。其实细化,可把代码分为两类:框架代码和客户端代码。客户端代码属于使用框架的代码。

此外,利用责任链模式相对于不用的方式,还有好处是,配置过滤算法更灵活,可选择只用某几个过滤算法。

servlet filter

servlet filter是java servlet规范中定义的组件,过滤器,可实现对http请求的过滤功能,如鉴权、限流、记录日志、验证参数等。因为是servlet规范的而一部分,所有tomcat、jetty等web容器都支持过滤器功能。

实际项目如何使用servlet filter?简单示例,添加一个过滤器,只需定义一个实现javax.sevlet.Filter接口的过滤器类,并将其配置到web.xml配置文件中。web容器启动时,读取web.xml的配置,创建过滤器对象。当有请求到来,先经过过滤器,再由servlet处理。

public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //创建filter时自动调用
        //其中filterConfig包含这个filter的配置参数,如name之类
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("拦截客户端发来的请求");
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("拦截发送给客户端的请求");
    }

    @Override
    public void destroy() {
        //销毁filter时自动调用
    }
}

//web.xml的配置
<filter>
    <filter-name>logFilter</filter-name>
    <filter-class>com.ai.doc.chain.filter.v1.LogFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>logFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

添加过滤器非常方便,不需要改任何代码,那servlet如何做到的呢?用责任链模式。责任链的实现包含处理器接口IHandler或抽象类Handler,以及处理器链HandlerChain,对应到servlet filter,javax.servlet.Filter是处理器接口,FilterChain是处理器链,看FilterChain如何实现。以tomcat提供的实现类ApplicationFilterChain为例

public class ApplicationFilterChain implements FilterChain {
    private int pos = 0;//当前执行到哪个filter
    private int n;//filter的个数
    private ApplicationFilterConfig[] filters;
    private Servlet servlet;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (pos < n){
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = filterConfig.getFilter();
            filter.doFilter(request,response,this);
        }else {
            //filter都处理完毕,执行servlet
            servlet.service(request, response);
        }
    }
    
    public void addFilter(ApplicationFilterConfig filterConfig){
        for (ApplicationFilterConfig filter:filters){
            if (filter == filterConfig){
                return;
            }
        }
        if (n==filters.length){
            //扩容
            ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n+n];
            System.arraycopy(filters,0,newFilters,0,n);
            filters = newFilters;
        }
        filters[n++] = filterConfig;
    }
}

ApplicationFilterChain的doFilter()方法的代码实现比较有技巧,实际是个递归调用,可用每个Filter(如LogFilter)的doFilter()的代码实现,直接替换ApplicationFilterChain的第12行代码即可。

@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
  if (pos < n){
    ApplicationFilterConfig filterConfig = filters[pos++];
    Filter filter = filterConfig.getFilter();
    System.out.println("拦截客户端发来的请求");
    chain.doFilter(request,response);//chain就是this
    System.out.println("拦截发送给客户端的响应");
  }else {
    //filter都处理完毕,执行servlet
    servlet.service(request, response);
  }
}

这样实现是为了在一个doFilter()方法中,支持双向拦截,既能拦截客户端发来的请求,也能拦截发给客户端的响应。

spring Interceptor

拦截器,也是用来实现对http请求进行拦截处理。不同之处是,servlet filter是servlet规范的一部分,实现依赖于web容器。spring interceptor是spring mvc框架的一部分,由spring mvc框架来提供实现。客户端的请求,先经过servlet filter,再经过spring interceptor,最后到达具体的业务代码。

如何使用呢?如下,LogInterceptor实现的功能跟刚才的LogFilter一样,只是实现方式不同。LogFilter对请求和响应的拦截在doFilter()实现,而LogInterceptor对请求的拦截在preHandle()中实现,对响应的拦截在postHandle()中实现。

public class LogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        System.out.println("拦截客户端发来的请求");
        return true;//继续后续的处理
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截发给客户端的响应");
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        System.out.println("这里总是被执行");
    }
}

//spring mvc配置文件中的配置
<mvc:interceptors>
    <mvc:interceptor>
        <bean class="com.ai.doc.chain.filter.LogInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

spring Interceptor底层如何实现?基于责任链模式,HandlerExecutionChain类是责任链模式的处理器链。实现相较于tomcat的ApplicationFilterChain,逻辑更清晰,不用递归,因为将请求和响应的拦截,拆分到两个方法中实现。源码如下:

public class HandlerExecutionChain {
    private final Object handler;
    private HandlerInterceptor[] interceptors;
    
    public void addInterceptor(HandlerInterceptor interceptor){
        initInterceptorList().add(interceptor);
    }
    boolean applyPreHandler(HttpServletRequest request, HttpServletResponse response){
        handlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)){
            for (int i=0;i<interceptors.length;i++){
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request,response,this.handler)){
                    triggerAfterCompletion(request,response,null);
                    return false;
                }
            }
        }
        return true;
    }
    
    void applyPostHandle(HttpServletRequest request,HttpServletResponse response){
        handlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)){
            for (int i=interceptors.length-1;i>=0;i--){
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request,response,this.handler,mv);
            }
        }
    }
    
    void triggerAfterCompletion(HttpServletRequest request,HttpServletResponse response){
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)){
            for (int i=interceptors.length-1;i>=0;i--){
                HandlerInterceptor interceptor = interceptors[i];
                try{
                    interceptor.afterCompletion(request,response,this.handler,ex);
                }catch (Throwable ex2){
                    logger.error("HandlerInterceptor.afterCompletion throw exception",ex2);
                }
            }
        }
    }
}

在spring框架,DsipatcherServlet的doDispatch()方法分发请求,在真正的业务逻辑执行前后,执行HandlerExecutionChain的applyPreHandle()和applyPostHandle()方法,用来实现拦截的功能。具体代码很简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值