理解责任链模式

Posted by AceKei on June 24, 2019

是什么

策略模式属于行为型模式。创建多个对象,使这些对象形成一条链,并沿着这条链传递请求,直到链上的某一个对象决定处理此请求。

优缺点

使程序结构更加灵活,扩展性更强。

优点

  1. 降低耦合度,客户端不需要知道这个请求被谁处理了,而处理者也不需要知道各个处理者之间的传递关系。
  2. 扩展性强,新增处理者的时候,只需要继承父处理者,然后重写或者延用父处理者的业务逻辑方法。

缺点

  1. 请求会从链的头部一直向下传递,直到有处理者处理,那么有可能因为链过长导致系统性能减低。
  2. 请求可能是递归传递的。

怎么用

问题重现

现在需要为从客户端发起的请求做一个 referer 的防盗链,这个防盗链分为本地、测试和生产。每一种环境 referer 处理的正则表达式都不一样。

一般想法的话,是在一个类中写出所有的 正则表达式 ,然后循环判断。但是当新增一个环境的时候,就需要修改这个类,这样子 不符合开闭原则

一般代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.wsk.gateway.config;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @description 描述
 */
public class Simple {

    private static final Pattern DEV = Pattern.compile("^https?://(localhost|127.0.0.1)(/|$)");

    private static final Pattern TEST = Pattern.compile("^https?://(test.wsk1103.com|192.168.1.1)(/|$)");

    private static final Pattern REP = Pattern.compile("^https?://(wsk1103.com|192.168.2.1)(/|$)");

    public static boolean doChain(String referer) {
        return DEV.matcher(referer).find() || TEST.matcher(referer).find() || REP.matcher(referer).find();
    }

}

当新增一个环境的时候,就需要改动这个代码。

使用责任链模式

设计抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.wsk.gateway.config;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description 抽象的处理器
 */
public abstract class AbstractRefererAction implements Comparable{
    /**
     * 链的优先级
     */
    private int num;

    /**
     * 正则表达式
     */
    private Pattern pattern;

    protected AbstractRefererAction(int num, Pattern pattern) {
        this.num = num;
        this.pattern = pattern;
    }

    /**
     * 处理逻辑,子类只需要提供正则表达式和优先级即可
     * @param referer
     * @return
     */
    public boolean doChain(String referer) {
        if (pattern == null) {
            return false;
        }
        return pattern.matcher(referer).find();
    }

    @Override
    public int compareTo(Object o) {
        if (o instanceof AbstractRefererAction) {
            return Integer.compare(this.num, ((AbstractRefererAction) o).getNum());
        } else {
            throw new IllegalArgumentException();
        }
    }

    public int getNum() {
        return this.num;
    }

}

实现抽象类-开发,测试,生产

配合 springbean 管理,使所有实现类实例化并且单例化

开发环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.wsk.gateway.config;

import org.springframework.stereotype.Service;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description 开发环境
 */
@Service
public class DevRefererAction extends AbstractRefererAction {

    private static final Pattern PATTERN = Pattern.compile("^https?://(localhost|127.0.0.1)(/|$)");

    private DevRefererAction() {
        super(999, PATTERN);
    }
}

测试环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.wsk.gateway.config;

import org.springframework.stereotype.Service;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description 描述
 */
@Service
public class TestRefererAction extends AbstractRefererAction {

    private static final Pattern PATTERN = Pattern.compile("^https?://(test.wsk1103.com|192.168.1.1)(/|$)");

    private TestRefererAction() {
        super(100, PATTERN);
    }

}

生产环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.wsk.gateway.config;

import org.springframework.stereotype.Service;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description 描述
 */
@Service
public class RepRefererAction extends AbstractRefererAction {

    private static final Pattern PATTERN = Pattern.compile("^https?://(wsk1103.com|192.168.2.1)(/|$)");

    private RepRefererAction() {
        super(-999, PATTERN);
    }

}

spring 全局bean管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.wsk.gateway.config;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description spring 全局bean管理
 */
public class SpringContext implements ApplicationContextAware {

    /**
     * Spring应用上下文环境
     */
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContext.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

虽然代码看起来是增多了,但是增强了扩展性。后续的开发不需要修改原来的代码,只需要实现相应的接口并且定义正则表达式即可。

还有一种更方便的实现,使用配置中心,例如携程的 Apollo 等等,然后将所有正则表达式发布到服务器,并且解析实现。

java中的应用

java.util.logging.Logger#log()
Apache Commons Chain
javax.servlet.Filter#doFilter()

最后

  1. spring 的过滤器 filter 就是使用了责任链模式- ApplicationFilterChain

参考:https://github.com/iluwatar/java-design-patterns/tree/master/chain