解決Spring在Thread中註入Bean無效的問題

在Thread中註入Bean無效

在Spring項目中,有時需要新開線程完成一些復雜任務,而線程中可能需要註入一些服務。而通過Spring註入來管理和使用服務是較為合理的方式。但是若直接在Thread子類中通過註解方式註入Bean是無效的。

因為Spring本身默認Bean為單例模式構建,同時是非線程安全的,因此禁止瞭在Thread子類中的註入行為,因此在Thread中直接註入的bean是null的,會發生空指針錯誤。

以下分別列舉錯誤的註入方法和兩種解決方式。

錯誤的註入方法

@Controller
public class SomeController{
    @ResponseBody
    @RequestMapping("test")
    String testInjection(){
        // 直接創建並運行線程
        new SomeThread().start();
    }
}
// 直接編寫線程
public SomeThread extends Thread {
    @Autowired
    SomeService someService;
    @Override
    public void run(){
        // do something...
        someService.doSomething();
        // 此時 someService實例是null.
    }
}

報NullpointException。

通過封裝Thread子類註入

個人比較推薦這種方法,對外部代碼的影響較小。

@Controller
public class SomeController{
    // 通過註解註入封裝線程的Bean
    @AutoWired
    SomeThread someThread;
    @ResponseBody
    @RequestMapping("test")
    String testInjection(){
        // 通過註入的Bean啟動線程
        someThread.execute();
    }
}
@Component
public class SomeThread {
    // 封裝Bean中註入服務
    @AutoWired
    SomeService someService
    public void execute() {
        new Worker().start();
    }
    // 線程內部類,Thread或者Runnable均可
    private class Worker extends Thread {
        @Override
        public void run() {
            // do something...
            SomeThread.this.someService.doSomething();
            // 此時someService已被註入,非null.
        }
    }
}

正常調用someService。

通過外部引入

即在可以註入的地方先得到可用的實例,在通過Thread子類的構造函數引入。這樣會使得在進行代碼修改時,影響到每個使用Thread子類的代碼,修改工作量大。

@Controller
public class SomeController{
    // 通過註解註入Service
    @AutoWired
    SomeService someService;
    @ResponseBody
    @RequestMapping("test")
    String testInjection(){
        // 通過構造函數從外部引入
        new Worker(someService).start();
    }
}
public class SomeThread {
    private SomeService someService;
    public SomeThread(SomeService someService){
        // 通過構造函數從外部引入
        this.someService  = someService;
    }
    @Override
    public void run() {
        // do something...
        someService.doSomething();
        // 此時someService非null.
    }
}

Spring多線程中,bean的註入問題

最近碰到瞭一個問題,使用SSM框架,在Service層需要另開一個線程,這個線程專門用來做一些操作,並將結果寫入數據庫中。但是在線程中使用@Resource或者@Autowired註入全部為NULL,原來是Spring不能在線程中註入。

網上的主要解決方法有

  • 將需要的Bean作為線程的的構造函數的參數傳入
  • 使用ApplicationContext.getBean方法來靜態的獲取Bean

我的線程中所需要的Bean的數量較多,並且以後還有可能增加或者減少,所以方法1並不適合

我的Spring配置文件並不隻一個,而且使用getBean方法需要重新加載一遍所有的Bean,這樣也違反的Spring的IoC,並不是我想要的,所以也不采用方法2

最後確定使用內部類的方法,將線程中需要的Bean提前註入好,大致的結構如下:

@Service
class TestExample{
    //這兩個為線程所需要的Bean
    @Resource
    TestDao testDao;
    @Resource
    NeedDap needDao;
    public void serviceExecute(){
        //在這裡開啟線程,執行操作
        ThreadExample te = new ThreadExample();
        te.start();
    }
    
    //內部類
    private class ThreadExample extends Thread{
        public ThreadExample(){
            //也可以在構造函數中傳入參數
        }
        public void run(){
            //這裡為線程的操作
            //就可以使用註入之後Bean瞭
        }
    }
}

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: