本文內容
- 基于構造器的依賴注入
- 基于setter的依賴注入
基于構造器的依賴注入
案例
定義2個簡單的bean類,BeanOne 和 BeanTwo,前者依賴后者,
package com.crab.spring.ioc.demo02;
public class BeanTwo {
}
package com.crab.spring.ioc.demo02;
/**
* @author zfd
* @version v1.0
* @date 2022/1/12 16:59
*/
public class BeanOne {
private int age;
private String name;
private BeanTwo beanTwo;
/**
* 建構式,用于依賴注入,定義3個依賴
* @param age
* @param name
* @param beanTwo
*/
public BeanOne(int age, String name, BeanTwo beanTwo) {
this.age = age;
this.name = name;
this.beanTwo = beanTwo;
}
@Override
public String toString() {
return "BeanOne{" +
"age=" + age +
", name='" + name + '\'' +
", beanTwo=" + beanTwo +
'}';
}
}
通過xml組態檔實作bean定義和依賴注入
<?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="bean2" />
<bean id="bean1" >
<constructor-arg name="age" index="0" type="int" value="https://www.cnblogs.com/kongbubihai/archive/2022/01/20/20"/>
<constructor-arg name="name" index="1" type="java.lang.String" value="https://www.cnblogs.com/kongbubihai/archive/2022/01/20/xxx"/>
<constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
</bean>
</beans>
來個測驗類驗證下注入
package com.crab.spring.ioc.demo02;
/**
* @author zfd
* @version v1.0
* @date 2022/1/12 17:09
*/
public class demo02Test {
@Test
public void test_construct() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml");
BeanOne bean1 = context.getBean("bean1", BeanOne.class);
System.out.println(bean1);
context.close();
}
輸出如下
BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@5204062d}
對照組態檔BeanOne的3個依賴都通過構造器的方式進行注入了,符合預期,很簡單,
constructor-arg詳解
標簽constructor-arg支持的元素串列如下,
| 元素名 | 作用 |
|---|---|
| name | 引數名 |
| index | 引數索引,0開始 |
| type | 引數型別 |
| value | 值 |
| ref | bean參考 |
例如案例中的配置
<bean id="bean1" >
<constructor-arg name="age" index="0" type="int" value="https://www.cnblogs.com/kongbubihai/archive/2022/01/20/20"/>
<constructor-arg name="name" index="1" type="java.lang.String" value="https://www.cnblogs.com/kongbubihai/archive/2022/01/20/xxx"/>
<constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
</bean>
注意: 在沒有引起歧義的情況下,上面的部分元素并不是都必須配置的,如指定了index時可以定位引數位置,那么name是可以不配置的,又如通過ref參考依賴bean,type可以省略
<bean id="bean1" >
<constructor-arg index="0" type="int" value="https://www.cnblogs.com/kongbubihai/archive/2022/01/20/20"/>
<constructor-arg index="1" type="java.lang.String" value="https://www.cnblogs.com/kongbubihai/archive/2022/01/20/xxx"/>
<constructor-arg index="2" ref="bean2"/>
</bean>
注意事項
當在<constructor />使用 name元素指定建構式中的引數名時,尤其要方法引數名編譯后是否保留的情況,舉個例子
// 編譯前的方法引數
public BeanOne(int age, String name, BeanTwo beanTwo)
// 編譯后的方法引數
public BeanOne(int var1, String var2, BeanTwo var3)
編譯后方法引數被編譯成var1這種形式會導致僅指定name的構造注入失效,
如何解決?提供2種方法
-
Maven編譯插件添加編譯引數,保留引數名,pom檔案下增加如下插件配置<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <encoding>UTF8</encoding> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin> </plugins> </build> -
使用
@ConstructorPropertiesJDK 注釋顯式命名建構式引數/** * 建構式,用于依賴注入,定義3個依賴 * @param age * @param name * @param beanTwo */ @ConstructorProperties({"age", "name", "beanTwo"}) // 顯式宣告構造引數名稱 public BeanOne(int age, String name, BeanTwo beanTwo) { this.age = age; this.name = name; this.beanTwo = beanTwo; }
基于setter的依賴注入
基于 Setter 的 DI 是通過容器在呼叫無引數建構式或無引數靜態工廠方法來實體化 bean 后呼叫 bean 上的 setter 方法來完成的,
來一個簡單類BeanThree依賴BeanOne和BeanTwo并提供了Setter方法來設定,
package com.crab.spring.ioc.demo02;
/**
* @author zfd
* @version v1.0
* @date 2022/1/13 8:18
*/
public class BeanThree {
private BeanTwo beanTwo;
private BeanOne beanOne;
public void setBeanTwo(BeanTwo beanTwo) {
this.beanTwo = beanTwo;
}
public void setBeanOne(BeanOne beanOne) {
this.beanOne = beanOne;
}
}
對應的組態檔可以通過標簽property中的ref或name來設定屬性參考或是屬性值
<?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="bean2" />
<!--建構式注入-->
<bean id="bean1" >
<constructor-arg name="age" index="0" type="int" value="https://www.cnblogs.com/kongbubihai/archive/2022/01/20/20"/>
<constructor-arg name="name" index="1" type="java.lang.String" value="https://www.cnblogs.com/kongbubihai/archive/2022/01/20/xxx"/>
<constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
</bean>
<!--setter注入-->
<bean id="bean3" >
<!-- 1 ref元素-->
<property name="beanOne" ref="bean1"></property>
<!-- 2 ref標簽-->
<property name="beanTwo">
<ref bean="bean2"></ref>
</property>
<property name="name" value="https://www.cnblogs.com/kongbubihai/archive/2022/01/20/xxxx"/>
</bean>
</beans>
運行測驗類和結果,可見依賴注入成功
public class demo02Test {
@Test
public void test_construct() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml");
BeanOne bean1 = context.getBean("bean1", BeanOne.class);
System.out.println(bean1);
System.out.println("演示Setter注入");
BeanThree beanThree = context.getBean(BeanThree.class);
System.out.println(beanThree);
context.close();
}
}
BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24}
演示Setter注入
BeanThree{name='xxxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24, beanOne=BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24}}
依賴解決程序
- ApplicationContext是用描述所有bean的配置元資料創建和初始化的,配置元資料可以通過XML、Java代碼或注釋指定,
- 對于每個bean,它的依賴項都以屬性、建構式引數或靜態工廠方法的引數的形式表示(如果使用靜態工廠方法而不是普通建構式的話),當bean實際創建時,這些依賴項被提供給bean,
- 每個屬性或建構式引數都是要設定的值的實際定義,或者是對容器中另一個bean的參考,
- 每個具有值的屬性或建構式引數都將從其指定的格式轉換為該屬性或建構式引數的實際型別,默認情況下,Spring可以將字串格式提供的值轉換為所有內置型別,如int、long、string、boolean等,
什么是回圈依賴,如何處理?
如果主要使用建構式注入,則可能會創建一個不可決議的回圈依賴場景,
例如:類A需要類B的一個實體通過建構式注入,類B需要類A的一個實體通過建構式注入,如果將類A和類B的bean配置為相互注入,Spring IoC容器會在運行時檢測此回圈參考,并拋出
BeanCurrentlyInCreationException,一個可能的解決方案是編輯一些由setter而不是建構式配置的類的源代碼,或者,避免建構式注入,只使用setter注入,換句話說,可以使用setter注入配置回圈依賴項,
與典型情況(沒有回圈依賴關系)不同,bean A 和 bean B 之間的回圈依賴關系強制其中一個 bean 在完全初始化之前注入另一個 bean(典型的先有雞還是先有蛋的場景),
蘿卜青菜各有所愛
選擇用構造器注入還是Setter注入?
Spring官方的推薦,建構式用于強制依賴項,將 setter 方法或配置方法用于可選依賴項,請注意,在 setter 方法上使用 @Required 注釋可用于使屬性成為必需的依賴項;然而,帶有引數的編程驗證的建構式注入是更可取的,
Spring 團隊通常提倡建構式注入,因為它允許您將應用程式組件實作為不可變物件,并確保所需的依賴項不為空,此外,建構式注入的組件總是以完全初始化的狀態回傳給客戶端(呼叫)代碼,作為旁注,大量的建構式引數是一種不好的代碼氣味,這意味著該類可能有太多的職責,應該重構以更好地解決適當的關注點分離, Setter 注入應該主要只用于可以在類中分配合理默認值的可選依賴項,否則,必須在代碼使用依賴項的任何地方執行非空檢查, setter 注入的一個好處是 setter 方法使該類的物件可以在以后重新配置或重新注入,
有時,在處理沒有源代碼的第三方類時,如果第三方類沒有公開任何 setter 方法,那么建構式注入可能是 DI 的唯一可用形式,
總結
本文演示2種依賴注入的方式:建構式注入和Setter方法注入,并對比如何選擇這2種方式,下一篇繼續深入依賴注入,
本篇原始碼地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo02
知識分享,轉載請注明出處,學無先后,達者為先!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/417057.html
標籤:其他
