扩展点的注解
扩展点的坐标一共三层:
bizId
useCase
scenario
自定义扩展点注解,通过注解来标识一个类
@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#"};
}
扩展点的根接口
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<>();
}
注册扩展点的流程
@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;
}
}
通过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;
}
}
履约平台扩展执行器,扩展点的四种加载方式,执行对应的方法
/**
* 履约平台扩展执行器
* @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");
}
}
}
扩展点在实际代码中的使用
扩展点定义,例如人车距离校验扩展点
/**
* 人车距离校验扩展点
* @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);
}