Spring官網閱讀系列(二):Spring依賴注入及方法注入

Spring官網閱讀系列(二):Spring依賴注入及方法注入

上篇文章咱們學習了官網中的1.2,1.3兩小節,主要是涉及了容器,以及Spring實例化對象的一些知識。這篇文章咱們繼續學習Spring官網,主要是針對1.4小節,主要涉及到Spring的依賴注入。雖然只有一節,可是涉及的東西確很多。話很少說,開始正文。java

[TOC]spring

依賴注入:

根據官網介紹,依賴注入主要分爲兩種方式app

  1. 構造函數注入
  2. Setter方法注入​ 官網:

Spring官網閱讀系列(二):Spring依賴注入及方法注入

咱們分別對以上兩種方式進行測試,官網上用的是XML的方式,我這邊就採用註解的方式了:ide

測試代碼以下,咱們經過在Service中注入LuBanService這個過程來函數

public class Main02 {    public static void main(String[] args) {        AnnotationConfigApplicationContext ac = new             // config類主要完成對類的掃描            AnnotationConfigApplicationContext(Config.class);        Service service = (Service) ac.getBean("service");        service.test();    }}@Componentpublic class LuBanService {    LuBanService(){        System.out.println("luBan create ");    }}
測試setter方法注入
@Componentpublic class Service {    private LuBanService luBanService;    public Service() {        System.out.println("service create");    }    public void test(){        System.out.println(luBanService);    }    // 經過autowired指定使用set方法完成注入    @Autowired    public void setLuBanService(LuBanService luBanService) {        System.out.println("注入luBanService by setter");        this.luBanService = luBanService;    }}

輸出以下:學習


luBan create service create注入luBanService by setter  // 驗證了確實是經過setter注入的com.dmz.official.service.LuBanService@5a01ccaa

測試構造函數注入
@Componentpublic class Service {    private LuBanService luBanService;    public Service() {        System.out.println("service create by no args constructor");    }    // 經過Autowired指定使用這個構造函數,不然默認會使用無參    @Autowired    public Service(LuBanService luBanService) {        System.out.println("注入luBanService by constructor with arg");        this.luBanService = luBanService;        System.out.println("service create by constructor with arg");    }    public void test(){        System.out.println(luBanService);    }}

輸出以下:測試

luBan create 注入luBanService by constructor // 驗證了確實是經過constructor注入的service create by constructorcom.dmz.official.service.LuBanService@1b40d5f0

疑問:

在上面的驗證中,你們可能會有如下幾個疑問:ui

  1. @Autowired直接加到字段上跟加到set方法上有什麼區別?爲何咱們驗證的時候須要將其添加到setter方法上?首先咱們明確一點,直接添加@Autowired註解到字段上,不須要提供setter方法也能完成注入。以上面的例子來講,Spring會經過反射獲取到Service中luBanService這個字段,而後經過反射包的方法,Filed.set(Service,luBanService)這種方式來完成注入咱們將@Autowired添加到setter方法時,咱們能夠經過斷點看一下方法的調用棧,以下:對於這種方式來講,最終是經過Method.invoke(object,args)的方式來完成注入的,這裏的method對象就是咱們的setter方法
  2. @Autowired爲何加到構造函數上能夠指定使用這個構造函數?咱們先能夠測試下,若是咱們不加這個註解會怎麼樣呢?我把前文中的@Autowired註解註釋,而後運行發現luBan create service create by no args constructor // 能夠看到執行的是空參構造 null先不急得出結論,咱們再進行一次測試,就是兩個函數上都添加@Autowired註解呢?Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Invalid autowire-marked constructor: public com.dmz.official.service.Service(com.dmz.official.service.LuBanService). Found constructor with 'required' Autowired annotation already: public com.dmz.official.service.Service()發現直接報錯了,報錯的大概意思是已經找到了一個被@Autowired註解標記的構造函數,同時這個註解中的required屬性爲true。後來我測試了將其中一個註解中的required屬性改成false,發現仍是報一樣的錯,最終將兩個註解中的屬性都改成false測試才經過,而且測試結果跟上面的同樣,都是執行的無參構造。要說清楚這一點,涉及到兩個知識Spring中的注入模型,下篇文章專門講這個Spring對構造函數的推斷。這個到源碼階段我打算專門寫一篇文章,如今咱們暫且記得:在默認的注入模型下,Spring若是同時找到了兩個符合要求的構造函數,那麼Spring會採用默認的無參構造進行實例化,若是這個時候沒有無參構造,那麼此時會報錯java.lang.NoSuchMethodException。什麼叫符合要求的構造函數呢?就是構造函數中的參數Spring能找到,參數被Spring所管理。這裏須要着重記得:一,默認注入模型;二,符合要求的構造函數
  3. 若是咱們同時採用構造注入加屬性注入會怎麼樣呢?在沒有進行測試前,咱們能夠大膽猜想下,Spring雖然能在構造函數裏完成屬性注入,可是這屬於實例化對象階段作的事情,那麼在後面真正進行屬性注入的時候,確定會將其覆蓋掉。如今咱們來驗證咱們的結論@Component public class Service { private LuBanService luBanService; public Service(LuBanService luBanService) { System.out.println("注入luBanService by constructor with arg"); this.luBanService = luBanService; System.out.println("service create by constructor with arg"); } public void test(){ System.out.println(luBanService); } @Autowired public void setLuBanService(LuBanService luBanService) { System.out.println("注入luBanService by setter"); this.luBanService = null; } }運行結果:注入luBanService by constructor with arg // 實例化時進行了一次注入 service create by constructor with arg // 完成了實例化 注入luBanService by setter // 屬性注入時將實例化時注入的屬性進行了覆蓋 null
區別:

Spring官網閱讀系列(二):Spring依賴注入及方法注入

根據上圖中官網所說,咱們能夠得出以下結論:this

  1. 構造函數注入跟setter方法注入能夠混用 2. 對於一些強制的依賴,咱們最好使用構造函數注入,對於一些可選依賴咱們能夠採用setter方法注入
  2. Spring團隊推薦使用構造函數的方式完成注入。可是對於一些參數過長的構造函數,Spring是不推薦的
方法注入:

咱們不徹底按照官網順序進行學習,先看這一小節,對應官網上的位置以下圖:spa

Spring官網閱讀系列(二):Spring依賴注入及方法注入

爲何須要方法注入:

首先咱們思考一個問題,在有了依賴注入的狀況下,爲何還須要方法注入這種方式呢?換而言之,方法注入解決了什麼問題?

咱們來看下面這種場景:

@Componentpublic class MyService {    @Autowired    private LuBanService luBanService;    public void test(int a){        luBanService.addAndPrint(a);    }}@Component// 原型對象@Scope("prototype")public class LuBanService {    int i;    LuBanService() {        System.out.println("luBan create ");    }    // 每次將當前對象的屬性i+a而後打印    public void addAndPrint(int a) {        i+=a;        System.out.println(i);    }}public class Main02 {    public static void main(String[] args) {        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);        MyService service = (MyService) ac.getBean("myService");        service.test(1);        service.test(2);        service.test(3);    }}

在上面的代碼中,咱們有兩個Bean,MyService爲單例的Bean,LuBanService爲原型的Bean。咱們的本意多是但願每次都能獲取到不一樣的LuBanService,預期的結果應該打印出:


1,2,3


實際輸出:


1 3 6


這個結果說明咱們每次調用到的LuBanService是同一個對象。固然,這也很好理解,由於在依賴注入階段咱們就完成了LuBanService的注入,以後咱們在調用測試方法時,不會再去進行注入,因此咱們一直使用的是同一個對象。

咱們能夠這麼說,原型對象在這種狀況下,失去了原型的意義,由於每次都使用的是同一個對象。那麼如何解決這個問題呢?只要我每次在使用這個Bean的時候都去從新獲取就能夠了,那麼這個時候咱們能夠經過方法注入來解決。

經過注入上下文(applicationContext對象)

又分爲如下兩種方式:

  • 實現org.springframework.context.ApplicationContextAware接口
@Componentpublic class MyService implements ApplicationContextAware {    private ApplicationContext applicationContext;    public void test(int a) {        LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));        luBanService.addAndPrint(a);    }    @Override    public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException {        this.applicationContext = applicationContext;    }}
  • 直接注入上下文
@Componentpublic class MyService{    @Autowired    private ApplicationContext applicationContext;    public void test(int a) {        LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));        luBanService.addAndPrint(a);    }}
經過@LookUp的方式(也分爲註解跟XML兩種方式,這裏只演示註解的)
@Componentpublic class MyService{    public void test(int a) {        LuBanService luBanService = lookUp();        luBanService.addAndPrint(a);    }    //     @Lookup    public LuBanService lookUp(){        return null;    }}
方法注入 之 replace-method

方法注入還有一種方式,即經過replace-method這種形式,沒有找到對應的註解,因此這裏咱們也就用XML的方式測試一下:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="myService" class="com.dmz.official.service.MyService">        <replaced-method replacer="replacer" name="test"/>    </bean>    <bean id="replacer" class="com.dmz.official.service.MyReplacer"/></beans>
public class MyReplacer implements MethodReplacer {    @Override   public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {        System.out.println("替代"+obj+"中的方法,方法名稱:"+method.getName());        System.out.println("執行新方法中的邏輯");        return null;    }}public class MyService{    public void test(int a) {        System.out.println(a);    }}public class Main {    public static void main(String[] args) {        ClassPathXmlApplicationContext cc =            new ClassPathXmlApplicationContext("application.xml");        MyService myService = ((MyService) cc.getBean("myService"));        myService.test(1);    }}

執行結果:

替代com.dmz.official.service.MyService$$EnhancerBySpringCGLIB$$61c14242@63e31ee中的方法,方法名稱:test執行新方法中的邏輯

這裏須要注意一點:

我在測試replace-method這種方法注入的方式時,受動態代理的影響,一直想將執行咱們被替代的方法。用代碼體現以下:

public class MyReplacer implements MethodReplacer {    @Override    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {//        System.out.println("替代"+obj+"中的方法,方法名稱:"+method.getName());//        System.out.println("執行新方法中的邏輯");        method.invoke(obj,args);        return null;    }}

可是,這段代碼是沒法執行的,會報棧內存溢出。由於obj是咱們的代理對象,method.invoke(obj,args)執行時會進入方法調用的死循環。最終我也沒有找到一種合適的方式來執行被替代的方法。目前看來這可能也是Spring的設計,因此咱們使用replace-method的場景應該是想徹底替代某種方法的執行邏輯,而不是像AOP那樣更多的用於在方法的執行先後等時機完成某些邏輯。

依賴注入跟方法注入的總結:
  • 咱們首先要明確一點,什麼是依賴(Dependencies)?來看官網中的一段話:

Spring官網閱讀系列(二):Spring依賴注入及方法注入

能夠說,一個對象的依賴就是它自身的屬性,Spring中的依賴注入就是屬性注入

  • 咱們知道一個對象由兩部分組成:屬性+行爲(方法),能夠說Spring經過屬性注入+方法注入的方式掌控的整個bean。
  • 屬性注入跟方法注入都是Spring提供給咱們用來處理Bean之間協做關係的手段
  • 屬性注入有兩種方式:構造函數,Setter方法。
  • 方法注入(LookUp Method跟Replace Method)須要依賴動態代理完成
  • 方法注入對屬性注入進行了必定程度上的補充,由於屬性注入的狀況下,原型對象可能會失去原型的意義,見:爲何須要方法注入

畫圖以下:

Spring官網閱讀系列(二):Spring依賴注入及方法注入

這篇文章到這裏就結束了,看完記得點個關注+分享,咱們下篇文章再見!


推薦閱讀

金三銀四季,阿里工做10多年Java大牛的「心得」,獻給迷茫中的你)

相關文章
相關標籤/搜索