本文共 15394 字,大约阅读时间需要 51 分钟。
封装变化,可灵活应对程序的需求变化。
步骤:
定义扩展点接口,类型可以是校验器,转换器,实体; 必须以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 部门 */ ListgetDepartmentsByCorpId(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")@Slf4jpublic class DingTalkOrganizationExt implements OrganizationExtPt { @Override public ListgetDepartmentsByCorpId(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 ListgetDepartmentsByCorpId(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 MultiResponselistCustomerByName(@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})@Componentpublic @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: * 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 */ protectedExt 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); }
实现步骤如下:
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 MapextensionRepo = 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 */@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); }}
注册过程如下:
以上是扩展类注册到扩展仓库的过程。
注册时机。启动的时刻通过包扫描进行注册。
把各种注册器放入到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; }}
扫描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); } } }
其它的核心组件的注册也在该代码中。
抽象的组件执行器,主要功能是定位到扩展类,然后执行接口的方法。
源码如下:
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 * @paramResponse 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软件编程知识和程序员发展职业之路,欢迎关注,我整理了这些年编程学习的各种资源,关注公众号‘李福春持续输出’,发送'学习资料'分享给你!