方法一
SpringContextUtil
public class SpringContextUtil {
private static ApplicationContext applicationContext;
//獲取上下文
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//設置上下文
public static void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtil.applicationContext = applicationContext;
}
//通過名字獲取上下文中的bean
public static Object getBean(String name){
return applicationContext.getBean(name);
}
//通過類型獲取上下文中的bean
public static Object getBean(Class<?> requiredType){
return applicationContext.getBean(requiredType);
}
}
啟動類
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext app = SpringApplication.run(Application.class, args);
SpringContextUtil.setApplicationContext(app);
}
}
測試bean
@Component
public class TestService {
public String doService(String contxt){
System.err.printf(contxt+"hello service");
return "hello service";
}
}
//無注入
public class TestController implements InitializingBean {
@Autowired
private TestService testService;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("我是動態注冊的你,不是容器啟動的時候注冊的你");
}
public String toAction(String content){
return "-->" + testService.doService(content);
}
}
測試
@RestController
public class CallCSBController {
@GetMapping("/bean")
public String registerBean() {
//將applicationContext轉換為ConfigurableApplicationContext
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) SpringContextUtil.getApplicationContext();
// 獲取bean工廠並轉換為DefaultListableBeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
// 通過BeanDefinitionBuilder創建bean定義
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestController.class);
// 設置屬性userService,此屬性引用已經定義的bean:userService,這里userService已經被spring容器管理了.
// beanDefinitionBuilder.addPropertyReference("testService", "testService");
// 注冊bean
defaultListableBeanFactory.registerBeanDefinition("testController", beanDefinitionBuilder.getRawBeanDefinition());
TestController userController = (TestController) SpringContextUtil.getBean("testController");
return userController.toAction("動態注冊生成調用");
//刪除bean.
//defaultListableBeanFactory.removeBeanDefinition("testService");
}
}
以上參考
鏈接:https://www.jianshu.com/p/41c716e7c31b
方法二(略有不同)
工具類
package com.theeternity.beans.applicationContextRegister;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @program: apiboot
* @description: 獲取ApplicationContext, 實現動態注入bean
* @author: TheEternity Zhang
* @create: 2019-06-22 12:15
*/
@Component
@Slf4j
public class ApplicationContextRegister implements ApplicationContextAware {
private static ApplicationContext APPLICATION_CONTEXT;
/**
* 設置spring上下文
* @param applicationContext spring上下文
* @throws BeansException
* */
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.debug("ApplicationContext registed-->{}", applicationContext);
APPLICATION_CONTEXT = applicationContext;
}
/**
* 獲取容器
* @return
*/
public static ApplicationContext getApplicationContext() {
return APPLICATION_CONTEXT;
}
/**
* 獲取容器對象
* @param type
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> type) {
return APPLICATION_CONTEXT.getBean(type);
}
public static <T> T getBean(String name,Class<T> clazz){
return APPLICATION_CONTEXT.getBean(name, clazz);
}
public static Object getBean(String name){
return APPLICATION_CONTEXT.getBean(name);
}
}
測試bean
@Component
public class TestService {
public String doService(String contxt){
System.err.printf(contxt+"hello service");
return "hello service";
}
}
//無注入
public class TestController implements InitializingBean {
@Autowired
private TestService testService;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("我是動態注冊的你,不是容器啟動的時候注冊的你");
}
public String toAction(String content){
return "-->" + testService.doService(content);
}
}
測試
@RestController
public class CallCSBController {
@GetMapping("/bean")
public String registerBean() {
//將applicationContext轉換為ConfigurableApplicationContext
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) ApplicationContextRegister.getApplicationContext();
// 獲取bean工廠並轉換為DefaultListableBeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
// 通過BeanDefinitionBuilder創建bean定義
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestController.class);
// 設置屬性userService,此屬性引用已經定義的bean:userService,這里userService已經被spring容器管理了.
// beanDefinitionBuilder.addPropertyReference("testService", "testService");
// 注冊bean
defaultListableBeanFactory.registerBeanDefinition("testController", beanDefinitionBuilder.getRawBeanDefinition());
TestController userController = (TestController) ApplicationContextRegister.getBean("testController");
return userController.toAction("動態注冊生成調用");
//刪除bean.
//defaultListableBeanFactory.removeBeanDefinition("testService");
}
第一種方法的另外一種形式
/**
//獲取ApplicationContext
ApplicationContext ctx=ApplicationContextRegister.getApplicationContext();
//獲取BeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
//創建bean信息.
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name","張三");
//動態注冊bean.
defaultListableBeanFactory.registerBeanDefinition("testService", beanDefinitionBuilder.getBeanDefinition());
//獲取動態注冊的bean.
TestService testService =ctx.getBean(TestService.class);、testService.print();
*/
@GetMapping("/bean2")
public String registerBean2() {
TestController userController = (TestController) ApplicationContextRegister.getBean(TestController.class);
return userController.toAction("動態注冊生成調用");
}
}
以上參考:
主力:https://www.jianshu.com/p/41c716e7c31b
輔助:https://www.jb51.net/article/140157.htm
拓展理解
我們通過getBean來獲得對象,但這些對象都是事先定義好的,我們有時候要在程序中動態的加入對象.因為如果采用配置文件或者注解,我們要加入對象的話,還要重啟服務,如果我們想要避免這一情況就得采用動態處理bean,包括:動態注入,動態刪除。
本節大綱 :
(1)動態注入bean思路;
(2)動態注入實現代碼;
(3)多次注入同一個bean的情況;
(4)動態刪除;
接下來我們看下具體的內容:
(1)動態注入bean思路;
在具體進行代碼實現的時候,我們要知道,Spring管理bean的對象是BeanFactory,具體的是DefaultListableBeanFactory,在這個類當中有一個注入bean的方法:registerBeanDefinition,在調用registerBeanDefinition方法時,需要BeanDefinition參數,那么這個參數怎么獲取呢?Spring提供了BeanDefinitionBuilder可以構建一個BeanDefinition,那么我們的問題就是如何獲取BeanFactory了,這個就很簡單了,只要獲取到ApplicationContext對象即可獲取到BeanFacory了。
(2)動態注入實現代碼;
綜上所述,如果我們要編寫一個簡單里的例子的話,那么分以個幾個步驟進行編碼即可進行動態注入了:
<1>. 獲取ApplicationContext;
<2>. 通過ApplicationContext獲取到BeanFacotory;
<3>. 通過BeanDefinitionBuilder構建BeanDefiniton;
<4>. 調用beanFactory的registerBeanDefinition注入beanDefinition;
<5>. 使用ApplicationContext.getBean獲取bean進行測試;
很明顯我們需要先定義個類進行測試,比如TestService代碼如下:
package com.kfit.demo.service;
public class TestService {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void print(){
System.out.println("動態載入bean,name="+name);
}
}
注意:這里沒有使用@Service和配置文件進行注入TestService。
那么下面我們的目標就是動態注入TestService了,根據以上的分析,我們進行編碼,具體代碼如下:
//獲取context. -- Angel -守護天使
ApplicationContext ctx = (ApplicationContext) SpringApplication.run(App.class, args);
//獲取BeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
//創建bean信息.
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name","張三");
//動態注冊bean.
defaultListableBeanFactory.registerBeanDefinition("testService", beanDefinitionBuilder.getBeanDefinition());
//獲取動態注冊的bean.
TestService testService =ctx.getBean(TestService.class);
testService.print();
執行代碼我們會在控制台看到如下打印信息:
動態載入bean,name=張三
到這里,就證明我們的代碼很成功了。
(3)多次注入同一個bean的情況;
多次注入同一個bean的,如果beanName不一樣的話,那么會產生兩個Bean;如果beanName一樣的話,后面注入的會覆蓋前面的。
第一種情況:beanName一樣的代碼:
beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
defaultListableBeanFactory.registerBeanDefinition("testService", beanDefinitionBuilder.getBeanDefinition());
TestService testService =ctx.getBean(TestService.class);
testService.print();
運行看控制台: 動態載入bean,name=李四
第二種情況:beanName不一樣的代碼:
beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
defaultListableBeanFactory.registerBeanDefinition("testService1", beanDefinitionBuilder.getBeanDefinition());
TestService testService =ctx.getBean(TestService.class);
testService.print();
此時如果沒有更改別的代碼直接運行的話,是會報如下錯誤的:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.kfit.demo.service.TestService] is defined: expected single matching bean but found 2: testService1,testService
大體意思就是在getBean的時候,找到了兩個bean,這時候就不知道要獲取哪個了,所以在獲取的時候,我們就要指定我們是要獲取的testService還是testService1,只需要修改一句代碼:
將代碼:
TestService testService =ctx.getBean(TestService.class);
修改為:
TestService testService =ctx.getBean("testService");
(4)動態刪除;
相對於動態注入,動態刪除就很簡單了,直接奉上代碼:
//刪除bean.
defaultListableBeanFactory.removeBeanDefinition("testService");
