干貨來襲!5 分鐘學會快速實現責任鏈,效率直接拉滿!
在軟件開發的 “戰場” 上,我們常常會遭遇各種棘手的難題。其中,如何優雅地解耦請求的發送者與接收者,同時實現請求在多個處理者之間靈活、動態地流轉,一直是困擾眾多開發者的 “攔路虎”。這時,責任鏈模式如同一位 “救星”,強勢登場,為我們打開了高效處理請求的大門。在日常開發中,我們頻繁使用的過濾器、攔截器等功能,其背后都離不開責任鏈模式的強力支撐。然而,很多開發者一聽到要自己動手實現一個責任鏈,就會眉頭緊皺,仿佛面對一座難以逾越的高山。
別害怕!今天就給大家帶來一款堪稱 “神器” 的工具 ——apache commons-chain。有了它,實現責任鏈將變得輕而易舉,就像擁有了一把萬能鑰匙,能夠輕松解鎖各種復雜的場景。不管你是經驗豐富的開發老手,還是剛剛踏入編程領域的新手小白,這款工具都能讓你在實現責任鏈的過程中事半功倍!
一、commons-chain:責任鏈實現的 “超級外掛”
commons-chain 是 apache commons 大家族中的重要一員,它就像是一個功能強大的 “超級外掛”,專門用于創建和管理命令對象。這些命令對象可不是普通的角色,它們各自擁有獨特的使命,就像一個個訓練有素的特種兵,隨時準備執行各種高難度任務。借助 commons-chain,開發者可以將多個命令以鏈式的方式巧妙地串聯起來,每個命令專注于完成自己的任務,完成后便如同接力賽中的運動員一樣,精準地將控制權傳遞給下一個命令,從而構建出一條高效、靈活的責任鏈。
二、commons-chain 核心接口:構建責任鏈的 “秘密武器”
Command 接口: 它是責任鏈中每一個具體任務的執行者,如同戰場上沖鋒陷陣的士兵。這個接口只有一個核心方法boolean execute(Context context),別看它簡單,卻蘊含著巨大的能量。當該方法返回true時,就像是吹響了停止的號角,責任鏈中的其他命令將停止執行;而返回false時,則意味著責任鏈可以繼續前進,執行后續的命令。不過,一旦執行過程中出現異常,就如同遭遇了突發狀況,責任鏈也會被迫中斷,后續命令將無法執行。
Chain 接口: 它是責任鏈的組織者和管理者,就像是一位經驗豐富的指揮官,負責將各個命令有序地編排在一起。所有要在責任鏈中執行的命令,都需要先添加到這個 Chain 中。而且,它還實現了 Command 接口,這意味著 Chain 不僅能夠組織命令,自身也具備執行命令的能力,可謂是 “身兼數職”。
Context 接口: 它是命令執行的上下文環境,如同一個信息共享的 “中央倉庫”,在整個責任鏈中傳遞著關鍵信息。各個命令可以通過它訪問和修改共享數據,實現不同命令之間的協同作戰。此外,它還實現了 Map 接口,就像一個靈活多變的百寶箱,能夠存儲各種類型的信息,方便命令隨時取用。
Filter 接口: 它是一種特殊的 Command,擁有更強大的功能,就像是特種兵中的精英。除了繼承 Command 的execute方法外,它還新增了一個boolean postprocess(Context context, Exception exception)方法。commons chain 會在執行完 Filter 的execute方法之后,無論責任鏈以何種方式結束,都會接著執行postprocess方法。而且,execute方法的執行順序與 Filter 在 Chain 中出現的位置一致,而postprocess方法的執行順序則與之相反。postprocess方法的返回值根據異常對象來決定,有異常時返回false,否則返回true,這樣就能確保在最后能及時拋出異常,方便我們進行錯誤排查和處理。
Catalog 接口: 它就像是一個命令的 “智能索引”,是一個邏輯命名的命令(或 Chain)集合。通過使用它,Command 的調用者無需了解具體實現 Command 的類名,只需通過名字就能像在圖書館中查找書籍一樣,輕松獲取所需的 Command 實例,大大提高了開發效率。
三、使用示例:手把手教你快速上手
1、項目中的pom引入commons-chain gav
<dependency>
<groupId>commons-chain</groupId>
<artifactId>commons-chain</artifactId>
<version>${commons-chain.version}</version>
</dependency>
2、實現command
這里以 Filter 為例,為大家展示如何實現具體的命令邏輯。下面分別實現了判斷飛翔、跳躍和跑步能力的命令,每個命令都有自己獨特的功能和邏輯:
public class FlyCommand implements Filter {
/**
* 執行當前操作
*
* 此方法應由具體的操作實現類來覆蓋,以提供具體的操作邏輯
* 它負責根據給定的上下文執行業務邏輯,并返回一個布爾值表示執行結果
*
* @param context 執行操作的上下文,包含操作所需的信息和環境設置
* @return boolean 表示操作執行的結果,true表示成功,false表示失敗
* @throws Exception 如果執行過程中發生錯誤,拋出異常
*/
@Override
public boolean execute(Context context) throws Exception {
Object fly = context.get("flyEnabled");
boolean flyEnabled = fly != null && "true".equalsIgnoreCase(fly.toString());
System.out.println("擁有飛翔的能力:" + flyEnabled);
return flyEnabled;
}
/**
* 在飛行能力判斷完成后進行后處理
*
*
* @param context 上下文環境,可能包含執行判斷飛行能力前后的相關環境信息
* @param exception 在判斷飛行能力過程中可能發生的異常,如果沒有異常,則為null
* @return boolean 表示飛行能力判斷是否成功完成如果沒有異常,則返回true,否則返回false
*/
@Override
public boolean postprocess(Context context, Exception exception) {
System.out.println("判定飛翔能力完畢...");
if(exception != null){
System.out.println("執行異常:" + exception.getMessage());
}
return exception == null;
}
}
public class JumpCommand implements Filter {
/**
* 執行當前操作
*
* 此方法應由具體的操作實現類來覆蓋,以提供具體的操作邏輯
* 它負責根據給定的上下文執行業務邏輯,并返回一個布爾值表示執行結果
*
* @param context 執行操作的上下文,包含操作所需的信息和環境設置
* @return boolean 表示操作執行的結果,true表示成功,false表示失敗
* @throws Exception 如果執行過程中發生錯誤,拋出異常
*/
@Override
public boolean execute(Context context) throws Exception {
Object jump = context.get("jumpEnabled");
boolean jumpEnabled = jump != null && "true".equalsIgnoreCase(jump.toString());
System.out.println("擁有跳躍的能力:" + jumpEnabled);
return jumpEnabled;
}
/**
* 在跳躍能力判斷完成后進行后處理
*
*
* @param context 上下文環境,可能包含執行判斷跳躍能力前后的相關環境信息
* @param exception 在判斷跳躍能力過程中可能發生的異常,如果沒有異常,則為null
* @return boolean 表示跳躍能力判斷是否成功完成如果沒有異常,則返回true,否則返回false
*/
@Override
public boolean postprocess(Context context, Exception exception) {
System.out.println("判定跳躍能力完畢...");
if(exception != null){
System.out.println("執行異常:" + exception.getMessage());
}
return exception == null;
}
}
public class RunCommand implements Filter {
/**
* 執行當前操作
*
* 此方法應由具體的操作實現類來覆蓋,以提供具體的操作邏輯
* 它負責根據給定的上下文執行業務邏輯,并返回一個布爾值表示執行結果
*
* @param context 執行操作的上下文,包含操作所需的信息和環境設置
* @return boolean 表示操作執行的結果,true表示成功,false表示失敗
* @throws Exception 如果執行過程中發生錯誤,拋出異常
*/
@Override
public boolean execute(Context context) throws Exception {
Object run = context.get("runEnabled");
boolean runEnabled = run != null && "true".equalsIgnoreCase(run.toString());
System.out.println("擁有跑步的能力:" + runEnabled);
return runEnabled;
}
/**
* 在跑步能力判斷完成后進行后處理
*
*
* @param context 上下文環境,可能包含執行判斷跑步能力前后的相關環境信息
* @param exception 在判斷跑步能力過程中可能發生的異常,如果沒有異常,則為null
* @return boolean 表示跑步能力判斷是否成功完成如果沒有異常,則返回true,否則返回false
*/
@Override
public boolean postprocess(Context context, Exception exception) {
System.out.println("判定跑步能力完畢...");
if(exception != null){
System.out.println("執行異常:" + exception.getMessage());
}
return exception == null;
}
}
3、將創建的command加入到chain中
@Slf4j
public class ChainTest {
private Context data;
@Before
public void prepareData(){
data = new ContextBase();
data.put("flyEnabled", "false");
data.put("jumpEnabled", "false");
data.put("runEnabled", "false");
}
@Test
public void testChain(){
Chain chain = new ChainBase();
chain.addCommand(new FlyCommand());
chain.addCommand(new JumpCommand());
chain.addCommand(new RunCommand());
try {
chain.execute(data);
} catch (Exception e) {
log.error("執行鏈路失敗",e);
}
}
}
運行并查看結果
擁有飛翔的能力:false
擁有跳躍的能力:false
擁有跑步的能力:false
判定跑步能力完畢...
判定跳躍能力完畢...
判定飛翔能力完畢...
a、 如果要根據名字獲取command,則可以利用catalog
@Test
public void testCatalog(){
Catalog catalog = new CatalogBase();
catalog.addCommand("fly", new FlyCommand());
catalog.addCommand("jump", new JumpCommand());
catalog.addCommand("run", new RunCommand());
Iterator names = catalog.getNames();
while (names.hasNext()) {
try {
String name = (String) names.next();
catalog.getCommand(name).execute(data);
} catch (Exception e) {
log.error("執行鏈路失敗",e);
}
}
}
運行并查看結果
擁有飛翔的能力:false
擁有跑步的能力:false
擁有跳躍的能力:false
四、與 spring 集成:強強聯合,打造無敵組合
1、自定義激活chain注解
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ChainRegistrar.class)
public @interface EnableChain {
/**
* Base packages to scan for annotated components.
* attribute.
*/
String[] basePackages() default {};
}
2、自定義chain掃描器
public class ChainClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public ChainClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
String[] interfaceNames = metadata.getInterfaceNames();
return interfaceNames.length > 0
&& Arrays.stream(interfaceNames)
.anyMatch(interfaceName ->
Command.class.getName().equals(interfaceName)
|| Filter.class.getName().equals(interfaceName));
}
}
3、將掃描的command注冊到spring容器中
public class ChainRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableChain.class.getName());
String[] basePackages = (String[]) annotationAttributes.get("basePackages");
System.out.println("basePackages:" + Arrays.toString(basePackages));
ChainClassPathBeanDefinitionScanner chainClassPathBeanDefinitionScanner = new ChainClassPathBeanDefinitionScanner(registry);
chainClassPathBeanDefinitionScanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
chainClassPathBeanDefinitionScanner.scan(basePackages);
}
}
4、編寫一個chain聚合器
@RequiredArgsConstructor
public class CommandDelegete implements Command, InitializingBean {
private final ObjectProvider<List<Command>> commandObjectProvider;
private final Chain chain = new ChainBase();
@Override
public boolean execute(Context context) throws Exception {
return chain.execute(context);
}
@Override
public void afterPropertiesSet() throws Exception {
List<Command> commands = commandObjectProvider.getIfAvailable();
if(CollectionUtil.isNotEmpty(commands)){
commands.forEach(chain::addCommand);
}
}
}
5、將chain聚合器注入到spring中
@Configuration
public class CommandAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CommandDelegete commandDelegete(ObjectProvider<List<Command>> commandObjectProvider){
return new CommandDelegete(commandObjectProvider);
}
}
6、如何使用
@RequiredArgsConstructor
public class UserHandlerInterceptor implements HandlerInterceptor {
private final CommandDelegete commandDelegete;
@Autowired
private ServletContext servletContext;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Context context = new ServletWebContext(servletContext, request, response);
commandDelegete.execute(context);
return true;
}
}
總結
commons-chain 就像是一位貼心的開發伙伴,在實現責任鏈的道路上,它能夠為我們節省大量的時間和精力,讓我們的開發工作變得更加輕松高效。盡管它已經進入維護期,但其中蘊含的設計思想和編程技巧依然值得我們深入學習和借鑒。如果你對 commons-chain 感興趣,想要深入了解更多細節,可以訪問官網:
https://commons.apache.org/dormant/commons-chain/cookbook.html
demo鏈接
為了方便大家學習和實踐,這里提供了完整的 demo 鏈接:
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-chain
如果這篇文章對你有所幫助,不妨點贊、轉發給身邊的小伙伴,讓更多的開發者受益于這個強大的工具!同時,也歡迎大家關注我們,獲取更多實用的技術干貨!

浙公網安備 33010602011771號