在上一篇中我们讲了阿里的 cola-component-extension-starter,但是他限制了定义的接口和其实现类必须以 ExePt 结尾 这个对于有强迫症的人来说很难受,因此我们自己实现一个简易版的
代码结构
源码
Extension
- 扩展点注解,即在具体的业务类上使用该注解,并且指定 bizId
/**
* 扩展点注解
*
* @author coderxdh
* @date 2023/7/17 23:27
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Component
public @interface Extension {
String bizId() default "defaultBizId";
}
ExtensionPointI
- 业务扩展点接口,当存在一个场景,在不同的业务背景下有着不同的实现逻辑,那么可以定义 xxx 接口,并且继承 ExtensionPointI 接口
/**
* 业务扩展点接口
*
* @author coderxdh
* @date 2023/7/17 23:47
*/
public interface ExtensionPointI {
}
ExtensionRepository
- 所有继承了 ExtensionPointI 类的接口都会存放到该 map 中,然后再根据不同的 bizId 去加载不同的实现类。何时存放进去的见下面的类
/**
* 存放所有的继承了 ExtensionPointI 接口的类
*
* @author coderxdh
* @date 2023/7/17 23:48
*/
@Component
public class ExtensionRepository {
public Map<String, ExtensionPointI> extensionRepo = new HashMap<>();
public Map<String, ExtensionPointI> getExtensionRepo() {
return extensionRepo;
}
}
ExtensionRegister
- 扩展点注册类,当 Spring 启动时,会去加载所有使用了 Extension 注解的类,然后以 Extension 的 bizId 作为 key,对应的类作为 value,存放到 extensionRepo 中
/**
* 注册扩展点
*
* @author coderxdh
* @date 2023/7/17 23:52
*/
@Component
public class ExtensionRegister implements ApplicationContextAware, SmartInitializingSingleton {
@Resource
private ExtensionRepository extensionRepository;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterSingletonsInstantiated() {
Map<String, Object> extensionBeans = this.applicationContext.getBeansWithAnnotation(Extension.class);
extensionBeans.entrySet().forEach(extensionObject -> {
Class<?> extensionClz = extensionObject.getValue().getClass();
if (AopUtils.isAopProxy(extensionObject)) {
extensionClz = ClassUtils.getUserClass(extensionObject);
}
Extension extensionAnn = AnnotationUtils.findAnnotation(extensionClz, Extension.class);
ExtensionPointI extensionPointI = (ExtensionPointI) extensionObject.getValue();
ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionAnn.bizId(), extensionPointI);
if (Objects.nonNull(preVal)) {
throw new RuntimeException("Duplicate bizId " + extensionAnn.bizId() + " is not allowed!");
}
});
}
}
ExtensionExecutor
- 定义两个方法,一个是有返回值的,一个是无返回值的
/**
* @author coderxdh
* @date 2023/7/18 0:08
*/
public interface ExtensionExecutor {
/**
* 执行void 方法
*
* @param clazz
* @param bizScene
* @param consumer
* @param <S>
*/
<S> void execute(Class<S> clazz, BizScene bizScene, Consumer<S> consumer);
/**
* 执行有返回值的方法
*
* @param clazz
* @param bizScene
* @param function
* @param <R>
* @param <S>
* @return
*/
<R, S> R execute(Class<S> clazz, BizScene bizScene, Function<S, R> function);
}
AbstractExtensionSelectorExecutor
- 实现上面定义的接口,并定义抽象方法,该方法的作用是:根据 bizId 去选择某一个实现类
/**
* @author coderxdh
* @date 2023/7/18 0:09
*/
public abstract class AbstractExtensionSelectorExecutor implements ExtensionExecutor {
@Override
public <S> void execute(Class<S> clazz, BizScene bizScene, Consumer<S> consumer) {
S service = selectService(bizScene, clazz);
consumer.accept(service);
}
@Override
public <R, S> R execute(Class<S> clazz, BizScene bizScene, Function<S, R> function) {
S service = selectService(bizScene, clazz);
return function.apply(service);
}
/**
* 根据 BizScene 查询相应的 Service
*
* @param bizScene
* @param clazz 接口Class
* @return
*/
protected abstract <S> S selectService(BizScene bizScene, Class<S> clazz);
}
ExtensionSelectorExecutor
- 实现 AbstractExtensionSelectorExecutor 接口的 selectService 方法
/**
* @author coderxdh
* @date 2023/7/18 0:11
*/
@Component
public class ExtensionSelectorExecutor extends AbstractExtensionSelectorExecutor {
private final Logger logger = LoggerFactory.getLogger(ExtensionSelectorExecutor.class);
@Resource
private ExtensionRepository extensionRepository;
@Override
protected <S> S selectService(BizScene bizScene, Class<S> clazz) {
checkNull(bizScene);
ExtensionPointI extensionPointI = extensionRepository.getExtensionRepo().get(bizScene.getBizId());
if (Objects.isNull(extensionPointI)) {
throw new RuntimeException("Can not find extension with ExtensionPoint: " + clazz + " BizScene:" + bizScene.getBizId());
}
return (S) extensionPointI;
}
private void checkNull(BizScene bizScene) {
if (bizScene == null) {
throw new IllegalArgumentException("bizScene can not be null for extension");
}
}
}
ExtensionAutoConfiguration
- 注入相关 Bean 到 Spring 容器中
/**
* @author coderxdh
* @date 2023/7/18 0:17
*/
@Configuration
public class ExtensionAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ExtensionRepository.class)
public ExtensionRepository repository() {
return new ExtensionRepository();
}
@Bean
@ConditionalOnMissingBean(ExtensionExecutor.class)
public ExtensionSelectorExecutor executor() {
return new ExtensionSelectorExecutor();
}
@Bean
@ConditionalOnMissingBean(ExtensionRegister.class)
public ExtensionRegister register() {
return new ExtensionRegister();
}
}
可以通过下面这种方式去自动注入,但是要注意在 spring.factories 中添加配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xdh.extension.config.ExtensionAutoConfiguration
/**
* @author coderxdh
* @date 2023/7/18 0:17
*/
@ComponentScan(basePackages = {"com.xdh.extension"})
@Configuration
public class ExtensionAutoConfiguration {
}
场景测试
- 还是用上一篇的业务场景(根据不同的业务场景,选择不同的跨区判断逻辑来获取跨区幅度)进行测试
- 但是这次增加一个抽象实现类,避免每一个实现类都要实现接口中的方法(借鉴 AQS 的思想)
定义业务接口
/**
* @author coderxdh
* @date 2023/7/13 23:44
*/
public interface CrossRegionService extends ExtensionPointI {
CrossRegionResult getCrossRegion(CrossRegionRequest crossRegionRequest);
String someOtherMethod1(String name);
void someOtherMethod2(String name);
}
定义抽象类,重写接口的方法
/**
* 定义抽象类,重写接口的方法
*
* @author coderxdh
* @date 2023/7/20 0:17
*/
public abstract class AbstractCrossRegionService implements CrossRegionService {
@Override
public CrossRegionResult getCrossRegion(CrossRegionRequest crossRegionRequest) {
throw new UnsupportedOperationException();
}
@Override
public String someOtherMethod1(String name) {
throw new UnsupportedOperationException();
}
@Override
public void someOtherMethod2(String name) {
throw new UnsupportedOperationException();
}
}
具体实现类,实现业务逻辑
/**
* @author coderxdh
* @date 2023/7/13 23:48
*/
@Extension(bizId = "regionNo")
@Slf4j
public class RegionNoCrossRegionService extends AbstractCrossRegionService {
@Override
public CrossRegionResult getCrossRegion(CrossRegionRequest crossRegionRequest) {
log.info("根据省区判断是否跨区");
return null;
}
@Override
public void someOtherMethod2(String name) {
System.out.println("重写了someOtherMethod2-" + name);
}
}
/**
* @author coderxdh
* @date 2023/7/13 23:50
*/
@Extension(bizId = "registerCity")
@Slf4j
public class RegisterCityCrossRegionService extends AbstractCrossRegionService {
@Override
public CrossRegionResult getCrossRegion(CrossRegionRequest crossRegionRequest) {
log.info("根据注册城市判断是否跨区");
return null;
}
@Override
public String someOtherMethod1(String name) {
return "重写了someOtherMethod1-" + name;
}
}
测试
@SpringBootTest
class DesignDemoApplicationTests {
@Resource
private ExtensionSelectorExecutor extensionSelectorExecutor;
@Test
void testCrossRegion() {
CrossRegionRequest crossRegionRequest = new CrossRegionRequest();
crossRegionRequest.setBizCode(CrossRegionBizEnum.JUDGE_BU_REGISTER_CITY.getValue());
CrossRegionResult crossRegionResult = extensionSelectorExecutor.execute(CrossRegionService.class, CrossRegionBizEnum.getEnumByValue(crossRegionRequest.getBizCode()),
crossRegionService -> {
return crossRegionService.getCrossRegion(crossRegionRequest);
});
CrossRegionRequest crossRegionRequest2 = new CrossRegionRequest();
crossRegionRequest.setBizCode(CrossRegionBizEnum.JUDGE_BY_REGION_NO.getValue());
String result = extensionSelectorExecutor.execute(CrossRegionService.class, CrossRegionBizEnum.getEnumByValue(crossRegionRequest2.getBizCode()),
crossRegionService -> {
return crossRegionService.someOtherMethod1("crossRegionRequest");
});
}
}
- 运行结果