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软件编程知识和程序员发展职业之路,欢迎关注,我整理了这些年编程学习的各种资源,关注公众号‘李福春持续输出’,发送'学习资料'分享给你!

COLA的扩展性使用和源码研究的更多相关文章

  1. MapReduce多用户任务调度器——容量调度器(Capacity Scheduler)原理和源码研究

    前言:为了研究需要,将Capacity Scheduler和Fair Scheduler的原理和代码进行学习,用两篇文章作为记录.如有理解错误之处,欢迎批评指正. 容量调度器(Capacity Sch ...

  2. 《Netty5.0架构剖析和源码解读》【PDF】下载

    <Netty5.0架构剖析和源码解读>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062545 内容简介 Netty 是个异步的 ...

  3. 使用Lua脚本语言开发出高扩展性的系统,AgileEAS.NET SOA中间件Lua脚本引擎介绍

    一.前言 AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台.用于帮助中小型软件企业建立一条适合市 ...

  4. Java并发编程:性能、扩展性和响应

    1.介绍 本文讨论的重点在于多线程应用程序的性能问题.我们会先给性能和扩展性下一个定义,然后再仔细学习一下Amdahl法则.下面的内容我们会考察一下如何用不同的技术方法来减少锁竞争,以及如何用代码来实 ...

  5. Zend server最大化应用程序的性能、扩展性和可用性

    如果我有8个小时去砍到一棵树,我会花6个小时磨斧子”——林肯(美国总统) 你可以知道? 世界页面访问量的峰值超过7000万每分钟. CloudFare公司服务器问题,导致785000站点崩溃一小时. ...

  6. [Spark内核] 第32课:Spark Worker原理和源码剖析解密:Worker工作流程图、Worker启动Driver源码解密、Worker启动Executor源码解密等

    本課主題 Spark Worker 原理 Worker 启动 Driver 源码鉴赏 Worker 启动 Executor 源码鉴赏 Worker 与 Master 的交互关系 [引言部份:你希望读者 ...

  7. Dubbo原理和源码解析之“微内核+插件”机制

    github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...

  8. 【转】rpm包和源码包安装的区别

    转自:https://blog.csdn.net/junjie_6/article/details/59483785 建议在安装线上的生产服务器软件包时都用源码安装,这是因为源码安装可以自行调整编译参 ...

  9. Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析

    相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 Java并发编程(五)Concurr ...

随机推荐

  1. Yii项目Security加密解密类提取

    <?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * ...

  2. 数据结构--栈(附上STL栈)

    定义: 栈是一种只能在某一端插入和删除数据的特殊线性表.他按照先进先出的原则存储数据,先进的数据被压入栈底,最后进入的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后被压入栈的,最先弹出).因此栈 ...

  3. HTML 页面跳转的五种方法

    H方法TML 页面跳转的五种方法 下面列了五个例子来详细说明,这几个例子的主要功能是:在5秒后,自动跳转到同目录下的hello.html(根据自己需要自行修改)文件.1) html的实现 <he ...

  4. Python基础01 集合

    初始化 # python3 # coding = utf-8 mylist = [] for item in range(10): mylist.append(item * 10 + 3) myset ...

  5. 整理高度塌陷与BFC

    当面试官问道你高度塌陷时,人们第一想到的方法一定是 .clearfix::after { content: ''; display: block; clear: both; visibility: h ...

  6. Vuel路由跳转动画

    1.App.vue中添加 <transition :name="animate"> <router-view/> </transition> e ...

  7. 基于BasicRF点对点无线开发基础知识

    BasicRF点对点概述 BasicRF软件包有四大部分: <1> 硬件层:Hardware Layer. <2> 硬件抽象层:Haware Abstraction Layer ...

  8. 实时(RTC)时钟,系统时钟和CPU时钟

    最近在学stm32的时候看到RTC时钟和系统时钟,不知道区别在哪里,于是上网查了一下. 实时时钟:RTC时钟,用于提供年.月.日.时.分.秒和星期等的实时时间信息,由后备电池供电,当你晚上关闭系统和早 ...

  9. 带你看看Java的锁(三)-CountDownLatch和CyclicBarrier

    带你看看Java中的锁CountDownLatch和CyclicBarrier 前言 基本介绍 使用和区别 核心源码分析 总结 前言 Java JUC包中的文章已经写了好几篇了,首先我花了5篇文章从源 ...

  10. stanfordcorenlp安装教程&问题汇总(importerror-no-module-named-psutil、OSError: stanford-chinese-corenlp-yyyy-MM-dd-models.jar not exists.)&简单使用教程

    stanfordcorenlp安装教程&简单使用教程 编译环境:python 3.6 .win10 64位.jdk1.8及以上 1.stanfordcorenlp安装依赖环境 下载安装JDK ...