自定义扩展点插件 extension-plugin

232 阅读4分钟

在上一篇中我们讲了阿里的 cola-component-extension-starter,但是他限制了定义的接口和其实现类必须以 ExePt 结尾 这个对于有强迫症的人来说很难受,因此我们自己实现一个简易版的

代码结构

image.png

源码

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 的思想)

image.png

定义业务接口

/**
 * @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");
                });

    }
}
  • 运行结果

image.png