日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Spring官网阅读(二)(依赖注入及方法注入)

發布時間:2025/3/21 68 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring官网阅读(二)(依赖注入及方法注入) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

依賴注入

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

  • 構造函數注入

  • Setter方法注入

    官網:

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

    測試代碼如下,我們通過在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();} }@Component public?class?LuBanService?{LuBanService(){System.out.println("luBan?create?");} }

    測試setter方法注入

    @Component public?class?Service?{private?LuBanService?luBanService;public?Service()?{System.out.println("service?create");}public?void?test(){System.out.println(luBanService);}//?通過autowired指定使用set方法完成注入@Autowiredpublic?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

    測試構造函數注入

    @Component public?class?Service?{private?LuBanService?luBanService;public?Service()?{System.out.println("service?create?by?no?args?constructor");}//?通過Autowired指定使用這個構造函數,否則默認會使用無參@Autowiredpublic?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?constructor com.dmz.official.service.LuBanService@1b40d5f0

    疑問:

    在上面的驗證中,大家可能會有以下幾個疑問:

  • @Autowired直接加到字段上跟加到set方法上有什么區別?為什么我們驗證的時候需要將其添加到setter方法上?

    • 首先我們明確一點,直接添加@Autowired注解到字段上,不需要提供setter方法也能完成注入。以上面的例子來說,Spring會通過反射獲取到Service中luBanService這個字段,然后通過反射包的方法,Filed.set(Service,luBanService)這種方式來完成注入

    • 我們將@Autowired添加到setter方法時,我們可以通過斷點看一下方法的調用棧,如下:

    對于這種方式來說,最終是通過Method.invoke(object,args)的方式來完成注入的,這里的method對象就是我們的setter方法

  • @Autowired為什么加到構造函數上可以指定使用這個構造函數?

    • 我們先可以測試下,如果我們不加這個注解會怎么樣呢?我把前文中的@Autowired注解注釋,然后運行發現


    java ? luBan create ? service create by no args constructor ?// 可以看到執行的是空參構造 ? null


    先不急得出結論,我們再進行一次測試,就是兩個函數上都添加@Autowired注解呢?

    java ? 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所管理。

    這里需要著重記得:一,默認注入模型;二,符合要求的構造函數

  • 如果我們同時采用構造注入加屬性注入會怎么樣呢?

    在沒有進行測試前,我們可以大膽猜測下,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);}@Autowiredpublic?void?setLuBanService(LuBanService?luBanService)?{System.out.println("注入luBanService?by?setter");this.luBanService?=?null;} }

    運行結果:


  • java ? 注入luBanService by constructor with arg ?// 實例化時進行了一次注入 ?

    service create by constructor with arg ? // 完成了實例化 ?

    注入luBanService by setter ? ?// 屬性注入時將實例化時注入的屬性進行了覆蓋 ? null


    區別:

    根據上圖中官網所說,我們可以得出如下結論:

  • 構造函數注入跟setter方法注入可以混用

  • 對于一些強制的依賴,我們最好使用構造函數注入,對于一些可選依賴我們可以采用setter方法注入

  • Spring團隊推薦使用構造函數的方式完成注入。但是對于一些參數過長的構造函數,Spring是不推薦的

  • 方法注入:

    我們不完全按照官網順序進行學習,先看這一小節,對應官網上的位置如下圖:

    為什么需要方法注入:

    首先我們思考一個問題,在有了依賴注入的情況下,為什么還需要方法注入這種方式呢?換而言之,方法注入解決了什么問題?

    我們來看下面這種場景:

    @Component public?class?MyService?{@Autowiredprivate?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接口

    @Component public?class?MyService?implements?ApplicationContextAware?{private?ApplicationContext?applicationContext;public?void?test(int?a)?{LuBanService?luBanService?=?((LuBanService)?applicationContext.getBean("luBanService"));luBanService.addAndPrint(a);}@Overridepublic?void?setApplicationContext(@Nullable?ApplicationContext?applicationContext)?throws?BeansException?{this.applicationContext?=?applicationContext;} }
    • 直接注入上下文

    @Component public?class?MyService{@Autowiredprivate?ApplicationContext?applicationContext;public?void?test(int?a)?{LuBanService?luBanService?=?((LuBanService)?applicationContext.getBean("luBanService"));luBanService.addAndPrint(a);} }

    通過@LookUp的方式(也分為注解跟XML兩種方式,這里只演示注解的)

    @Component public?class?MyService{public?void?test(int?a)?{LuBanService?luBanService?=?lookUp();luBanService.addAndPrint(a);}//?@Lookuppublic?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?{@Overridepublic?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?{@Overridepublic?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通過屬性注入+方法注入的方式掌控的整個bean。

    • 屬性注入跟方法注入都是Spring提供給我們用來處理Bean之間協作關系的手段

    • 屬性注入有兩種方式:構造函數,Setter方法。

    • 方法注入(LookUp Method跟Replace Method)需要依賴動態代理完成

    • 方法注入對屬性注入進行了一定程度上的補充,因為屬性注入的情況下,原型對象可能會失去原型的意義,見:為什么需要方法注入

    畫圖如下:

    ?

    總結

    以上是生活随笔為你收集整理的Spring官网阅读(二)(依赖注入及方法注入)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。