• COLA的扩展性使用和源码研究


    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<Department> 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")
    @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);
        }
    
    
    }
    
    

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

    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})
    @Component
    public @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> 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);
        }
    

    实现步骤如下:

    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
     */
    @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);
        }
    
    }
    

    注册过程如下:

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

  • 相关阅读:
    react native ios 报错SyntaxError: Unexpected end of JSON input
    微信小程序父组件给子组件传参
    xcode10 Command PhaseScriptExecution failed with a nonzero exit code
    html中的video放置在微信上打开会全屏显示,并且丢失文件上的其他内容
    vue cli使用vue-awesome-swiper动画
    vue 使用swiper
    react native 配置leancloud推送 ios版
    iOS----------jenkins
    iOS----------学习路线思维导图
    iOS学习路线
  • 原文地址:https://www.cnblogs.com/snidget/p/12961700.html
Copyright © 2020-2023  润新知