前言:設(shè)計(jì)模式源于生活
責(zé)任鏈基本概念
客戶端發(fā)出一個(gè)請(qǐng)求,鏈上的對(duì)象都有機(jī)會(huì)來(lái)處理這一請(qǐng)求,而客戶端不需要知道誰(shuí)是具體的處理對(duì)象。
這樣就實(shí)現(xiàn)了請(qǐng)求者和接受者之間的解耦,并且在客戶端可以實(shí)現(xiàn)動(dòng)態(tài)的組合職責(zé)鏈。使編程更有靈活性。
責(zé)任鏈定義
使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免了請(qǐng)求的發(fā)送者和接受者之間的耦合關(guān)系。將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有對(duì)象處理它為止。其過(guò)程實(shí)際上是一個(gè)遞歸調(diào)用。
要點(diǎn):
1.有多個(gè)對(duì)象共同對(duì)一個(gè)任務(wù)進(jìn)行處理。
2.這些對(duì)象使用鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),形成一個(gè)鏈,每個(gè)對(duì)象知道自己的下一個(gè)對(duì)象。
3.一個(gè)對(duì)象對(duì)任務(wù)進(jìn)行處理,可以添加一些操作后將對(duì)象傳遞個(gè)下一個(gè)任務(wù)。也可以在此對(duì)象上結(jié)束任務(wù)的處理,并結(jié)束任務(wù)。
4.客戶端負(fù)責(zé)組裝鏈?zhǔn)浇Y(jié)構(gòu),但是客戶端不需要關(guān)心最終是誰(shuí)來(lái)處理了任務(wù)。
責(zé)任鏈優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
職責(zé)鏈模式的最主要功能就是:動(dòng)態(tài)組合,請(qǐng)求者和接受者解耦。
請(qǐng)求者和接受者松散耦合:請(qǐng)求者不需要知道接受者,也不需要知道如何處理。每個(gè)職責(zé)對(duì)象只負(fù)責(zé)自己的職責(zé)范圍,其他的交給后繼者。各個(gè)組件間完全解耦。
動(dòng)態(tài)組合職責(zé):職責(zé)鏈模式會(huì)把功能分散到單獨(dú)的職責(zé)對(duì)象中,然后在使用時(shí)動(dòng)態(tài)的組合形成鏈,從而可以靈活的分配職責(zé)對(duì)象,也可以靈活的添加改變對(duì)象職責(zé)。
缺點(diǎn):
產(chǎn)生很多細(xì)粒度的對(duì)象:因?yàn)楣δ芴幚矶挤稚⒌搅藛为?dú)的職責(zé)對(duì)象中,每個(gè)對(duì)象功能單一,要把整個(gè)流程處理完,需要很多的職責(zé)對(duì)象,會(huì)產(chǎn)生大量的細(xì)粒度職責(zé)對(duì)象。
不一定能處理:每個(gè)職責(zé)對(duì)象都只負(fù)責(zé)自己的部分,這樣就可以出現(xiàn)某個(gè)請(qǐng)求,即使把整個(gè)鏈走完,都沒(méi)有職責(zé)對(duì)象處理它。這就需要提供默認(rèn)處理,并且注意構(gòu)造鏈的有效性。
責(zé)任鏈應(yīng)用場(chǎng)景
1.java過(guò)濾器的底層實(shí)現(xiàn)filter(例如:請(qǐng)求進(jìn)來(lái)到服務(wù)端,filter會(huì)經(jīng)過(guò)參數(shù)過(guò)濾、session過(guò)濾、表單過(guò)濾、請(qǐng)求頭過(guò)濾等等)
2.erp審批流程(例如:項(xiàng)目組長(zhǎng)->項(xiàng)目經(jīng)理->項(xiàng)目總監(jiān)->人事經(jīng)理)
3.多重if判斷問(wèn)題
責(zé)任鏈模式環(huán)境流程圖
1.抽象處理者(Handler)角色:定義出一個(gè)處理請(qǐng)求的接口。如果需要,接口可以定義 出一個(gè)方法以設(shè)定和返回對(duì)下家的引用。這個(gè)角色通常由一個(gè)Java抽象類或者Java接口實(shí)現(xiàn)。上圖中Handler類的聚合關(guān)系給出了具體子類對(duì)下家的引用,抽象方法handleRequest()規(guī)范了子類處理請(qǐng)求的操作。
2.具體處理者(ConcreteHandler)角色:具體處理者接到請(qǐng)求后,可以選擇將請(qǐng)求處理掉,或者將請(qǐng)求傳給下家。由于具體處理者持有對(duì)下家的引用,因此,如果需要,具體處理者可以訪問(wèn)下家
接下來(lái)將通過(guò)兩種基于責(zé)任鏈設(shè)計(jì)模式實(shí)現(xiàn)網(wǎng)關(guān)權(quán)限方式
兩種方式POM都通用
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>responsiit_model</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<!-- <scope>provided</scope>-->
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!-- mysql 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
</dependencies>
</project>
第一種:基于內(nèi)存,模板模式+工廠模式+責(zé)任鏈模式實(shí)現(xiàn)網(wǎng)關(guān)權(quán)限框架
抽象handler
/**
* @Author: klm
* @Description: 抽象網(wǎng)關(guān)
*/
public abstract class GatewayHandler {
abstract public void service();
protected GatewayHandler nextGatewayHandler;
public void setNextGatewayHandler(GatewayHandler gatewayHandler) {
this.nextGatewayHandler = gatewayHandler;
}
protected void nextService() {
if (nextGatewayHandler != null) {
nextGatewayHandler.service();
}
}
}
具體API限流handler
/**
* @Author: klm
* @Description: API接口限流Handler
*/
@Slf4j
public class CurrentLimitHandler extends GatewayHandler {
@Override
public void service() {
log.info("第一關(guān):API接口限流");
nextService();
}
}
具體黑名單限流Handler
/**
* @Author: klm
* @Description: 黑名單限流Handler
*/
@Slf4j
public class BlacklistHandler extends GatewayHandler {
@Override
public void service() {
log.info("第二關(guān):黑名單限流");
nextService();
}
}
具體攔截用戶信息Handler
/**
* @Author: klm
* @Description: 攔截用戶信息Handler
*/
@Slf4j
public class ConversationHandler extends GatewayHandler {
@Override
public void service() {
log.info("第三關(guān):攔截用戶信息");
}
}
工廠:初始化對(duì)象并設(shè)置引用執(zhí)行順序
/**
* @Author: klm
* @Description: 工廠
*/
public class FactoryHandler {
public static GatewayHandler getCurrentLimitHandler() {
//初始化對(duì)象
GatewayHandler currentLimitHandler = new CurrentLimitHandler();
GatewayHandler blacklistHandler = new BlacklistHandler();
GatewayHandler conversationHandler = new ConversationHandler();
//設(shè)置引用,執(zhí)行順序設(shè)置
currentLimitHandler.setNextGatewayHandler(blacklistHandler);
blacklistHandler.setNextGatewayHandler(conversationHandler);
return currentLimitHandler;
}
}
測(cè)試類,執(zhí)行一波,看看效果
public class test {
public static void main(String[] args) {
GatewayHandler currentLimitHandler = FactoryHandler.getCurrentLimitHandler();
currentLimitHandler.service();
}
}
代碼還有需要改進(jìn)的地方
改進(jìn)點(diǎn):在每個(gè)handler處理器里面,都會(huì)要執(zhí)行一次nextService()方法,執(zhí)行下一個(gè)Handler,但是這種行為比較冗余,low所以我們改造一波
抽象Handler改造點(diǎn)
抽象Handler改造點(diǎn)
/**
* @Author: klm
* @Description: 抽象網(wǎng)關(guān)
*/
public abstract class GatewayHandler {
//改造點(diǎn)1:將訪問(wèn)修飾符改為只有子類才能使用,不再進(jìn)行公開(kāi)使用
protected abstract void service();
protected GatewayHandler nextGatewayHandler;
public void setNextGatewayHandler(GatewayHandler gatewayHandler) {
this.nextGatewayHandler = gatewayHandler;
}
//改造點(diǎn)3:將執(zhí)行下一次操作的行為,改為我們的入口操作
protected void nextService() {
if (nextGatewayHandler != null) {
// nextGatewayHandler.service();
nextGatewayHandler.execute();
}
}
//改造點(diǎn)2:抽出一個(gè)模板:用于外部調(diào)用的入口
public void execute() {
service();
nextService();
}
}
具體執(zhí)行的handler,其他的handler也需要去掉冗余,我這里就不顯示
/**
* @Author: klm
* @Description: 黑名單限流Handler
*/
@Slf4j
public class BlacklistHandler extends GatewayHandler {
@Override
public void service() {
log.info("第二關(guān):黑名單限流");
//改造點(diǎn)4:注釋掉,去掉冗余操作
// nextService();
}
}
測(cè)試類
/**
* @Author: klm
* @Description:
*/
public class test {
public static void main(String[] args) {
GatewayHandler currentLimitHandler = FactoryHandler.getCurrentLimitHandler();
// currentLimitHandler.service();
currentLimitHandler.execute();
}
}
第二種:基于Mysql,模板模式+責(zé)任鏈模式實(shí)現(xiàn)網(wǎng)關(guān)權(quán)限框架
db sql文件
CREATE TABLE `gateway_handler` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`handler_name` varchar(32) DEFAULT NULL COMMENT 'handler名稱',
`handler_id` varchar(32) DEFAULT NULL COMMENT 'handler主鍵id',
`prev_handler_id` varchar(32) DEFAULT NULL,
`next_handler_id` varchar(32) DEFAULT NULL COMMENT '下一個(gè)handler',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COMMENT='權(quán)限表';
-- ----------------------------
-- Records of gateway_handler
-- ----------------------------
INSERT INTO `gateway_handler` VALUES ('16', 'Api接口限流', 'currentLimitHandler', null, 'blacklistHandler');
INSERT INTO `gateway_handler` VALUES ('17', '黑名單攔截', 'blacklistHandler', 'currentLimitHandler', 'conversationHandler');
INSERT INTO `gateway_handler` VALUES ('18', '會(huì)話驗(yàn)證', 'conversationHandler', 'blacklistHandler', null);
-- 查詢第一個(gè)
SELECT id as id ,handler_name as handlerName,handler_id as handlerId,prev_handler_id as prevHandlerId,next_handler_id as nextHandlerId FROM gateway_handler gh where gh.prev_handler_id is null;
SELECT id as id ,handler_name as handlerName,handler_id as handlerId,prev_handler_id as prevHandlerId,next_handler_id as nextHandlerId FROM gateway_handler gh where gh.handler_id = 'blacklistHandler';
entity類
/**
* @Author: klm
* @Description:
*/
@Data
public class GatewayHandlerEntity implements Serializable, Cloneable {
/**
* 主鍵ID
*/
private Integer id;
/**
* handler名稱
*/
private String handlerName;
/**
* handler主鍵id
*/
private String handlerId;
/**
* 下一個(gè)handler
*/
private String nextHandlerId;
}
mapper類
/**
* @Author: klm
* @Description:
*/
@Repository
public interface GatewayHandlerMapper {
/**
* 獲取第一個(gè)執(zhí)行的handler
*
* @return
*/
@Select("SELECT id as id ,handler_name as handlerName,handler_id as handlerId,prev_handler_id as prevHandlerId," +
"next_handler_id as nextHandlerId FROM gateway_handler gh " +
"where gh.prev_handler_id is null;")
GatewayHandlerEntity getFirstGatewayHandler();
/**
* 獲取下一個(gè)執(zhí)行的handler
*
* @param handlerId
* @return
*/
@Select("SELECT id as id ,handler_name as handlerName,handler_id as handlerId,prev_handler_id as prevHandlerId," +
"next_handler_id as nextHandlerId FROM gateway_handler gh " +
"where gh.handler_id = #{handlerId};")
GatewayHandlerEntity getNextGatewayHandler(@Param("handlerId") String handlerId);
}
service類
/**
* @Author: klm
* @Description:
*/
public interface GatewayHandlerService {
/**
* db責(zé)任鏈模式
*
* @return
*/
GatewayHandler getGatewayHandler();
}
service實(shí)現(xiàn)類
/**
* @Author: klm
* @Description:
*/
@Service
@Slf4j
public class GatewayHandlerServiceImpl implements GatewayHandlerService {
@Autowired
private GatewayHandlerMapper gatewayHandlerMapper;
@Override
public GatewayHandler getGatewayHandler() {
//獲取頭節(jié)點(diǎn)
GatewayHandlerEntity firstGatewayHandler = gatewayHandlerMapper.getFirstGatewayHandler();
if (Objects.isNull(firstGatewayHandler)) {
log.info("未配置頭節(jié)點(diǎn),請(qǐng)注意");
return null;
}
String handlerId = firstGatewayHandler.getHandlerId();
if (StringUtils.isEmpty(handlerId)) {
log.info("未配置頭節(jié)點(diǎn),請(qǐng)注意");
return null;
}
//從容器中獲取bean對(duì)象
GatewayHandler bean = SpringUtils.getBean(handlerId, GatewayHandler.class);
if (Objects.isNull(bean)) {
log.info("容器中未找到");
return null;
}
//下一節(jié)點(diǎn)名稱
String nextHandlerId = firstGatewayHandler.getNextHandlerId();
//記錄當(dāng)前對(duì)象
GatewayHandler currentBean = bean;
while (!StringUtils.isEmpty(nextHandlerId)) {
//從容器獲取下一個(gè)執(zhí)行handler
GatewayHandler nextBean = SpringUtils.getBean(nextHandlerId, GatewayHandler.class);
if (Objects.isNull(nextBean)) {
break;
}
//查詢db獲取下一個(gè)handlerid
GatewayHandlerEntity nextGatewayHandler = gatewayHandlerMapper.getNextGatewayHandler(nextHandlerId);
if (Objects.isNull(nextGatewayHandler)) {
break;
}
nextHandlerId = nextGatewayHandler.getNextHandlerId();
currentBean.setNextGatewayHandler(nextBean);
currentBean = nextBean;
}
return bean;
}
}
抽象handler
/**
* @Author: klm
* @Date: 2020/8/5 09:41
* @Description:
*/
public abstract class GatewayHandler {
protected abstract void doService();
protected GatewayHandler nextGatewayHandler;
public void setNextGatewayHandler(GatewayHandler gatewayHandler) {
this.nextGatewayHandler = gatewayHandler;
}
protected void nextService() {
if (nextGatewayHandler != null) {
// nextGatewayHandler.doService();
nextGatewayHandler.execute();
}
}
public void execute() {
doService();
nextService();
}
}
具體API接口限流Handler
/**
* @Author: klm
* @Description:
*/
@Component
@Slf4j
public class CurrentLimitHandler extends GatewayHandler {
@Override
public void doService() {
log.info("第一關(guān):API接口限流");
// nextService();
}
}
具體黑名單限流Handler
/**
* @Author: klm
* @Description:
*/
@Component
@Slf4j
public class BlacklistHandler extends GatewayHandler {
@Override
public void doService() {
log.info("第二關(guān):黑名單限流");
// nextService();
}
}
具體攔截用戶信息Handler
/**
* @Author: klm
* @Description:
*/
@Component
@Slf4j
public class ConversationHandler extends GatewayHandler {
@Override
public void doService() {
log.info("第三關(guān):攔截用戶信息");
}
}
SpringUtils類
/**
* @Author: klm
* @Description:
*/
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
//獲取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通過(guò)name獲取 Bean.
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
//通過(guò)class獲取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
//通過(guò)name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
controller類
/**
* @Author: klm
* @Description:
*/
@RestController
public class GatewayHandlerController {
@Autowired
private GatewayHandlerService gatewayHandlerService;
@GetMapping("/test")
public void test() {
GatewayHandler gatewayHandler = gatewayHandlerService.getGatewayHandler();
gatewayHandler.execute();
}
}
application.yml文件
###服務(wù)啟動(dòng)端口號(hào)
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/responsibility?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
####打印MyBatias日志
logging:
level:
### 開(kāi)發(fā)環(huán)境使用DEBUG 生產(chǎn)環(huán)境info或者error
com.xuyu.mapper: DEBUG
訪問(wèn)網(wǎng)頁(yè):執(zhí)行l(wèi)ocalhost:8080/test
執(zhí)行效果圖