cola擴展點使用和設計初探 封裝變化,可靈活應對程式的需求變化。 擴展點使用 步驟: 定義擴展點介面,類型可以是校驗器,轉換器,實體; 必須以ExtPt結尾,表示一個擴展點。 比如,我定義一個雲樞的組織結構的擴展點介面,消息發送擴展點,二開擴展點,webapi的rest介面擴展點點。 定義擴展點接 ...
cola擴展點使用和設計初探
封裝變化,可靈活應對程式的需求變化。
擴展點使用
步驟:
定義擴展點介面,類型可以是校驗器,轉換器,實體; 必須以ExtPt結尾,表示一個擴展點。
比如,我定義一個雲樞的組織結構的擴展點介面,消息發送擴展點,二開擴展點,webapi的rest介面擴展點點。
定義擴展點介面
package com.authine.web.cola.domain.customer;
import com.alibaba.cola.extension.ExtensionPointI;
import com.authine.web.cola.dto.domainmodel.Department;
import java.util.List;
/**
* @author carter
* create_date 2020/5/25 14:25
* description 定義擴展點介面,對組織機構的某些方法。
*/
public interface OrganizationExtPt extends ExtensionPointI {
/**
* 根據corpId查詢企業下所有部門
*
* @param corpId 企業編號
* @param includeDelete 是否包含刪除的部門
* @return 部門
*/
List<Department> getDepartmentsByCorpId(String corpId, Boolean includeDelete);
}
比如業務擴展分為釘釘,微信:
這裡基於擴展理論(x,y);
即通過 業務,用例,場景得到擴展點的key, 那後擴展類就是針對實際的業務場景的業務處理代碼;
釘釘場景擴展點實現
package com.authine.web.cola.domain.customer.extpt;
import com.alibaba.cola.extension.Extension;
import com.authine.web.cola.dto.domainmodel.Department;
import com.authine.web.cola.domain.customer.OrganizationExtPt;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.List;
/**
* @author carter
* create_date 2020/5/25 14:32
* description 企業部門在通過corpId獲取部門列表的場景下,釘釘的擴展
*/
@Extension(bizId = "organize",useCase = "getByCorpId",scenario = "dingTalk")
@Slf4j
public class DingTalkOrganizationExt implements OrganizationExtPt {
@Override
public List<Department> getDepartmentsByCorpId(String corpId, Boolean includeDelete) {
log.info("在組織結構業務,通過企業編號獲取部門列表的用例,在釘釘的場景下業務的實現處理方式");
log.info("通過釘釘的配置信息和API獲取得到組織信息,並組裝成雲樞識別的部門信息");
Department department = new Department();
department.setName("dingTalk");
department.setCorpId(corpId);
return Collections.singletonList(department);
}
}
企業微信擴展點實現
package com.authine.web.cola.domain.customer.extpt;
import com.alibaba.cola.extension.Extension;
import com.authine.web.cola.dto.domainmodel.Department;
import com.authine.web.cola.domain.customer.OrganizationExtPt;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.List;
/**
* @author carter
* create_date 2020/5/25 15:05
* description 企業微信的擴展點實現
*/
@Extension(bizId = "organize",useCase = "getByCorpId",scenario = "wechat")
@Slf4j
public class WechatOrganizationExt implements OrganizationExtPt {
@Override
public List<Department> getDepartmentsByCorpId(String corpId, Boolean includeDelete) {
log.info("業務:組織機構,用例:通過企業編號獲取部門 , 場景:企業微信");
log.info("通過企業微信的API獲取組織的部門信息,然後包裝為需要的部門列表");
Department department = new Department();
department.setName("wechat");
department.setCorpId(corpId);
return Collections.singletonList(department);
}
}
擴展點使用
在命令執行器中使用。
package com.authine.web.cola.executor.query;
import com.alibaba.cola.command.Command;
import com.alibaba.cola.command.CommandExecutorI;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.extension.ExtensionExecutor;
import com.authine.web.cola.dto.domainmodel.Department;
import com.authine.web.cola.domain.customer.OrganizationExtPt;
import com.authine.web.cola.dto.OrgnizationQry;
import java.util.List;
/**
* @author carter
* create_date 2020/5/25 15:09
* description 查詢組織機構的指令執行
*/
@Command
public class OrgazationQueryExe implements CommandExecutorI<MultiResponse, OrgnizationQry> {
private final ExtensionExecutor extensionExecutor;
public OrgazationQueryExe(ExtensionExecutor extensionExecutor) {
this.extensionExecutor = extensionExecutor;
}
@Override
public MultiResponse execute(OrgnizationQry cmd) {
String corpId = cmd.getCorpId();
boolean includeDelete = cmd.isIncludeDelete();
List<Department> departmentList = extensionExecutor.execute(OrganizationExtPt.class, cmd.getBizScenario(),
ex -> ex.getDepartmentsByCorpId(corpId, includeDelete));
return MultiResponse.ofWithoutTotal(departmentList);
}
}
測試擴展點的使用
封裝一個http介面來調用。
package com.authine.web.cola.controller;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.extension.BizScenario;
import com.authine.web.cola.api.OrganizationServiceI;
import com.authine.web.cola.dto.OrgnizationQry;
import com.authine.web.cola.dto.domainmodel.Department;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrganizationController {
private final OrganizationServiceI organizationServiceI;
public OrganizationController(OrganizationServiceI organizationServiceI) {
this.organizationServiceI = organizationServiceI;
}
@GetMapping(value = "/organization/getDepartmentsByCorpId/{corpId}/{scenario}")
public MultiResponse<Department> listCustomerByName(@PathVariable("corpId") String corpId,@PathVariable("scenario") String scenario){
OrgnizationQry qry = new OrgnizationQry();
qry.setCorpId(corpId);
qry.setIncludeDelete(true);
qry.setBizScenario(BizScenario.valueOf("organize","getByCorpId",scenario));
return organizationServiceI.getDepartmentsByCorpId(qry);
}
}
下麵是使用介面進行測試的結果。
小結
基於元數據的擴展點設計,可以靈活的應對 業務場景的多樣性,以及靈活的支持版本升級。
其它的擴展點(校驗器,轉換器)其它等,也可以輕鬆做到擴展。
使用例子在框架的單元測試用例中。
擴展點設計
設計本質
設計理念。是一種基於數據的配置擴展。即基於註解上帶上配置數據。
@Extension 源碼如下:
package com.alibaba.cola.extension;
import com.alibaba.cola.common.ColaConstant;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Component
public @interface Extension {
String bizId() default BizScenario.DEFAULT_BIZ_ID;
String useCase() default BizScenario.DEFAULT_USE_CASE;
String scenario() default BizScenario.DEFAULT_SCENARIO;
}
圖文說明如下:
下麵深入源碼進行研究。從使用的源碼出發。
ExtensionExecutor
類圖如下。
首先,標註了Component,所以,在ioc中可以通過類型拿到實例。
最後,執行函數是放在父類AbstractComponentExecutor中;
重點分析一下它實現的功能:即通過坐標得到擴展實例;
/**
* if the bizScenarioUniqueIdentity is "ali.tmall.supermarket"
*
* the search path is as below:
* 1、first try to get extension by "ali.tmall.supermarket", if get, return it.
* 2、loop try to get extension by "ali.tmall", if get, return it.
* 3、loop try to get extension by "ali", if get, return it.
* 4、if not found, try the default extension
* @param targetClz
*/
protected <Ext> Ext locateExtension(Class<Ext> targetClz, BizScenario bizScenario) {
checkNull(bizScenario);
Ext extension;
String bizScenarioUniqueIdentity = bizScenario.getUniqueIdentity();
logger.debug("BizScenario in locateExtension is : " + bizScenarioUniqueIdentity);
// first try
extension = firstTry(targetClz, bizScenarioUniqueIdentity);
if (extension != null) {
return extension;
}
// loop try
extension = loopTry(targetClz, bizScenarioUniqueIdentity);
if (extension != null) {
return extension;
}
throw new ColaException("Can not find extension with ExtensionPoint: "+targetClz+" BizScenario:"+bizScenarioUniqueIdentity);
}
實現步驟如下:
ExtensionRepository
package com.alibaba.cola.extension;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import lombok.Getter;
/**
* ExtensionRepository
* @author fulan.zjf 2017-11-05
*/
@Component
public class ExtensionRepository {
@Getter
private Map<ExtensionCoordinate, ExtensionPointI> extensionRepo = new HashMap<>();
}
裡面是一個空的map,主要還是看組裝過程。看下麵的ExtensionRegister;
ExtensionRegister
看名字,就是註冊擴展的組件。
/*
* Copyright 2017 Alibaba.com All right reserved. This software is the
* confidential and proprietary information of Alibaba.com ("Confidential
* Information"). You shall not disclose such Confidential Information and shall
* use it only in accordance with the terms of the license agreement you entered
* into with Alibaba.com.
*/
package com.alibaba.cola.boot;
import com.alibaba.cola.common.ApplicationContextHelper;
import com.alibaba.cola.common.ColaConstant;
import com.alibaba.cola.exception.framework.ColaException;
import com.alibaba.cola.extension.*;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* ExtensionRegister
* @author fulan.zjf 2017-11-05
*/
@Component
public class ExtensionRegister implements RegisterI{
@Autowired
private ExtensionRepository extensionRepository;
@Override
public void doRegistration(Class<?> targetClz) {
ExtensionPointI extension = (ExtensionPointI) ApplicationContextHelper.getBean(targetClz);
Extension extensionAnn = targetClz.getDeclaredAnnotation(Extension.class);
String extPtClassName = calculateExtensionPoint(targetClz);
BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario());
ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(extPtClassName, bizScenario.getUniqueIdentity());
ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extension);
if (preVal != null) {
throw new ColaException("Duplicate registration is not allowed for :" + extensionCoordinate);
}
}
/**
* @param targetClz
* @return
*/
private String calculateExtensionPoint(Class<?> targetClz) {
Class[] interfaces = targetClz.getInterfaces();
if (ArrayUtils.isEmpty(interfaces))
throw new ColaException("Please assign a extension point interface for "+targetClz);
for (Class intf : interfaces) {
String extensionPoint = intf.getSimpleName();
if (StringUtils.contains(extensionPoint, ColaConstant.EXTENSION_EXTPT_NAMING))
return intf.getName();
}
throw new ColaException("Your name of ExtensionPoint for "+targetClz+" is not valid, must be end of "+ ColaConstant.EXTENSION_EXTPT_NAMING);
}
}
註冊過程如下:
以上是擴展類註冊到擴展倉庫的過程。
註冊時機。啟動的時刻通過包掃描進行註冊。
RegisterFactory
把各種註冊器放入到ioc中,通過一個統一的方法返回。
/*
* Copyright 2017 Alibaba.com All right reserved. This software is the
* confidential and proprietary information of Alibaba.com ("Confidential
* Information"). You shall not disclose such Confidential Information and shall
* use it only in accordance with the terms of the license agreement you entered
* into with Alibaba.com.
*/
package com.alibaba.cola.boot;
import com.alibaba.cola.command.Command;
import com.alibaba.cola.command.PostInterceptor;
import com.alibaba.cola.command.PreInterceptor;
import com.alibaba.cola.event.EventHandler;
import com.alibaba.cola.extension.Extension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* RegisterFactory
*
* @author fulan.zjf 2017-11-04
*/
@Component
public class RegisterFactory{
@Autowired
private PreInterceptorRegister preInterceptorRegister;
@Autowired
private PostInterceptorRegister postInterceptorRegister;
@Autowired
private CommandRegister commandRegister;
@Autowired
private ExtensionRegister extensionRegister;
@Autowired
private EventRegister eventRegister;
public RegisterI getRegister(Class<?> targetClz) {
PreInterceptor preInterceptorAnn = targetClz.getDeclaredAnnotation(PreInterceptor.class);
if (preInterceptorAnn != null) {
return preInterceptorRegister;
}
PostInterceptor postInterceptorAnn = targetClz.getDeclaredAnnotation(PostInterceptor.class);
if (postInterceptorAnn != null) {
return postInterceptorRegister;
}
Command commandAnn = targetClz.getDeclaredAnnotation(Command.class);
if (commandAnn != null) {
return commandRegister;
}
Extension extensionAnn = targetClz.getDeclaredAnnotation(Extension.class);
if (extensionAnn != null) {
return extensionRegister;
}
EventHandler eventHandlerAnn = targetClz.getDeclaredAnnotation(EventHandler.class);
if (eventHandlerAnn != null) {
return eventRegister;
}
return null;
}
}
BootStrap
掃描java的class,進行ioc組裝;
/*
* Copyright 2017 Alibaba.com All right reserved. This software is the
* confidential and proprietary information of Alibaba.com ("Confidential
* Information"). You shall not disclose such Confidential Information and shall
* use it only in accordance with the terms of the license agreement you entered
* into with Alibaba.com.
*/
package com.alibaba.cola.boot;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.cola.exception.framework.ColaException;
import lombok.Getter;
import lombok.Setter;
/**
* <B>應用的核心引導啟動類</B>
* <p>
* 負責掃描在applicationContext.xml中配置的packages. 獲取到CommandExecutors, intercepters, extensions, validators等
* 交給各個註冊器進行註冊。
*
* @author fulan.zjf 2017-11-04
*/
public class Bootstrap {
@Getter
@Setter
private List<String> packages;
private ClassPathScanHandler handler;
@Autowired
private RegisterFactory registerFactory;
public void init() {
Set<Class<?>> classSet = scanConfiguredPackages();
registerBeans(classSet);
}
/**
* @param classSet
*/
private void registerBeans(Set<Class<?>> classSet) {
for (Class<?> targetClz : classSet) {
RegisterI register = registerFactory.getRegister(targetClz);
if (null != register) {
register.doRegistration(targetClz);
}
}
}
其它的核心組件的註冊也在該代碼中。
AbstractComponentExecutor
抽象的組件執行器,主要功能是定位到擴展類,然後執行介面的方法。
源碼如下:
package com.alibaba.cola.boot;
import com.alibaba.cola.extension.BizScenario;
import com.alibaba.cola.extension.ExtensionCoordinate;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* @author fulan.zjf
* @date 2017/12/21
*/
public abstract class AbstractComponentExecutor {
/**
* Execute extension with Response
*
* @param targetClz
* @param bizScenario
* @param exeFunction
* @param <R> Response Type
* @param <T> Parameter Type
* @return
*/
public <R, T> R execute(Class<T> targetClz, BizScenario bizScenario, Function<T, R> exeFunction) {
T component = locateComponent(targetClz, bizScenario);
return exeFunction.apply(component);
}
public <R, T> R execute(ExtensionCoordinate extensionCoordinate, Function<T, R> exeFunction){
return execute((Class<T>) extensionCoordinate.getExtensionPointClass(), extensionCoordinate.getBizScenario(), exeFunction);
}
/**
* Execute extension without Response
*
* @param targetClz
* @param context
* @param exeFunction
* @param <T> Parameter Type
*/
public <T> void executeVoid(Class<T> targetClz, BizScenario context, Consumer<T> exeFunction) {
T component = locateComponent(targetClz, context);
exeFunction.accept(component);
}
public <T> void executeVoid(ExtensionCoordinate extensionCoordinate, Consumer<T> exeFunction){
executeVoid(extensionCoordinate.getExtensionPointClass(), extensionCoordinate.getBizScenario(), exeFunction);
}
protected abstract <C> C locateComponent(Class<C> targetClz, BizScenario context);
}
主要用到了java8的函數式介面Function<T,R>.
T:即系統中註冊好的擴展類實例;
R即調用T的使用類的方法,執行之後的返回值。
把執行哪個方法的選擇權交給了業務邏輯代碼。
提供了4種不同的重載方法。
小結
通過key,value的方式進行擴展。
代碼
原創不易,關註誠可貴,轉發價更高!轉載請註明出處,讓我們互通有無,共同進步,歡迎溝通交流。
我會持續分享Java軟體編程知識和程式員發展職業之路,歡迎關註,我整理了這些年編程學習的各種資源,關註公眾號‘李福春持續輸出’,發送'學習資料'分享給你!