SpringBoot學習——@Autowired自動注入報:could not be found問題的理解和解決方案

聖鬥士Morty發表於2017-12-14

微服務應用程式中,我們會通過Java後臺的方式傳送http請求並呼叫其他註冊在Spring Cloud Eureka server上的微服務,之前我們可能會手動封裝一個Http傳送請求類,然後通過其中的sendGet或者sendPost方法藉由java IO的形式傳送出去。

但是,上述方法過於繁瑣和和臃腫,我們使用org.springframework.web.client.RestTemplate例項,通過幾行程式碼就可以輕鬆傳送我們需要的請求。

然而,在實際的應用程式呼叫時,我通過@Autowired方式將RestTemplate例項注入到類中,

@Autowired
    private RestTemplate restTemplate;

在啟動springboot時,控制檯報告啟動失敗:

***************************
APPLICATION FAILED TO START
***************************

Description:

Field restTemplate in com.seco.ad.controller.MobileAdController required a bean of 
type 'org.springframework.web.client.RestTemplate' that could not be found.

Action:

Consider defining a bean of type 'org.springframework.web.client.RestTemplate' in your configuration.

可以看到Description:中的描述是RestTemplate未能找到!!!

為什麼?因為Spring容器沒有註冊RestTemplate例項,也就無法通過@Autowired自動注入方式“new”一個RestTemplate例項出來(通過重寫無參構造器的方式,可以發現這種通過@Autowired方式自動注入的例項確實是通過new方法來完成的)。

在之前的@Autowired註解使用中,我們可以輕鬆的將DAO層的依賴物件JdbcTemplate例項順利的自動注入到DAO層的服務中去,而不需要任何類似JavaConfig或者xml配置Bean定義到Spring容器中去,那為什麼RestTemplate就需要?

原來JdbcTemplate 和RestTemplate一樣都是都是spring框架本身提供的元件,但是專案中用到的JdbcTemplate是不需要引數的,可以通過@Autowired自動注入,而不需要初始化,但是RestTemplate需要。另外,在《@Autowired自動注入例項》這篇文章中也可以看到,當需要引數的JdbcTemplate進行自動注入的時候應用程式在啟動時就會發生類似“could not be found”的空指標異常。

因此,回過頭來反思@Autowired自動注入RestTemplate報“could not be found”錯誤的原因,即是此注入例項需要引數!

我在通過網上資料的查詢中找到了正確注入RestTemplate的方法,確實需要將RestTemplate註冊到spring容器中去,另外,還有我們剛剛提到的引數問題,也就是RestTemplate例項的依賴:org.springframework.http.client.ClientHttpRequestFactory。

SpringBoot提倡通過JavaConfig方式註冊我們需要的Bean元素:

package com.seco.ad.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**
 * RestTemplate配置
 * 這是一種JavaConfig的容器配置,用於spring容器的bean收集與註冊,並通過引數傳遞的方式實現依賴注入。
 * "@Configuration"註解標註的配置類,都是spring容器配置類,springboot通過"@EnableAutoConfiguration"
 * 註解將所有標註了"@Configuration"註解的配置類,"一股腦兒"全部注入spring容器中。
 * 
 * @author mht
 *
 */
@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);//ms
        factory.setConnectTimeout(15000);//ms
        return factory;
    }
}

(最近在研讀《SpringBoot揭祕》有關springboot啟動方面的知識中談到了有關@Configuration註解的的作用我寫在了這段code中的JavaDoc裡)

可以看到,在此JavaConfig配置中,我註冊了兩個Bean,而第二個Bean正是我們最終需要的RestTemplate例項的依賴,有了這個配置類,我們回過頭再通過@Autowired方式自動注入我們需要的RestTemplate就不會在springboot啟動時報“無法找到”錯誤了。

令人欣喜的是,將這些思路理清的功臣是《@Autowired自動注入例項》這篇文章。

我試著通過文章中提到的“@Autowired自動注入有引數依賴物件的寫法”編寫類似的程式碼後,發現即便是沒有RestTemplateConfig這個配置類,沒有對spring容器進行bean定義的註冊,依然可以成功的啟動springboot應用程式。寫法如下:

    private RestTemplate restTemplate;
    private ClientHttpRequestFactory factory;
    
    @Autowired
    public void setFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);//ms
        factory.setConnectTimeout(15000);//ms
        this.factory = factory;
    }
    
    @Autowired
    public void setRestTemplate() {
        this.restTemplate = new RestTemplate(this.factory);
    }

上述程式碼思路很簡單:設定兩個私有屬性 restTemplate和factory,然後下面先通過設定引數的方式自動完成對factory物件的注入,然後再講factory注入到restTemplate中去,即完成了RestTemplate的注入工作。即便沒有JavaConfig這樣的Bean的註冊配置類,應用程式依然可以啟動成功。

實際上還是圍繞了spring容器的關鍵兩步(引自王富強的《SpringBoot解密》):第一步:收集和註冊;第二步:分析和組裝。

當我們依賴的例項(例如本文中提到的JdbcTemplate和RestTemplate)需要其他的依賴時,當然就不可能僅僅通過@Autowired自動注入到我們需要的類中而不去考慮它們的引數(也就是它們的依賴),因此,不管是通過JavaConfig配置方式還是利用@Autowired完成引數的配置,實際上都是在解決我們最終的依賴與它們自己的依賴的組裝問題。

相關文章