博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
COLA的扩展性使用和源码研究
阅读量:4211 次
发布时间:2019-05-26

本文共 15394 字,大约阅读时间需要 51 分钟。

cola扩展点使用和设计初探

image.png

封装变化,可灵活应对程序的需求变化。

扩展点使用

步骤:

定义扩展点接口,类型可以是校验器,转换器,实体; 必须以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
getDepartmentsByCorpId(String corpId, Boolean includeDelete);}

比如业务扩展分为钉钉,微信:

这里基于扩展理论(x,y);

即通过 业务,用例,场景得到扩展点的key, 那后扩展类就是针对实际的业务场景的业务处理代码;

file

钉钉场景扩展点实现

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")@Slf4jpublic class DingTalkOrganizationExt implements OrganizationExtPt {    @Override    public List
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")@Slf4jpublic class WechatOrganizationExt  implements OrganizationExtPt {    @Override    public List
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     查询组织机构的指令执行 */@Commandpublic class OrgazationQueryExe implements CommandExecutorI
{ 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
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;@RestControllerpublic class OrganizationController {    private final OrganizationServiceI organizationServiceI;    public OrganizationController(OrganizationServiceI organizationServiceI) {        this.organizationServiceI = organizationServiceI;    }    @GetMapping(value = "/organization/getDepartmentsByCorpId/{corpId}/{scenario}")    public MultiResponse
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); }}

下面是使用接口进行测试的结果。

image.png

小结

image.png 基于元数据的扩展点设计,可以灵活的应对 业务场景的多样性,以及灵活的支持版本升级。 其它的扩展点(校验器,转换器)其它等,也可以轻松做到扩展。 使用例子在框架的单元测试用例中。

扩展点设计

设计本质

设计理念。是一种基于数据的配置扩展。即基于注解上带上配置数据。

@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})@Componentpublic @interface Extension {    String bizId()  default BizScenario.DEFAULT_BIZ_ID;    String useCase() default BizScenario.DEFAULT_USE_CASE;    String scenario() default BizScenario.DEFAULT_SCENARIO;}

图文说明如下:

image.png

下面深入源码进行研究。从使用的源码出发。

ExtensionExecutor

类图如下。

首先,标注了Component,所以,在ioc中可以通过类型拿到实例。

最后,执行函数是放在父类AbstractComponentExecutor中;

image.png

重点分析一下它实现的功能:即通过坐标得到扩展实例;

/**     * 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 locateExtension(Class
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); }

实现步骤如下:

file

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 */@Componentpublic class ExtensionRepository {    @Getter    private Map
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 */@Componentpublic 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); }}

注册过程如下:

file

以上是扩展类注册到扩展仓库的过程。

注册时机。启动的时刻通过包扫描进行注册。

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 */@Componentpublic 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;/** * 应用的核心引导启动类 * 

* 负责扫描在applicationContext.xml中配置的packages. 获取到CommandExecutors, intercepters, extensions, validators等 * 交给各个注册器进行注册。 * * @author fulan.zjf 2017-11-04 */public class Bootstrap { @Getter @Setter private List

packages; private ClassPathScanHandler handler; @Autowired private RegisterFactory registerFactory; public void init() { Set
> classSet = scanConfiguredPackages(); registerBeans(classSet); } /** * @param classSet */ private void registerBeans(Set
> 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 
Response Type * @param
Parameter Type * @return */ public
R execute(Class
targetClz, BizScenario bizScenario, Function
exeFunction) { T component = locateComponent(targetClz, bizScenario); return exeFunction.apply(component); } public
R execute(ExtensionCoordinate extensionCoordinate, Function
exeFunction){ return execute((Class
) extensionCoordinate.getExtensionPointClass(), extensionCoordinate.getBizScenario(), exeFunction); } /** * Execute extension without Response * * @param targetClz * @param context * @param exeFunction * @param
Parameter Type */ public
void executeVoid(Class
targetClz, BizScenario context, Consumer
exeFunction) { T component = locateComponent(targetClz, context); exeFunction.accept(component); } public
void executeVoid(ExtensionCoordinate extensionCoordinate, Consumer
exeFunction){ executeVoid(extensionCoordinate.getExtensionPointClass(), extensionCoordinate.getBizScenario(), exeFunction); } protected abstract
C locateComponent(Class
targetClz, BizScenario context);}

主要用到了java8的函数式接口Function<T,R>. T:即系统中注册好的扩展类实例; R即调用T的使用类的方法,执行之后的返回值。

把执行哪个方法的选择权交给了业务逻辑代码。

提供了4种不同的重载方法。

小结

通过key,value的方式进行扩展。

代码

 

原创不易,关注诚可贵,转发价更高!转载请注明出处,让我们互通有无,共同进步,欢迎沟通交流。 我会持续分享Java软件编程知识和程序员发展职业之路,欢迎关注,我整理了这些年编程学习的各种资源,关注公众号‘李福春持续输出’,发送'学习资料'分享给你!

你可能感兴趣的文章
JVM内存模型详解
查看>>
(六) Git--标签管理
查看>>
建造者模式(Builder)-设计模式(三)
查看>>
Android——ImageSwitcher轮流显示动画
查看>>
Android——利用手机端的文件存储和SQLite实现一个拍照图片管理系统
查看>>
罗永浩欲直播带货,京东说可以帮忙联系
查看>>
B站,正在变成下一个“公众号”?
查看>>
小米启动安心服务月 手机家电产品可免费清洁保养
查看>>
刘作虎:一加新品将全系支持 5G
查看>>
滴滴顺风车上线新功能,特殊时期便捷出行
查看>>
不会延期!iPhone 12S预计如期在9月发售:升级三星LTPO屏幕
查看>>
腾讯物联网操作系统TencentOS tiny线上移植大赛,王者机器人、QQ公仔、定制开发板等礼品等你来拿 !
查看>>
为云而生,腾讯云服务器操作系统TencentOS内核正式开源
查看>>
腾讯汤道生:开源已成为许多技术驱动型产业重要的创新推动力
查看>>
微信小程序多端框架 kbone 开源
查看>>
视频质量评估算法 DVQA 正式开源
查看>>
在中国提供了60亿次服务的疫情模块向世界开源 腾讯抗疫科技输出海外
查看>>
在中国提供了60亿次服务的疫情模块向世界开源
查看>>
世界卫生组织与腾讯加深合作 新冠肺炎AI自查助手全球开源
查看>>
Hibernate 中get, load 区别
查看>>