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

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
苹果公司所用字体大全苹果公司所用字体大全苹果公司所用字体大全苹果公司所用字体大全苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大全,苹果公司所用字体大

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值