Spring下动态注册和获取Bean
有时候需要在运行时动态注册Bean到Spring容器,并根据名称获取注册的Bean。比如我们自己的SAAS架构的系统需要调用ThingsBoard API和Thingsboard交互,就可以通过ThingsBoard提供的RestClient工具类。但这要求每个租户使用自己唯一的RestClient,为了达到此目的,系统启动时需要将每个租户的RestClient加载到Spring容器中以供租户随时使用,另外系统管理员可以在系统中随时创建新的租户,因此就需要在系统启动后运行过程随时可以注册新的RestClient到Spring容器中。
下面从运行时手动注册Bean到Spring容器以及从Spring容器中获取容器管理的Bean入手进行介绍。 运行时注册Bean到Spring容器
访问接口 /** * 注册bean到Spring容器。使用构造函数参数初始化bean。 * 备注:需要有默认构造器,即需要有无参构造器。 * @param beanName * @param clazz * @param constructorArgs */ public static void registerBean(String beanName, Class<?> clazz, Object... constructorArgs) { registerBean(beanName, clazz, new InitBean() { @Override public void init(BeanDefinitionBuilder beanDefinitionBuilder) { log.info("使用构造函数参数初始化class[{}]",clazz); if(constructorArgs!=null&&constructorArgs.length>0){ for (Object constructorArg : constructorArgs) { beanDefinitionBuilder.addConstructorArgValue(constructorArg); } } } }); } /** * 注册bean到spring容器中。使用属性参数初始化bean。 * @param beanName 名称 * @param clazz class */ public static void registerBean(String beanName, Class<?> clazz, Map propertyValueMap) { registerBean(beanName, clazz, new InitBean() { @Override public void init(BeanDefinitionBuilder beanDefinitionBuilder) { log.info("使用属性参数初始化class[{}]",clazz); if(propertyValueMap!=null){ propertyValueMap.forEach((k,v)->{ beanDefinitionBuilder.addPropertyValue(k, v); }); } } }); }
核心代码: private static void registerBean(String beanName, Class<?> clazz, InitBean initBean) { // 1. 检查是否存在重名的bean,如果存在打印警告日志,并且返回, if (defaultListableBeanFactory.containsBean(beanName)) { log.warn("The Bean [{}] for type [{}] is already exists. Please check.", beanName, clazz.getName()); return; } // 2. 通过BeanDefinitionBuilder创建bean定义 BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz); //3. 初始化Bean if (initBean != null) { initBean.init(beanDefinitionBuilder); } // 4. 注册bean defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition()); log.info("register bean [{}],Class [{}] success.", beanName, clazz); }
由于初始化Bean有2重方式,一种是设置Property的方式(必须有默认的构造函数),一种是构造函数的方式,为了避免重复的代码特写了回调类 InitBean public interface InitBean{ void init( BeanDefinitionBuilder beanDefinitionBuilder); }
ApplicationContext和DefaultListableBeanFactory的获取 @Slf4j public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext = null; private static DefaultListableBeanFactory defaultListableBeanFactory; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (SpringContextUtil.applicationContext == null) { SpringContextUtil.applicationContext = applicationContext; } //将applicationContext转换为ConfigurableApplicationContext ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext; // 获取bean工厂并转换为DefaultListableBeanFactory this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory(); log.info("init ApplicationContext and BeanFactory Success."); } ....Bean从Spring容器中的动态获取
提供三种方式从Spring 容器中获取bean,分别是根据bean的名称,bean的class类型(bean是gingleton的)根据bean的名称以及class类型。 public static Object getBean(String name) { return getApplicationContext().getBean(name); } public static T getBean(Class clazz) { return getApplicationContext().getBean(clazz); } public static T getBean(String name, Class clazz) { return getApplicationContext().getBean(name, clazz); }
如上,使用如上介绍的注册和获取Bean的方式就可以轻松获得,运行时动态注册和获取Bean的能力。
备注:在SpringBoot微服务启动时手动完成Bean的注册可以利用SpringBoot的提供的 org.springframework.CommandLineRunner或者 org.springframework.bootApplicationRunner`
参考示例: @Component @Slf4j public class TenantRestClientInit implements CommandLineRunner { @Override public void run(String... args) throws Exception { initSomeThings(); } private void initSomeThings(){ .... } } }