監聽器中spring注入相關的問題

木叔發表於2017-02-17
問題描述:
需求是要求在專案啟動自動觸發一個service中的執行緒的操作,使用監聽器來實現,但是自定義監聽器中spring註解service失敗,通過WebApplicationContextUtils去spring容器中獲取仍然獲取不到,通過斷點檢視spring容器中沒有被注入的service物件
 
程式碼如下:
1、web.xml檔案中配置監聽器
<listener>
    <listener-class>com.cairh.xpe.aips.web.Test.TestLinstener</listener-class>
</listener>

 2、寫監聽器實現ServletContextListener並重寫相應的方法

public class TestLinstener implements ServletContextListener{
    
    @Autowired
    MessMediaService messMediaService;
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("*************執行相關方法************");
        //spring容器中獲取service物件
        MessMediaService service = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).getBean(MessMediaService.class);
        service.test();    
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // TODO Auto-generated method stub
    }

}

3、service類

@Lazy(false)
@Service
public class MessMediaService {
    
    @Autowired
    private ITaskService taskService;
    
    private RedisLock redisLock = new RedisLock();
    
    private Map taskMap = (Map)JSON.parse(RedisClientUtil.get("config.cache./aips/tasksettings.properties"));
    
   private Thread thread = new Thread(new Runnable() {
        
        @Override
        public void run() {

        }
    });
    
    public int test(){
    taskService.test();
     ...
        return 1;
    }
    
    public MessMediaService() {
        super();
        System.out.println("*******************測試構造方法********************");
    }
    
    @PostConstruct
    private void init() throws Exception {
        System.out.println("*******************init******************");
        thread.start();
    }
    
}

 

 解決歷程:

後來嘗試在構造方法中觸發事件也失敗,因為斷點並沒有進入到構造方法中;
再後來嘗試使用@PostConstruct註解讓service在例項化過程中自動執行方法來實現,@PostConstruct執行順序大致如下:
伺服器載入servlet-->servlet建構函式-->@PostConstruct方法-->init-->service方法-->destroy方法-->@PreDestory方法-->伺服器解除安裝servlet完畢
 
通過此方法正常來說也可實現專案啟動自動觸發一定的操作,但是啟動時並沒有按照預想進入到註解的方法中執行任何操作
注:@PostConstruct詳細解釋見:@PostConstruct與@PreDestroy講解及例項
 
分析:
構造方法沒有執行並且spring容器中沒有messMediaService例項,說明要麼沒有例項化要麼是例項化失敗
首先驗證是否例項化失敗,在普通的controller中注入此service,在呼叫controller時成功,說明service本身沒有錯誤,不會導致例項化失敗,剩下的可能就是在專案啟動載入的時候沒有例項化service
 
在檢視spring配置檔案的時候發現了beans屬性default-lazy-init="true",發現問題所在,此屬性表示延時載入,為了提高平時開發中專案啟動時間設定的,就是在IoC容器啟動時不會例項化bean,只有當容器需要用到時才例項化它,故在監聽器中並沒有例項化service
 
解決:
如果是手動配置則可在bean屬性中新增lazy-init="false"屬性來對需要提前載入的bean在spring容器啟動時例項化,或者使用註解標籤@Lazy(false)
 
再次嘗試:
1、監聽器中注入messMediaService依然失敗;
2、spring容器中獲取messMediaService物件成功,可通過從spring容器中獲取messMediaService物件來呼叫,可滿足需求;
3、在messMediaService構造方法中斷點發現執行到service構造方法時service中注入的taskService依然為null;
4、在@PostConstruct註解方法中發現messMediaService中注入的taskService也有了例項,此方法可成功解決問題;
 
總結:
專案啟動最先載入context-parame標籤中配置的spring檔案,此檔案中配置了spring需要例項化的bean目錄,但是spring注入和bean的例項化不是同時的,先例項化再注入,兩步操作是緊接著的,但是在例項化呼叫構造方法完成後才有依賴注入行為。
注:spring例項化詳細解釋見:spring容器初始化過程 和 Spring BeanFactory例項化Bean的詳細過程
 
專案啟動執行順序:呼叫構造方法例項化-->呼叫@PostConstruct方法-->執行listener的contextInitialized方法
 
listener中messMediaService沒有注入成功而messMediaService中能注入taskService是因為messMediaService例項化時其屬性service屬於一種依賴關係也會被例項化並注入進來,而listener不會被spring例項化並查詢依賴關係,故監聽器中不能使用spring註解,只能手動獲取spring容器中物件

 

 

 

 
 
 
 
 

相關文章