执子之手

与子偕老


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索
close

迁移到SpringBoot 01 - 插件加载

时间: 2018-05-09   |   分类: 开发     |   阅读: 1324 字 ~3分钟   |   访问: 0

首先简单说一下云平台的业务功能:云平台上提供了多种多样的服务,这些服务可能来源于不同的供应商。也就是说对于某一种服务来说,可能会有多个供应商提供相同的服务;而对于某一个供应商来说,它可能提供多种服务。

之前的云平台中有一个插件机制,使用插件机制解决了供应商与系统的耦合度问题。系统抽象了服务层,服务层在合适的时机加载插件以调用供应商的服务。

加载相关的代码大概类似这样:

 1...
 2ClassLoader localClassLoader = Thread.currentThread().getContextClassLoader();
 3for (String str : providerClassNames) {
 4    try {
 5        Class<?> localClass = localClassLoader.loadClass(str);
 6        XXXServiceProvider localProvider = (XXXServiceProvider) localClass.newInstance();
 7        localProvider.initial();// 初始化插件
 8    } catch (Exception e) {
 9		...
10    }
11}
12...

providerClassNames是一个数组,其中并所有插件的主类名字。通过获取到的ClassLoader,对插件主类进行加载,从而获得插件对象。

但是上面这个加载机制没有解决Spring诸如的问题,使得插件代码中无法通过@Value这种形式注入配置,所以每个插件中必须自行读取配置文件,类似这样:

 1private void initProperties() {
 2        InputStream inputStream = getClass().getResourceAsStream("/config.properties");
 3        try {
 4            Properties prop = new Properties();
 5            prop.load(inputStream);
 6            reqUrl = prop.getProperty("xxx.reqUrl").trim();
 7            userId = prop.getProperty("xxx.userId").trim();
 8			...
 9        } catch (IOException e) {
10            logger.error("init haoduo auth provider error! ", e);
11        } finally {
12            if (inputStream != null) {
13                try {
14                    inputStream.close();
15                } catch (IOException e) {
16                    logger.warn("close config.properties inputStream error! ", e);
17                }
18            }
19        }
20    }

这种方式用起来还是挺繁琐的,所以很希望改造成能够和普通Spring类一样使用注入方式。

1. 改造方案

要实现上述目标,合理的方案是将插件定义成Spring的Bean。但是考虑到插件的与系统的耦合度问题,所以也不太考虑直接将插件定义成Bean,写在Spring的配置文件中。那剩余的解决方案只能是将插件动态定义成Bean。通过在网上搜索解决方案,找到了两种方案。

1.1 BeanFactoryPostProcessor

最初找到的是BeanFactoryPostProcessor方案:BeanFactoryPostProcessor在BeanFactory创建成功、初始化之后、Bean真正创建之前执行。一般通过该回调函数中修改Bean定义或者定义新的Bean。

理论上讲可以在此节点创建新的Bean定义,例如根据插件列表创建对应的插件Bean。但是问题在于这个节点@Value是无效的(@Value也是由BeanFactory处理的),在这个节点Bean还没有创建,更加不可能注入配置值,因此也就无法从配置文件中拿到插件列表这一配置项信息。

方案失败。在此不做过多描述,具体可以参考网上的资料。

1.2 直接在动态加载的地方注册Bean

最后找到的方案是先动态注册Bean然后加载,代码大概如下:

 1AutowireCapableBeanFactory bf = applicationContext.getAutowireCapableBeanFactory();
 2            for (String str : this.providers) {
 3                try {
 4                    // 定义BeanDefinition,根据providers列表动态添加Bean定义
 5                    String beanName = getSimpleClassName(str);
 6                    GenericBeanDefinition bd = new GenericBeanDefinition();
 7                    bd.setBeanClassName(str);
 8                    ((DefaultListableBeanFactory)bf).registerBeanDefinition(beanName, bd);
 9                    // 使用applicationContext加载刚刚定义的Bean,此时应该完成了@Autowired的注入工作
10                    XXXServiceProvider localProvider = (XXXServiceProvider) applicationContext.getBean(beanName);
11                    localProvider.initial();// 初始化插件
12                    logger.info("XXXServiceProvider {} is added to list.", localProvider.getProviderName());
13                } catch (Exception e) {
14					...
15                }
16            }

上面的代码使用applicationContext获取的autowireCapableBeanFactory,先创建Bean定义(registerBeanDefinition),然后获取该Bean(applicationContext.getBean)。这样处理之后,除了Bean定义是动态创建的意外,就和一个普通的Spring Bean表现一致了。

该方案工作正常。

附录A、参考资料

  • Spring - Dynamically register beans
#Spring# #Springboot#
迁移到SpringBoot 02 - 异常处理
迁移到SpringBoot 00 - 背景介绍
  • 文章目录
  • 站点概览
Orchidflower

Orchidflower

Do one thing at a time, and do well.

77 日志
6 分类
84 标签
GitHub 知乎 OSC 豆瓣
  • 1. 改造方案
    • 1.1 BeanFactoryPostProcessor
    • 1.2 直接在动态加载的地方注册Bean
  • 附录A、参考资料
© 2009 - 2024 执子之手
Powered by - Hugo v0.113.0
Theme by - NexT
ICP - 鲁ICP备17006463号-1
0%