封裝變化,可靈活應對程序的需求變化。java
步驟:git
定義擴展點接口,類型能夠是校驗器,轉換器,實體; 必須以ExtPt結尾,表示一個擴展點。程序員
好比,我定義一個雲樞的組織結構的擴展點接口,消息發送擴展點,二開擴展點,webapi的rest接口擴展點點。github
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); }
好比業務擴展分爲釘釘,微信:web
這裏基於擴展理論(x,y);spring
即經過 業務,用例,場景獲得擴展點的key, 那後擴展類就是針對實際的業務場景的業務處理代碼;apache
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接口來調用。api
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); } }
下面是使用接口進行測試的結果。springboot
基於元數據的擴展點設計,能夠靈活的應對 業務場景的多樣性,以及靈活的支持版本升級。
其它的擴展點(校驗器,轉換器)其它等,也能夠輕鬆作到擴展。
使用例子在框架的單元測試用例中。
設計理念。是一種基於數據的配置擴展。即基於註解上帶上配置數據。
@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; }
圖文說明以下:
下面深刻源碼進行研究。從使用的源碼出發。
類圖以下。
首先,標註了Component,因此,在ioc中能夠經過類型拿到實例。
最後,執行函數是放在父類AbstractComponentExecutor中;
重點分析一下它實現的功能:即經過座標獲得擴展實例;
/** * if the bizScenarioUniqueIdentity is "ali.tmall.supermarket" * * the search path is as below: * 一、first try to get extension by "ali.tmall.supermarket", if get, return it. * 二、loop try to get extension by "ali.tmall", if get, return it. * 三、loop try to get extension by "ali", if get, return it. * 四、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); }
實現步驟以下:
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;
看名字,就是註冊擴展的組件。
/* * 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); } }
註冊過程以下:
以上是擴展類註冊到擴展倉庫的過程。
註冊時機。啓動的時刻經過包掃描進行註冊。
把各類註冊器放入到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; } }
掃描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); } } }
其它的核心組件的註冊也在該代碼中。
抽象的組件執行器,主要功能是定位到擴展類,而後執行接口的方法。
源碼以下:
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軟件編程知識和程序員發展職業之路,歡迎關注,我整理了這些年編程學習的各類資源,關注公衆號‘李福春持續輸出’,發送'學習資料'分享給你!