Skip to content

Latest commit

 

History

History
664 lines (505 loc) · 20.6 KB

扩展点相关.md

File metadata and controls

664 lines (505 loc) · 20.6 KB

扩展点相关

1.扩展点实现

扩展点的注解

扩展点的坐标一共三层:

bizId

useCase

scenario

1.1 扩展点注解

自定义扩展点注解,通过注解来标识一个类

@MultipleExtension(bizId = BizLineConstant.BIKE)
public class BikeDefaultCheckNearBikeDistanceExtPt implements ICheckNearBikeDistanceExtPt {
		...
	}

@MultipleExtension(bizId = BizLineConstant.SPOCK, useCase = BizIdConstant.BATTERY_EXCHANGE)
public class SpockBatteryExchangeGetLifeValidateConditionExt implements GetLifeValidateConditionExtPt {
	 ...
	}


@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Component
public @interface MultipleExtension {
    String[] bizId() default {"#defaultBizId#"};

    String[] useCase() default {"#defaultUseCase#"};
    
    String[] scenario() default {"#defaultScenario#"};

}

1.2 注册扩展点对象

1.2.1 扩展点容器

扩展点的根接口

ExtensionPointI is the parent interface of all ExtensionPoints

扩展点表示一块逻辑在不同的业务有不同的实现,使用扩展点做接口申明,然后用Extension(扩展)去实现扩展点。

public interface ExtensionPointI {

}

ExtensionRepository

其中Map的key是扩展点坐标,value是对应的扩展点

@Component
public class ExtensionRepository {
    public Map<ExtensionCoordinate, ExtensionPointI> getExtensionRepo() {
        return extensionRepo;
    }
	private Map<ExtensionCoordinate, ExtensionPointI> extensionRepo = new HashMap<>();
    
}

1.2.2 注册扩展点

注册扩展点的流程

@Component
public class MultipleExtensionRegister {

    @Resource
    private ExtensionRepository extensionRepository;

    public static final String EXTENSION_EXTPT_NAMING = "ExtPt";

    // 注册扩展点对象,key是扩展点坐标 value扩展点对象
    public void doRegistration(ExtensionPointI extensionObject) {
        // 获得当前对象的类模版信息
        Class<?> extensionClz = extensionObject.getClass();
        // 获取当前的注解对象
        MultipleExtension extensionAnn = extensionClz.getDeclaredAnnotation(MultipleExtension.class);
        // 构建扩展点坐标
        List<BizScenario> bizScenarios = buildBizScenarios(extensionAnn.bizId(), extensionAnn.useCase(),
                extensionAnn.scenario());
        for (BizScenario bizScenario : bizScenarios) {
            // 获得扩展点的坐标(接口名称,坐标)
            ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz),
                    bizScenario.getUniqueIdentity());
            // 如果出现重复则返回旧值,不允许重复注册
            ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);
            if (preVal != null) {
                throw new RuntimeException("Duplicate registration is not allowed for :" + extensionCoordinate);
            }
        }
    }

    public String getUniqueIdentity(){
        return bizId + DOT_SEPARATOR + useCase + DOT_SEPARATOR + scenario;
    }

    /**
     * 获取目标类型实现的扩展,根据扩展点对象获得对应的接口,查看是否有接口的名称包含 "ExtPt"
     * @param targetClz 目标类
     * @return 扩展接口名称
     */
    private String calculateExtensionPoint(Class<?> targetClz) {
        Class[] interfaces = targetClz.getInterfaces();
        if (interfaces == null || interfaces.length == 0) {
            throw new RuntimeException("Please assign a extension point interface for " + targetClz);
        }
        for (Class intf : interfaces) {
            String extensionPoint = intf.getSimpleName();
            if (extensionPoint.contains(EXTENSION_EXTPT_NAMING)) {
                return intf.getName();
            }
        }
        throw new RuntimeException("Your name of ExtensionPoint for " + targetClz + " is not valid, must be end of "
                + EXTENSION_EXTPT_NAMING);
    }
        //获得对应的所有的扩展点坐标
    private List<BizScenario> buildBizScenarios(String[] bizIds, String[] useCases, String[] scenarios) {
        List<BizScenario> bizScenarios = Lists.newArrayList();
        for (String bizId : bizIds) {
            for (String useCase : useCases) {
                for (String scenario : scenarios) {
                    bizScenarios.add(BizScenario.valueOf(bizId, useCase, scenario));
                }
            }
        }
        return bizScenarios;
    }
}

1.2.3 注册整体流程

通过MultipleExtensionBootstrap实现ApplicationContextAware接口,(关于ApplicationContextAware详见https://km.sankuai.com/collabpage/2357905170)

Aware中文翻译:感知,意识到,获取到

在Spirng中Aware接口中,可以获得Spring的组件

比如实现了ApplicationContextAware接口的类,能够获取到ApplicationContext

比如实现了BeanFactoryAware接口的类,能够获取到BeanFactory对象

根据Spring中Bean的生命周期,先执行实现Aware的方法,然后执行Bean的init方法

setApplicationContext方法首先被调用。当Spring容器实例化并装配好一个Bean后,如果这个Bean实现了ApplicationContextAware接口,Spring就会调用它的setApplicationContext方法,将当前的ApplicationContext传入。

@PostConstruct注解的方法(如init方法)会在setApplicationContext方法之后被调用。这个方法会在所有的依赖注入都完成后,也就是说在Spring容器完成Bean的初始化之后被调用。

获取applicationContext之后,获得带有MultipleExtension的全部类的Map

@Component
public class MultipleExtensionBootstrap implements ApplicationContextAware {
    @Resource
    private MultipleExtensionRegister multipleExtensionRegister;

    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        // 通过注解获得对应的扩展点的 Map
        Map<String, Object> extensionBeans = applicationContext.getBeansWithAnnotation(MultipleExtension.class);
        extensionBeans.values().forEach(
                    // 遍历每个扩展点对象进行注册
                extension -> multipleExtensionRegister.doRegistration((ExtensionPointI) extension)
        );
    }

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

2.扩展执行器的定义

履约平台扩展执行器,扩展点的四种加载方式,执行对应的方法

/**

 * 履约平台扩展执行器

 * @author zhuguodong
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class FulFillExtensionExecutor {

    private final ExtensionRepository extensionRepository;

    /**

     * 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) {
        // 根据存放扩展点的Map,扩展点的坐标 = 扩展点类名 + 场景
        // 因此需要传入扩展点的Class和BizScenario
        T component = this.locateExtension(targetClz, bizScenario);
        log.info("Execute extension {}", component.getClass().getName());
        return exeFunction.apply(component);
    }

    /**

     * 无返回值的执行函数
     * @param targetClz
     * @param context
     * @param exeFunction
     * @param <T>
     */
    public <T> void executeVoid(Class<T> targetClz, BizScenario context, Consumer<T> exeFunction) {
        T component = this.locateExtension(targetClz, context);
        log.info("Execute extension {}", component.getClass().getName());
        exeFunction.accept(component);
    }

    /**

     * 执行扩展实例

     * 会进行四次尝试获取扩展点

     * 1.使用完整的命名空间查找扩展点

     * 2.使用默认场景查找扩展点

     * 3.使用默认使用场景和默认场景查找扩展点

     * 4.全部使用默认

     * @param targetClz 扩展

     * @param bizScenario  扩展坐标

     * @param <T> 扩展实例的类型
     */
    private <T> T locateExtension(Class<T> targetClz, BizScenario bizScenario) {
        // 检查bizScenario是否为null
        checkNull(bizScenario);
        // 定义需要返回的扩展点函数的类型
        T extension;

        log.debug("BizScenario in locateExtension is : " + bizScenario.getUniqueIdentity());

        // first try with full namespace
        // 尝试使用完整的命名空间查找扩展点函数
        // 类名 biz1.useCase1.scenario1
        extension = firstTry(targetClz, bizScenario);
        if (extension != null) {
            return extension;
        }

        // second try with default scenario
        // 尝试使用默认的场景查找扩展点
        // 类名,biz1.useCase1.#defaultScenario#
        extension = secondTry(targetClz, bizScenario);
        if (extension != null) {
            return extension;
        }

        // third try with default use case + default scenario
        // 尝试使用默认的使用场景和默认的场景查找扩展点
        // biz1.#defaultUseCase#.#defaultScenario#
        extension = defaultUseCaseTry(targetClz, bizScenario);
        if (extension != null) {
            return extension;
        }

        // forth try with default bizId + default use case + default scenario
        // #defaultBizId#defaultUseCase#.#defaultScenario#
        extension = defaultUseCaseTry(targetClz, BizScenario.valueOf(BizScenario.DEFAULT_BIZ_ID));
        if (extension != null) {
            return extension;
        }
        // 仍然没有加载到抛出异常
        throw new RuntimeException(
                "Can not find extension with ExtensionPoint: " + targetClz + " BizScenario:" + bizScenario
                        .getUniqueIdentity());
    }

    /**

     * first try with full namespace
     * 类名 biz1.useCase1.scenario1
     * example:  biz1.useCase1.scenario1
     */
    private <T> T firstTry(Class<T> targetClz, BizScenario bizScenario) {
        log.debug("First trying with " + bizScenario.getUniqueIdentity());
        return locate(targetClz.getName(), bizScenario.getUniqueIdentity());
    }

    /**

     * second try with default scenario
     * 类名,biz1.useCase1.#defaultScenario#
     * example:  biz1.useCase1.#defaultScenario#
     */
    private <T> T secondTry(Class<T> targetClz, BizScenario bizScenario) {
        log.debug("Second trying with " + bizScenario.getIdentityWithDefaultScenario());
        return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultScenario());
    }

    /**

     * third try with default use case + default scenario
     * 尝试使用默认的使用场景和默认的场景查找扩展点
     * example:  biz1.#defaultUseCase#.#defaultScenario#
     */
    private <T> T defaultUseCaseTry(Class<T> targetClz, BizScenario bizScenario) {
        log.debug("Third trying with " + bizScenario.getIdentityWithDefaultUseCase());
        return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultUseCase());
    }

    /**

     * 去extensionRepository中获取扩展点
     * Map的key由 new ExtensionCoordinate(name, uniqueIdentity) 组成
     * 使用对象作为key的话,注意要重写hashCode和equals方法
     * @param name
     * @param uniqueIdentity
     * @return
     * @param <T>
     */
    private <T> T locate(String name, String uniqueIdentity) {
        final T ext = (T) extensionRepository.getExtensionRepo().
                get(new ExtensionCoordinate(name, uniqueIdentity));
        return ext;
    }

    private void checkNull(BizScenario bizScenario) {
        if (bizScenario == null) {
            throw new IllegalArgumentException("BizScenario can not be null for extension");
        }
    }
}

3.扩展点的使用

扩展点在实际代码中的使用

扩展点定义,例如人车距离校验扩展点

/**
 * 人车距离校验扩展点
 * @author zhuguodong
 */
public interface ICheckNearBikeDistanceExtPt extends ExtensionPointI {

    /**
     * 是否需要校验人车距离
     * @param businessContext 业务上下文
     * @return {@code true}需要校验,{@code false}不需要校验
     */
    boolean checkBikeDistance(BusinessContext businessContext);

    /**

     * 获取距离车辆位置校验距离
     * @param businessContext
     * @return
     */
    Double getNearBikeCheckDistance(BusinessContext businessContext);

    /**

     * 获取距离车辆位置快照信息
     * @param businessContext
     * @return
     */
    BikeLocationSnapshot getNearBikeLocationSnapshot(BusinessContext businessContext);
}

详细的扩展点实现,通过@MultipleExtension注解标识

@RequiredArgsConstructor
@Component
@MultipleExtension(bizId = BizLineConstant.BIKE)
public class BikeDefaultCheckNearBikeDistanceExtPt implements ICheckNearBikeDistanceExtPt {
    .....
}

@RequiredArgsConstructor
@Component
@MultipleExtension(bizId = BizLineConstant.SPOCK)
public class SpockDefaultCheckNearBikeDistanceExtPt implements ICheckNearBikeDistanceExtPt {

    ....

}

代码中扩展点的调用

// 简写
protected Double getCondition(BusinessContext businessContext) {

        //传入的是ICheckNearBikeDistanceExtPt类型,所以extension的类型是ICheckNearBikeDistanceExtPt
        //在实际调用的时候有单车和电单车,通过扩展点的坐标区分单车和电单
        return extensionExecutor
        .execute(ICheckNearBikeDistanceExtPt.class, businessContextService.getBizScenario(businessContext),
        extension -> extension.getNearBikeCheckDistance(businessContext));
        }
// 不简写
protected Double getCondition(BusinessContext businessContext) {
        Class<ICheckNearBikeDistanceExtPt> extensionPointClass = ICheckNearBikeDistanceExtPt.class;
    BizScenario bizScenario = businessContextService.getBizScenario(businessContext);


            ExtensionExecutor extensionExecutor = this.extensionExecutor;
            Function<ICheckNearBikeDistanceExtPt, Double> function = new Function<ICheckNearBikeDistanceExtPt, Double>() {
@Override
public Double apply(ICheckNearBikeDistanceExtPt extension) {
        return extension.getNearBikeCheckDistance(businessContext);
        }
        };

        return extensionExecutor.execute(extensionPointClass, bizScenario, function);

        }

4.参数和配置校验过程

4.1 通过Lion配置获得需要校验的参数列表

线上配置ConvertParamTypeEnum中需要转换的参数列表

/** * 根据自定义key获取待校验的参数列表 * @param customSceneKey * @return */ private List getConvertTypeForCustom(String customSceneKey) {

    List<ParamConvertTypeConfig> convertTypeConfigs = Lion.getConfigRepository()
            .getList(LionConstant.CUSTOM_PARAM_CONVERT_TYPES_CONFIG, ParamConvertTypeConfig.class);
    if (CollectionUtils.isEmpty(convertTypeConfigs)) {
        log.warn("getCheckTypeForCustom|getCheckTypeForCustom get empty checkTypeConfigs.");
        return Lists.newArrayList();
    }

    return convertTypeConfigs.stream().filter(c -> customSceneKey.equals(c.getCustomConvertKey())).findFirst()
            .map(c -> c.getConvertTypes().stream()
                    .map(ConvertParamTypeEnum::fromType)
                    .collect(Collectors.toList()))
            .orElse(Lists.newArrayList());
}

4.2 通过抽象类定义整个参数校验的流程

package com.sankuai.bikeb.fulfillcore.domain.orderoperate.ability.convertparam.converter;

import com.sankuai.bikeb.fulfillcore.client.context.BusinessContext; import com.sankuai.fulfill.common.client.enums.check.ConvertParamTypeEnum; import com.sankuai.fulfill.common.client.exception.BusinessException; import lombok.extern.slf4j.Slf4j;

/**

  • 参数转换器, 含必要参数校验 */ @Slf4j public abstract class AbstractParamConverter {

    /**

    • 验证并转换参数

    • @param businessContext

    • @return */ public boolean checkAndConvert(BusinessContext businessContext) {

      // 1.跳过验证 if (skip(businessContext)) { log.info("AbstractParamConverter.{} skip", getConvertType()); return true; }

      // 2.获取原有参数 T t = getParamValue(businessContext); log.info("AbstractParamConverter.{} get paramValue: {}", getConvertType(), t);

      // 3.验证参数 boolean checkResult = checkParamWithException(t, false);

      // 4.验证不通过, 重新获取并再次验证, 验证通过后填充参数 if (!checkResult) { t = getFromAnother(businessContext); log.info("AbstractParamConverter.getFromAnother {}", t); checkResult = checkParamWithException(t, true); if (checkResult) { fillParam(businessContext, t); } }

      // 5.返回验证结果, 由业务方决定是否继续执行 return checkResult; }

    /**

    • 验证参数, 含异常处理

    • @param t

    • @return */ private boolean checkParamWithException(T t, boolean last) { boolean checkResult = false; try { checkResult = checkParam(t); } catch (BusinessException be) { log.warn("AbstractParamConverter.{} fail for businessError: {}", getConvertType(), be.getMessage(), be); // 如果是最后一次参数验证, 仍没有数据则抛异常 if (last) { throw be; } }

      return checkResult; }

    /**

    • 转换类型
    • @return */ public abstract ConvertParamTypeEnum getConvertType();

    /**

    • 是否可跳过
    • @param businessContext
    • @return */ protected abstract boolean skip(BusinessContext businessContext);

    /**

    • 获取参数值
    • @param businessContext
    • @return */ protected abstract T getParamValue(BusinessContext businessContext);

    /**

    • 验证参数, 会抛出业务异常
    • @param t
    • @return
    • @throws BusinessException */ protected abstract boolean checkParam(T t) throws BusinessException;

    /**

    • 重新获取参数
    • @param businessContext
    • @return */ protected abstract T getFromAnother(BusinessContext businessContext);

    /**

    • 填充参数
    • @param businessContext
    • @param t */ protected abstract void fillParam(BusinessContext businessContext, T t);

}

4.3 通过Lion配置获得需要校验的配置列表

/** * 根据自定义key获取验证配置 * @param customSceneKey * @return */ private List getCheckTypeForCustom(String customSceneKey) {

    List<CheckTypeConfig> checkTypeConfigs = Lion.getConfigRepository().getList(LionConstant.CUSTOM_CHECK_TYPES_CONFIG, CheckTypeConfig.class);
    if (CollectionUtils.isEmpty(checkTypeConfigs)) {
        log.warn("getCheckTypeForCustom|getCheckTypeForCustom get empty checkTypeConfigs.");
        return Lists.newArrayList();
    }

    return checkTypeConfigs.stream().filter(c -> customSceneKey.equals(c.getCustomCheckKey())).findFirst()
            .map(c -> c.getCheckTypes().stream()
                    .map(CheckTypeEnum::fromType)
                    .collect(Collectors.toList()))
            .orElse(Lists.newArrayList());
}

4.4 通过抽象类定义整个配置校验的流程

@Service @Slf4j public abstract class AbstractValidator<CON , FACT> { protected static final String ALL = "all";

@Autowired
protected BusinessContextService businessContextService;

public boolean validate(BusinessContext businessContext){
    if(skip(businessContext)){
        log.info("AbstractValidator.{}, skip",getCheckType());
        return true;
    }
    CON condition = getCondition(businessContext);
    FACT fact = getFact(businessContext);
    log.info("AbstractValidator.{}, condition:{},fact:{}",getCheckType(), JacksonUtil.toJson(condition),JacksonUtil.toJson(fact));
    return executeValidator(condition, fact, businessContext);
}

public abstract CheckTypeEnum getCheckType();

protected abstract boolean skip(BusinessContext businessContext);

protected abstract CON getCondition(BusinessContext businessContext);

protected abstract FACT getFact(BusinessContext businessContext);

protected abstract boolean executeValidator(CON condition, FACT fact, BusinessContext businessContext);

}