COLA的擴展性使用和源碼研究

cola擴展點使用和設計初探

image.png

封裝變化,可靈活應對程序的需求變化。java

擴展點使用

步驟:git

定義擴展點接口,類型能夠是校驗器,轉換器,實體; 必須以ExtPt結尾,表示一個擴展點。程序員

好比,我定義一個雲樞的組織結構的擴展點接口,消息發送擴展點,二開擴展點,webapi的rest接口擴展點點。github

定義擴展點接口

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);


}

好比業務擴展分爲釘釘,微信:web

這裏基於擴展理論(x,y);spring

即經過 業務,用例,場景獲得擴展點的key, 那後擴展類就是針對實際的業務場景的業務處理代碼;apache

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接口來調用。api

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);
    }


}

下面是使用接口進行測試的結果。springboot

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:
     * 一、first try to get extension by "ali.tmall.supermarket", if get, return it.
     * 二、loop try to get extension by "ali.tmall", if get, return it.
     * 三、loop try to get extension by "ali", if get, return it.
     * 四、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軟件編程知識和程序員發展職業之路,歡迎關注,我整理了這些年編程學習的各類資源,關注公衆號‘李福春持續輸出’,發送'學習資料'分享給你!
李福春持續輸出公衆號二維碼
相關文章
相關標籤/搜索