Expansion of COLA and source code research

Posted May 25, 202012 min read

Preliminary study on the use and design of cola extension point

image.png

Packaging changes can flexibly respond to changes in program requirements.

Use of extension points

step:

Define the extension point interface, the type can be a checker, converter, entity; it must end with ExtPt to indicate an extension point.

For example, I define an extension point interface of a cloud hub's organizational structure, a message sending extension point, a two-open extension point, and a webapi rest interface extension point.

Define extension point interface

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 defines the extension point interface, some methods for the organization.
* /

public interface OrganizationExtPt extends ExtensionPointI {

   /**
     * Query all departments under the company according to corpId
     *
     * @param corpId Enterprise ID
     * @param includeDelete whether to include the deleted department
     * @return department
     * /
    List <Department> getDepartmentsByCorpId(String corpId, Boolean includeDelete);


}

For example, business expansion is divided into Dingding, WeChat:

This is based on the extended theory(x, y);

That is, the key of the extension point is obtained through business, use cases, and scenarios, and then the extension class is the business processing code for the actual business scenario;

file

Realization of the extension point of Dingding scene

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 The expansion of the enterprise department in the scenario of obtaining the department list through 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("In the case of organizational structure business, the use case of obtaining the department list by enterprise number, the way to realize the business in the nailing scenario");

        log.info("obtain the organization information through nail configuration information and API, and assemble into the department information identified by Yunshu");

        Department department = new Department();

        department.setName("dingTalk");
        department.setCorpId(corpId);

        return Collections.singletonList(department);
    }
}

Implementation of Enterprise WeChat Extension Point

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 Enterprise WeChat extension point implementation
* /
@Extension(bizId = "organize", useCase = "getByCorpId", scenario = "wechat")
@ Slf4j
public class WechatOrganizationExt implements OrganizationExtPt {
@Override
public List getDepartmentsByCorpId(String corpId, Boolean includeDelete) {

        log.info("Business:Organization, Use Case:Get Department by Enterprise Number, Scene:Enterprise WeChat");

        log.info("Acquiring the organization's department information through the enterprise WeChat API, and then packing it into the required department list");

        Department department = new Department();

        department.setName("wechat");
        department.setCorpId(corpId);

        return Collections.singletonList(department);
    }
}

Use of extension points

Used in command executors.

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 Query the order execution of the organization
* /
@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);
    }
}

Test the use of extension points

Encapsulate an http interface to call.

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


}

The following are the results of testing using the interface.

image.png

summary

image.png
The extension point design based on metadata can flexibly respond to the diversity of business scenarios and flexibly support version upgrades.
Other extension points(checkers, converters), etc., can also be easily extended.
Usage examples are in the unit test cases of the framework.

Extension point design

Design essence

design concept. It is a data-based configuration extension. That is, the configuration data is carried based on the annotation.

@Extension source code is as follows:

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

The graphic description is as follows:

image.png

Let's dive into the source code for research. Start with the source code used.

ExtensionExecutor

The class diagram is as follows.

First of all, Component is marked, so in ioc you can get an instance by type.

Finally, the execution function is placed in the parent class AbstractComponentExecutor;

image.png

Focus on analyzing the functions it realizes:that is, to obtain extended examples through coordinates;

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

The implementation steps are as follows:

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

}

Inside is an empty map, mainly depends on the assembly process. See ExtensionRegister below;

ExtensionRegister

Look at the name, is to register the extension component.

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

}

The registration process is as follows:

file

The above is the process of registering the extension class to the extension warehouse.

Time to register. Register at the time of startup through the package scan.

RegisterFactory

Put various registrars into ioc and return them through a unified method.

/*
* 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

Scan the java class and assemble 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;

/**
* Application's core boot class
*


* Scan the packages configured in applicationContext.xml. Get CommandExecutors, intercepters, extensions, validators, etc.
* Submit to each registrar for registration.
*
* @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 <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);
            }
        }

    }

The registration of other core components is also in the code.

AbstractComponentExecutor

Abstract component executor, the main function is to locate the extended class, and then execute the interface method.

The source code is as follows:

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

The functional interface Function <T, R> of java8 is mainly used.
T:the registered extension class instance in the system;
R is the method of calling T's usage class, and the return value after execution.

The choice of which method to execute is given to the business logic code.

Four different overloading methods are provided.

summary

Expand by key and value.

Code

Code point I get!

Originality is not easy, pay attention to sincerity, and the forwarding price is higher! Reprint please indicate the source, let us communicate with each other, make progress together, welcome to communicate.
I will continue to share Java software programming knowledge and the career path of programmers. Welcome to pay attention. I have organized various resources for programming and learning over the years, pay attention to the public number "Li Fuchun continuous output", and send "learning materials" to you!
Li Fuchun continues to output the QR code of the public number