• cola架构:一种扩展点的实现思路浅析


    目录

    1.扩展点使用实例

    2.主要技术点

    2.1 注解加持

    2.2 注解解析

    2.3 扩展点路由


    在实际项目中,我们经常使用策略模式、或者状态模式来隔离同一接口下不同的实现逻辑,进而消除代码中ifelse硬编码分支,使代码结构更清晰,也大大提升了代码可读性;同时也满足了“开闭原则”,具备更高的可扩展性;

    在cola架构中,给出了一种“扩展点”的思路,本质还是策略模式的实现方式,通过“扩展点注解”的组装方式将策略模式实现类注册到容器中,供后续场景逻辑决策使用;

    1.扩展点使用实例

    首先通过实例了解下,cola 扩展点的使用方式:

    1.首先定义一个SomeExtPt接口,并实现ExtensionPointI接口

    1. public interface SomeExtPt extends ExtensionPointI {
    2. public void doSomeThing();
    3. }

    2.具体实现SomeExtPt接口,这里给出了2个实现类,如下:

    1. @Extension(bizId = "A")
    2. @Component
    3. public class SomeExtensionA implements SomeExtPt {
    4. @Override
    5. public void doSomeThing() {
    6. System.out.println("SomeExtensionA::doSomething");
    7. }
    8. }
    1. @Extension(bizId = "B")
    2. @Component
    3. public class SomeExtensionB implements SomeExtPt {
    4. @Override
    5. public void doSomeThing() {
    6. System.out.println("SomeExtensionB::doSomething");
    7. }
    8. }

    3.测试方法:

    1. @RunWith(SpringJUnit4ClassRunner.class)
    2. @SpringBootTest(classes = Application.class)
    3. public class ExtensionRegisterTest {
    4. @Resource
    5. private ExtensionRegister register;
    6. @Resource
    7. private ExtensionExecutor executor;
    8. @Test
    9. public void test() {
    10. SomeExtPt extA = new SomeExtensionA();
    11. register.doRegistration(extA);
    12. SomeExtPt extB = CglibProxyFactory.createProxy(new SomeExtensionB());
    13. register.doRegistration(extB);
    14. executor.executeVoid(SomeExtPt.class, BizScenario.valueOf("A"), SomeExtPt::doSomeThing);
    15. executor.executeVoid(SomeExtPt.class, BizScenario.valueOf("B"), SomeExtPt::doSomeThing);
    16. }
    17. }
    1. public class CglibProxyFactory {
    2. public static T createProxy(T object) {
    3. Enhancer enhancer = new Enhancer();
    4. enhancer.setSuperclass(object.getClass());
    5. enhancer.setCallback(new ProxyCallback(object));
    6. return (T) enhancer.create();
    7. }
    8. public static class ProxyCallback implements MethodInterceptor {
    9. private Object target;
    10. public ProxyCallback(Object target) {
    11. this.target = target;
    12. }
    13. @Override
    14. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    15. System.out.println("ProxyObject::before");
    16. Object object = proxy.invoke(target, args);
    17. System.out.println("ProxyObject::after");
    18. return object;
    19. }
    20. }
    21. }

    2.主要技术点

    2.1 注解加持

    上述具体策略接口实现方法标注了扩展点注解:@Extension

    1. @Inherited
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Target({ElementType.TYPE})
    4. @Repeatable(Extensions.class)
    5. @Component
    6. public @interface Extension {
    7. String bizId() default BizScenario.DEFAULT_BIZ_ID;
    8. String useCase() default BizScenario.DEFAULT_USE_CASE;
    9. String scenario() default BizScenario.DEFAULT_SCENARIO;
    10. }

    所有的具体实现方法都需要标注该注解,表明该类属于一个扩展点;同时,由于标注了@Component注解,表明每一个扩展点也是一个bean实例;

    2.2 注解解析

    扩展点注解的解析工作主要借助类ExtensionBootstrap和ExtensionRegister完成:

    1. @Component
    2. public class ExtensionBootstrap implements ApplicationContextAware {
    3. @Resource
    4. private ExtensionRegister extensionRegister;
    5. private ApplicationContext applicationContext;
    6. @PostConstruct
    7. public void init(){
    8. Map extensionBeans = applicationContext.getBeansWithAnnotation(Extension.class);
    9. extensionBeans.values().forEach(
    10. extension -> extensionRegister.doRegistration((ExtensionPointI) extension)
    11. );
    12. // handle @Extensions annotation
    13. Map extensionsBeans = applicationContext.getBeansWithAnnotation(Extensions.class);
    14. extensionsBeans.values().forEach( extension -> extensionRegister.doRegistrationExtensions((ExtensionPointI) extension));
    15. }
    16. @Override
    17. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    18. this.applicationContext = applicationContext;
    19. }
    20. }
    1. @Component
    2. public class ExtensionRegister {
    3. /**
    4. * 扩展点接口名称不合法
    5. */
    6. private static final String EXTENSION_INTERFACE_NAME_ILLEGAL = "extension_interface_name_illegal";
    7. /**
    8. * 扩展点不合法
    9. */
    10. private static final String EXTENSION_ILLEGAL = "extension_illegal";
    11. /**
    12. * 扩展点定义重复
    13. */
    14. private static final String EXTENSION_DEFINE_DUPLICATE = "extension_define_duplicate";
    15. @Resource
    16. private ExtensionRepository extensionRepository;
    17. public final static String EXTENSION_EXTPT_NAMING = "ExtPt";
    18. public void doRegistration(ExtensionPointI extensionObject) {
    19. Class extensionClz = extensionObject.getClass();
    20. if (AopUtils.isAopProxy(extensionObject)) {
    21. extensionClz = ClassUtils.getUserClass(extensionObject);
    22. }
    23. Extension extensionAnn = AnnotationUtils.findAnnotation(extensionClz, Extension.class);
    24. BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario());
    25. ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());
    26. ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);
    27. if (preVal != null) {
    28. String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;
    29. throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);
    30. }
    31. }
    32. public void doRegistrationExtensions(ExtensionPointI extensionObject){
    33. Class extensionClz = extensionObject.getClass();
    34. if (AopUtils.isAopProxy(extensionObject)) {
    35. extensionClz = ClassUtils.getUserClass(extensionObject);
    36. }
    37. Extensions extensionsAnnotation = AnnotationUtils.findAnnotation(extensionClz, Extensions.class);
    38. Extension[] extensions = extensionsAnnotation.value();
    39. if (!ObjectUtils.isEmpty(extensions)){
    40. for (Extension extensionAnn : extensions) {
    41. BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario());
    42. ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());
    43. ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);
    44. if (preVal != null) {
    45. String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;
    46. throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);
    47. }
    48. }
    49. }
    50. //
    51. String[] bizIds = extensionsAnnotation.bizId();
    52. String[] useCases = extensionsAnnotation.useCase();
    53. String[] scenarios = extensionsAnnotation.scenario();
    54. for (String bizId : bizIds) {
    55. for (String useCase : useCases) {
    56. for (String scenario : scenarios) {
    57. BizScenario bizScenario = BizScenario.valueOf(bizId, useCase, scenario);
    58. ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());
    59. ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);
    60. if (preVal != null) {
    61. String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;
    62. throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);
    63. }
    64. }
    65. }
    66. }
    67. }
    68. /**
    69. * @param targetClz
    70. * @return
    71. */
    72. private String calculateExtensionPoint(Class targetClz) {
    73. Class[] interfaces = ClassUtils.getAllInterfacesForClass(targetClz);
    74. if (interfaces == null || interfaces.length == 0) {
    75. throw new ExtensionException(EXTENSION_ILLEGAL, "Please assign a extension point interface for " + targetClz);
    76. }
    77. for (Class intf : interfaces) {
    78. String extensionPoint = intf.getSimpleName();
    79. if (extensionPoint.contains(EXTENSION_EXTPT_NAMING)) {
    80. return intf.getName();
    81. }
    82. }
    83. String errMessage = "Your name of ExtensionPoint for " + targetClz +
    84. " is not valid, must be end of " + EXTENSION_EXTPT_NAMING;
    85. throw new ExtensionException(EXTENSION_INTERFACE_NAME_ILLEGAL, errMessage);
    86. }
    87. }

    最终将扩展点和决策条件的映射关系存储到ExtensionRepository中:

    1. @Component
    2. public class ExtensionRepository {
    3. public Map getExtensionRepo() {
    4. return extensionRepo;
    5. }
    6. private Map extensionRepo = new HashMap<>();
    7. }

    2.3 扩展点路由

    在实际业务场景调度过程中,会调用ExtensionExecutor的方法locateExtension完成扩展点的查找,最终执行扩展点逻辑;

    1. @Component
    2. public class ExtensionExecutor extends AbstractComponentExecutor {
    3. private static final String EXTENSION_NOT_FOUND = "extension_not_found";
    4. private Logger logger = LoggerFactory.getLogger(ExtensionExecutor.class);
    5. @Resource
    6. private ExtensionRepository extensionRepository;
    7. @Override
    8. protected C locateComponent(Class targetClz, BizScenario bizScenario) {
    9. C extension = locateExtension(targetClz, bizScenario);
    10. logger.debug("[Located Extension]: " + extension.getClass().getSimpleName());
    11. return extension;
    12. }
    13. /**
    14. * if the bizScenarioUniqueIdentity is "ali.tmall.supermarket"
    15. *

    16. * the search path is as below:
    17. * 1、first try to get extension by "ali.tmall.supermarket", if get, return it.
    18. * 2、loop try to get extension by "ali.tmall", if get, return it.
    19. * 3、loop try to get extension by "ali", if get, return it.
    20. * 4、if not found, try the default extension
    21. *
    22. * @param targetClz
    23. */
    24. protected Ext locateExtension(Class targetClz, BizScenario bizScenario) {
    25. checkNull(bizScenario);
    26. Ext extension;
    27. logger.debug("BizScenario in locateExtension is : " + bizScenario.getUniqueIdentity());
    28. // first try with full namespace
    29. extension = firstTry(targetClz, bizScenario);
    30. if (extension != null) {
    31. return extension;
    32. }
    33. // second try with default scenario
    34. extension = secondTry(targetClz, bizScenario);
    35. if (extension != null) {
    36. return extension;
    37. }
    38. // third try with default use case + default scenario
    39. extension = defaultUseCaseTry(targetClz, bizScenario);
    40. if (extension != null) {
    41. return extension;
    42. }
    43. String errMessage = "Can not find extension with ExtensionPoint: " +
    44. targetClz + " BizScenario:" + bizScenario.getUniqueIdentity();
    45. throw new ExtensionException(EXTENSION_NOT_FOUND, errMessage);
    46. }
    47. /**
    48. * first try with full namespace
    49. *

    50. * example: biz1.useCase1.scenario1
    51. */
    52. private Ext firstTry(Class targetClz, BizScenario bizScenario) {
    53. logger.debug("First trying with " + bizScenario.getUniqueIdentity());
    54. return locate(targetClz.getName(), bizScenario.getUniqueIdentity());
    55. }
    56. /**
    57. * second try with default scenario
    58. *

    59. * example: biz1.useCase1.#defaultScenario#
    60. */
    61. private Ext secondTry(Class targetClz, BizScenario bizScenario) {
    62. logger.debug("Second trying with " + bizScenario.getIdentityWithDefaultScenario());
    63. return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultScenario());
    64. }
    65. /**
    66. * third try with default use case + default scenario
    67. *

    68. * example: biz1.#defaultUseCase#.#defaultScenario#
    69. */
    70. private Ext defaultUseCaseTry(Class targetClz, BizScenario bizScenario) {
    71. logger.debug("Third trying with " + bizScenario.getIdentityWithDefaultUseCase());
    72. return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultUseCase());
    73. }
    74. private Ext locate(String name, String uniqueIdentity) {
    75. final Ext ext = (Ext) extensionRepository.getExtensionRepo().
    76. get(new ExtensionCoordinate(name, uniqueIdentity));
    77. return ext;
    78. }
    79. private void checkNull(BizScenario bizScenario) {
    80. if (bizScenario == null) {
    81. throw new IllegalArgumentException("BizScenario can not be null for extension");
    82. }
    83. }
    84. }

  • 相关阅读:
    jdk线程池ThreadPoolExecutor优雅停止原理解析(自己动手实现线程池)(二)
    STC单片机17——adc 8032
    领导大规模敏捷 - Leading SAFe认证,SAFe认证Leading SAFe官方认证培训班
    CountDownLatch
    【深度学习相关知识】
    测试开发工程师到底是做什么的?
    OpManager 帮助排查网络延迟问题
    m基于随机接入代价的异构网络速率分配算法matlab仿真
    C#实验二
    MySQL-函数
  • 原文地址:https://blog.csdn.net/supzhili/article/details/133843274