目录
在实际项目中,我们经常使用策略模式、或者状态模式来隔离同一接口下不同的实现逻辑,进而消除代码中ifelse硬编码分支,使代码结构更清晰,也大大提升了代码可读性;同时也满足了“开闭原则”,具备更高的可扩展性;
在cola架构中,给出了一种“扩展点”的思路,本质还是策略模式的实现方式,通过“扩展点注解”的组装方式将策略模式实现类注册到容器中,供后续场景逻辑决策使用;
首先通过实例了解下,cola 扩展点的使用方式:
1.首先定义一个SomeExtPt接口,并实现ExtensionPointI接口
- public interface SomeExtPt extends ExtensionPointI {
-
- public void doSomeThing();
- }
2.具体实现SomeExtPt接口,这里给出了2个实现类,如下:
- @Extension(bizId = "A")
- @Component
- public class SomeExtensionA implements SomeExtPt {
-
- @Override
- public void doSomeThing() {
- System.out.println("SomeExtensionA::doSomething");
- }
-
- }
- @Extension(bizId = "B")
- @Component
- public class SomeExtensionB implements SomeExtPt {
-
- @Override
- public void doSomeThing() {
- System.out.println("SomeExtensionB::doSomething");
- }
-
- }
3.测试方法:
- @RunWith(SpringJUnit4ClassRunner.class)
- @SpringBootTest(classes = Application.class)
- public class ExtensionRegisterTest {
-
- @Resource
- private ExtensionRegister register;
-
- @Resource
- private ExtensionExecutor executor;
-
- @Test
- public void test() {
- SomeExtPt extA = new SomeExtensionA();
- register.doRegistration(extA);
-
- SomeExtPt extB = CglibProxyFactory.createProxy(new SomeExtensionB());
- register.doRegistration(extB);
-
- executor.executeVoid(SomeExtPt.class, BizScenario.valueOf("A"), SomeExtPt::doSomeThing);
- executor.executeVoid(SomeExtPt.class, BizScenario.valueOf("B"), SomeExtPt::doSomeThing);
- }
-
- }
- public class CglibProxyFactory {
-
- public static
T createProxy(T object) { - Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(object.getClass());
- enhancer.setCallback(new ProxyCallback(object));
- return (T) enhancer.create();
- }
-
- public static class ProxyCallback implements MethodInterceptor {
-
- private Object target;
-
- public ProxyCallback(Object target) {
- this.target = target;
- }
-
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- System.out.println("ProxyObject::before");
- Object object = proxy.invoke(target, args);
- System.out.println("ProxyObject::after");
- return object;
- }
- }
- }
上述具体策略接口实现方法标注了扩展点注解:@Extension
- @Inherited
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE})
- @Repeatable(Extensions.class)
- @Component
- public @interface Extension {
- String bizId() default BizScenario.DEFAULT_BIZ_ID;
- String useCase() default BizScenario.DEFAULT_USE_CASE;
- String scenario() default BizScenario.DEFAULT_SCENARIO;
- }
所有的具体实现方法都需要标注该注解,表明该类属于一个扩展点;同时,由于标注了@Component注解,表明每一个扩展点也是一个bean实例;
扩展点注解的解析工作主要借助类ExtensionBootstrap和ExtensionRegister完成:
- @Component
- public class ExtensionBootstrap implements ApplicationContextAware {
-
- @Resource
- private ExtensionRegister extensionRegister;
-
- private ApplicationContext applicationContext;
-
- @PostConstruct
- public void init(){
- Map
extensionBeans = applicationContext.getBeansWithAnnotation(Extension.class); - extensionBeans.values().forEach(
- extension -> extensionRegister.doRegistration((ExtensionPointI) extension)
- );
-
- // handle @Extensions annotation
- Map
extensionsBeans = applicationContext.getBeansWithAnnotation(Extensions.class); - extensionsBeans.values().forEach( extension -> extensionRegister.doRegistrationExtensions((ExtensionPointI) extension));
- }
-
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = applicationContext;
- }
- }
- @Component
- public class ExtensionRegister {
-
- /**
- * 扩展点接口名称不合法
- */
- private static final String EXTENSION_INTERFACE_NAME_ILLEGAL = "extension_interface_name_illegal";
- /**
- * 扩展点不合法
- */
- private static final String EXTENSION_ILLEGAL = "extension_illegal";
- /**
- * 扩展点定义重复
- */
- private static final String EXTENSION_DEFINE_DUPLICATE = "extension_define_duplicate";
-
- @Resource
- private ExtensionRepository extensionRepository;
-
- public final static String EXTENSION_EXTPT_NAMING = "ExtPt";
-
-
- public void doRegistration(ExtensionPointI extensionObject) {
- Class> extensionClz = extensionObject.getClass();
- if (AopUtils.isAopProxy(extensionObject)) {
- extensionClz = ClassUtils.getUserClass(extensionObject);
- }
- Extension extensionAnn = AnnotationUtils.findAnnotation(extensionClz, Extension.class);
- BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario());
- ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());
- ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);
- if (preVal != null) {
- String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;
- throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);
- }
- }
-
- public void doRegistrationExtensions(ExtensionPointI extensionObject){
- Class> extensionClz = extensionObject.getClass();
- if (AopUtils.isAopProxy(extensionObject)) {
- extensionClz = ClassUtils.getUserClass(extensionObject);
- }
-
- Extensions extensionsAnnotation = AnnotationUtils.findAnnotation(extensionClz, Extensions.class);
- Extension[] extensions = extensionsAnnotation.value();
- if (!ObjectUtils.isEmpty(extensions)){
- for (Extension extensionAnn : extensions) {
- BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario());
- ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());
- ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);
- if (preVal != null) {
- String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;
- throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);
- }
- }
- }
-
- //
- String[] bizIds = extensionsAnnotation.bizId();
- String[] useCases = extensionsAnnotation.useCase();
- String[] scenarios = extensionsAnnotation.scenario();
- for (String bizId : bizIds) {
- for (String useCase : useCases) {
- for (String scenario : scenarios) {
- BizScenario bizScenario = BizScenario.valueOf(bizId, useCase, scenario);
- ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());
- ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);
- if (preVal != null) {
- String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;
- throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);
- }
- }
- }
- }
- }
-
- /**
- * @param targetClz
- * @return
- */
- private String calculateExtensionPoint(Class> targetClz) {
- Class>[] interfaces = ClassUtils.getAllInterfacesForClass(targetClz);
- if (interfaces == null || interfaces.length == 0) {
- throw new ExtensionException(EXTENSION_ILLEGAL, "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();
- }
- }
- String errMessage = "Your name of ExtensionPoint for " + targetClz +
- " is not valid, must be end of " + EXTENSION_EXTPT_NAMING;
- throw new ExtensionException(EXTENSION_INTERFACE_NAME_ILLEGAL, errMessage);
- }
-
- }
最终将扩展点和决策条件的映射关系存储到ExtensionRepository中:
- @Component
- public class ExtensionRepository {
-
- public Map
getExtensionRepo() { - return extensionRepo;
- }
-
- private Map
extensionRepo = new HashMap<>(); -
-
- }
在实际业务场景调度过程中,会调用ExtensionExecutor的方法locateExtension完成扩展点的查找,最终执行扩展点逻辑;
- @Component
- public class ExtensionExecutor extends AbstractComponentExecutor {
-
- private static final String EXTENSION_NOT_FOUND = "extension_not_found";
-
- private Logger logger = LoggerFactory.getLogger(ExtensionExecutor.class);
-
- @Resource
- private ExtensionRepository extensionRepository;
-
- @Override
- protected
C locateComponent(Class targetClz, BizScenario bizScenario) { - C extension = locateExtension(targetClz, bizScenario);
- logger.debug("[Located Extension]: " + extension.getClass().getSimpleName());
- return extension;
- }
-
- /**
- * 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 locateExtension(Class targetClz, BizScenario bizScenario) { - checkNull(bizScenario);
-
- Ext extension;
-
- logger.debug("BizScenario in locateExtension is : " + bizScenario.getUniqueIdentity());
-
- // first try with full namespace
- extension = firstTry(targetClz, bizScenario);
- if (extension != null) {
- return extension;
- }
-
- // second try with default scenario
- extension = secondTry(targetClz, bizScenario);
- if (extension != null) {
- return extension;
- }
-
- // third try with default use case + default scenario
- extension = defaultUseCaseTry(targetClz, bizScenario);
- if (extension != null) {
- return extension;
- }
-
- String errMessage = "Can not find extension with ExtensionPoint: " +
- targetClz + " BizScenario:" + bizScenario.getUniqueIdentity();
- throw new ExtensionException(EXTENSION_NOT_FOUND, errMessage);
- }
-
- /**
- * first try with full namespace
- *
- * example: biz1.useCase1.scenario1
- */
- private
Ext firstTry(Class targetClz, BizScenario bizScenario) { - logger.debug("First trying with " + bizScenario.getUniqueIdentity());
- return locate(targetClz.getName(), bizScenario.getUniqueIdentity());
- }
-
- /**
- * second try with default scenario
- *
- * example: biz1.useCase1.#defaultScenario#
- */
- private
Ext secondTry(Class targetClz, BizScenario bizScenario) { - logger.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
Ext defaultUseCaseTry(Class targetClz, BizScenario bizScenario) { - logger.debug("Third trying with " + bizScenario.getIdentityWithDefaultUseCase());
- return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultUseCase());
- }
-
- private
Ext locate(String name, String uniqueIdentity) { - final Ext ext = (Ext) 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");
- }
- }
-
- }