责任链模式
责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。如Spring Secuurity的处理。
责任链的角色
Handler(抽象处理者)
定义请求处理类,一般为接口或抽象类,也可以理解为定义一个处理责任链。
ConcreteHandler(具体处理者)
具体处理者实现或继承自抽象处理者,实现抽象处理者的方法,在方法中定义不同的处理逻辑,并将处理结果传递给下一个处理者。
有些像策略模式,但不同的是:策略模式根据不同的上下文类型选择不同的策略对象进行不同的逻辑处理,而责任链模式如何执行逻辑、执行什么逻辑是程序员选择的。 请注意下文中的 责任链的定义(Demo.class) Middleware middleware = new ThrottlingMiddleware(2); middleware.linkWith(new UserExistsMiddleware(server)) .linkWith(new RoleCheckMiddleware()); 责任链会将特定行为转换为被称作处理者的独立对象。 在上述示例中, 每个检查步骤都可被抽取为仅有单个方法的类, 并执行检查操作。 请求及其数据则会被作为参数传递给该方法。
如果后期需要加入安全校验、IP重复请求进行密码暴力破解、将用户信息放入缓存等步骤才能满足需求,那么每加入一个步骤,代码就会更加冗余,显然这不是最好的解决方案。
责任链解决方案
你将这些处理者连成一条链。 链上的每个处理者都有一个成员变量来保存对于下一处理者的引用。 如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。除了处理请求外, 处理者还负责沿着链传递请求。 请求会在链上移动, 直至所有处理者都有机会对其进行处理。 处理者可以决定不再沿着链传递请求, 这可高效地取消所有后续处理步骤。
这种需求就类似于Spring Security中的过滤器,一个请求到达后,被过滤链中的每个过滤器逐个进行处理。
在SpringSecrity的VirtualFilterChain源码中:
doFilter接口实现
过滤器在doFilter方法中执行过滤。 每个Filter都可以访问FilterConfig对象,可以从中获取其初始化参数,它是对ServletContext的引用,可以使用该ServletContext加载例如过滤任务所需的资源。 在Web应用程序的部署描述符中配置过滤器 为此设计确定的示例是 1)认证过滤器 2)记录和审核过滤器 3)图像转换滤镜 4)数据压缩过滤器 5)加密过滤器 6)标记化过滤器 7)触发资源访问事件的过滤器 8)XSL / T过滤器 9)Mime型链式过滤器
点击进去Filter的任意一个接口实现:
这里doFilter就是责任链中的其中一个链条(责任链中的每一个链条都实现自同一接口,表示同一个对象,这样才能将2请求传递给下一个),使用责任链模式可以避免请求和响应耦合在一起。
talk is cheaper,show me your code.
使用责任链模式完成认证、授权、验证
Middleware.class
代码语言:javascript复制/**
* Base middleware class.
* @author Liu-PC
* @Description 基础验证接口
*/
public abstract class Middleware {
private Middleware next;
/**
* Builds chains of middleware objects.
*
* @param next
* @return
*/
public Middleware linkWith(Middleware next) {
this.next = next;
return next;
}
/**
* Subclasses will implement this method with concrete checks.
*
* @param email
* @param password
* @return
*/
public abstract boolean check(String email, String password);
protected boolean checkNext(String email, String password) {
if (next == null) {
return true;
}
return next.checkNext(email, password);
}
}
UserExistsMiddleware.class
代码语言:javascript复制/**
* @author Liutx
* @date 2021/1/17 16:51
* @Description 检查用户登录信息
* ConcreteHandler. Checks whether a user with the given credentials exists.
*/
public class UserExistsMiddleware extends Middleware {
private Server server;
public UserExistsMiddleware(Server server) {
this.server = server;
}
/**
*
* @param email
* @param password
* @return 返回true/false 通过条件来控制责任链是否向下进行
*/
@Override
public boolean check(String email, String password) {
if (!server.hasEmail(email)) {
System.out.println("This email is not registered!");
return false;
}
if (!server.isValidPassword(email, password)) {
System.out.println("Wrong password!");
return false;
}
return checkNext(email, password);
}
}
ThrottlingMiddleware.class
代码语言:javascript复制/**
* @author Liutx
* @date 2021/1/17 16:30
* @Description 请求数量(次数)限制
* ConcreteHandler. Checks whether there are too many failed login requests.
*/
public class ThrottlingMiddleware extends Middleware {
public static final long A_MINUTE = 60_000;
/**
* 请求间隔
*/
private int requestPerMinute;
private int request;
private long currentTime;
public ThrottlingMiddleware(int requestPerMinute) {
this.requestPerMinute = requestPerMinute;
this.currentTime = System.currentTimeMillis();
}
@Override
public boolean check(String email, String password) {
if (System.currentTimeMillis() > currentTime A_MINUTE) {
request = 0;
currentTime = System.currentTimeMillis();
}
request ;
if (request > requestPerMinute) {
System.out.println("Request limit exceeded!");
Thread.currentThread().interrupt();
}
return checkNext(email, password);
}
}
RoleCheckMiddleware.class
代码语言:javascript复制/**
* @author Liutx
* @date 2021/1/17 17:20
* @Description 检查用户角色
* ConcreteHandler. Checks a user's role.
*/
public class RoleCheckMiddleware extends Middleware {
@Override
public boolean check(String email, String password) {
if (email.equals("admin@example.com")) {
System.out.println("Hello, admin!");
return true;
}
System.out.println("Hello, user!");
return checkNext(email, password);
}
}
Server.class
代码语言:javascript复制/**
* @author Liutx
* @date 2021/1/17 16:55
* @Description Server
*/
public class Server {
private Map<String, String> users = new HashMap<>();
private Middleware middleware;
/**
* Client passes a chain of object to server. This improves flexibility and
* makes testing the server class easier.
*/
public void setMiddleware(Middleware middleware) {
this.middleware = middleware;
}
/**
* Server gets email and password from client and sends the authorization
* request to the chain.
*/
public boolean logIn(String email, String password) {
if (middleware.check(email, password)) {
System.out.println("Authorization have been successful!");
// Do something useful here for authorized users.
return true;
}
return false;
}
public void register(String email, String password) {
users.put(email, password);
}
public boolean hasEmail(String email) {
return users.containsKey(email);
}
public boolean isValidPassword(String email, String password) {
return users.get(email).equals(password);
}
}
Demo.class
代码语言:javascript复制/**
* @author Liutx
* @date 2021/1/17 17:21
* @Description
*/
public class Demo {
private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
private static Server server;
private static void init() {
server = new Server();
server.register("admin@example.com", "admin_pass");
server.register("user@example.com", "user_pass");
//All checks are linked. Client can build various chains using the same component.
Middleware middleware = new ThrottlingMiddleware(2);
middleware.linkWith(new UserExistsMiddleware(server))
.linkWith(new RoleCheckMiddleware());
// Server gets a chain from client code
server.setMiddleware(middleware);
}
public static void main(String[] args) throws IOException {
init();
boolean success;
do {
System.out.print("Enter email: ");
String email = reader.readLine();
System.out.print("Input password: ");
String password = reader.readLine();
success = server.logIn(email, password);
} while (!success);
}
}
职责链模式的主要优点
- 对象仅需知道该请求会被处理即可,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度
- 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接
- 新增接收者处理也只需要增加链中的一个节点,不需要改动太多.
责任链模式应用
- Spring Security 使用责任链模式,可以动态地添加或删除责任(处理 request 请求)
- Spring AOP 通过责任链模式来管理 Advisor
- Mybatis 中的 Plugin 机制使用了责任链模式,配置各种官方或者自定义的 Plugin,与 Filter 类似,可以在执行 Sql 语句的时候做一些操作