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軟體程式設計知識和程式設計師發展職業之路,歡迎關注,我整理了這些年程式設計學習的各種資源,關注公眾號‘李福春持續輸出’,傳送'學習資料'分享給你!