spring boot 動態注入bean


方法一

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"); 

拓展參考:

https://412887952-qq-com.iteye.com/blog/2348445


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM