首先简单说一下云平台的业务功能:云平台上提供了多种多样的服务,这些服务可能来源于不同的供应商。也就是说对于某一种服务来说,可能会有多个供应商提供相同的服务;而对于某一个供应商来说,它可能提供多种服务。
之前的云平台中有一个插件机制,使用插件机制解决了供应商与系统的耦合度问题。系统抽象了服务层,服务层在合适的时机加载插件以调用供应商的服务。
加载相关的代码大概类似这样:
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表现一致了。
该方案工作正常。