目錄
前言
1、Bean的裝配
1.1 預設的裝配方式
1.2 Bean的作用域
2、基于xml的依賴注入
2.1 set注入(也叫設值注入)
2.1.1 簡單類型
2.1.2 引用類型
2.2 構造注入
2.3 引用類型的自動注入
2.3.1 ByName 自動注入
2.3.2 ByType 自動注入
2.4 Spring多配置檔案
3、基于注解的依賴注入
3.1 定義Bean的注解@Component
3.2 簡單類型屬性注入@value
3.3 byType自動注入@AutoWired
3.4 byName自動注入@AutoWired和@Qualifier
3.5 JDK注解@Resource自動注入
4、XML和注解兩種方式的對比
5、小結
前言
聲明:本專欄文章均為觀看動力節點王鶴老師三大架構的視訊所撰寫的筆記,筆者實力有限,内容如有錯誤歡迎各位小夥伴在評論區指出。
視訊連結:SSM-Spring
👉上文說到利用控制反轉的思想可以将建立對象的任務交給Spring容器去執行,對象建立好了以後,自然就要去考慮如何給我們的對象指派的問題,這同樣是Spring容器應該考慮并解決的問題。那麼Spring是如何做的呢?首先,Spring使用依賴注入(Dependency Injection),簡稱DI,實作了控制反轉,在此基礎上,分别基于xml檔案和注解解決了給對象指派的問題,這就相當于給類的私有屬性暴露出兩種公共通路方法供人進行屬性設定一樣,具體如何操作,閑話少說,大家往下看!
1、Bean的裝配
Bean的概念:
Bean的英文含義是小豆子的意思,Spring非常形象的将我們需要用到的各種Java對象,比如實體類對象、容器對象、操作接口實作類對象等,都比喻成一顆顆小豆子,然後存放在自己的容器中。
1.1 預設的裝配方式
所謂預設裝配方式就是指當我們使用ApplicationContext容器建立對象的時候,他會讀取配置檔案中<bean>并預設調用的是該對象的無參構造方法進行執行個體化。
1.2 Bean的作用域
除了對象的建立以及對象屬性指派意外,關于類還有一個重要的屬性需要關注,那就是對象的作用範圍。java基礎當中,利用四種不同的通路修飾符可以在類中的使控制其方法變量的使用範圍,類似的,關于容器中bean的使用範圍,Spring在<bean>标簽中提供了一種屬性,Scope屬性,進行使用範圍也就是作用域的指定。
scope屬性值 | 作用域 |
singleton(單例模式) | 作用于整個 Spring 容器中,使用 singleton 定義的 Bean 将是單例的,叫這個名稱的對象隻有一個執行個體 |
prototype(原型模式) | 作用域每一個調用它的方法中,每次使用 getBean 方法擷取的同一個的執行個體都是一個 新的執行個體 |
request | 作用于每一個請求中,對于每次 HTTP 請求,都将會産生一個不同的 Bean 執行個體;該作用域僅适用于web的Spring WebApplicationContext環境 |
session | 對于每個不同的 HTTP session,都将産生一個不同的 Bean 執行個體; |
2、基于xml的依賴注入
bean執行個體在調用無參構造器建立對象後,就要對bean對象的屬性進行初始化。初始化是由容器自動完成的,稱為注入。 根據注入方式的不同,常用的有兩類:set注入、構造注入
2.1 set注入(也叫設值注入)
set注入也叫設值注入是指,通過setter方法傳入被調用者的執行個體。這種注入方式簡單、直覺,因而在Spring的依賴注入中大量使用。
2.1.1 簡單類型set注入
Spring中規定java中的基本資料類型和String類型都是簡單類型。
建立學生類:
package com.bjpowernode.ba01;
public class Student {
private String name;
private int age;
public Student() {
System.out.println("spring會調用類的無參數構造方法建立對象");
}
public void setName(String name) {
System.out.println("setName:"+name);
this.name = name.toUpperCase();
}
public void setAge(int age) {
System.out.println("setAge:"+age);
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
spring配置檔案spring.xml中聲明Student類的bean:
<bean id="myStudent" class="com.bjpowernode.ba01.Student" >
<property name="name" value="李四" /><!--setName("李四")-->
<property name="age" value="22" /><!--setAge(21)-->
</bean>
單元測試:
@Test
public void test01(){
System.out.println("=====test01========");
String config="ba01/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//從容器中擷取Student對象
Student myStudent = (Student) ac.getBean("myStudent");
System.out.println("student對象="+myStudent);
}
2.1.2 引用類型set注入
當指定bean的某屬性值為另一bean的執行個體時,屬性值不是設定value而是通過ref指定它們間的引用關系。ref的值必須為某bean的id值。 例如上述學生類的屬性中增加一個學校資訊。
建立學校類:
package com.bjpowernode.ba02;
public class School {
private String name;
private String address;
public void setName(String name) {
this.name = name;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
Student類中添加School屬性,修改如下:
package com.bjpowernode.ba02;
public class Student {
private String name;
private int age;
//聲明一個引用類型
private School school;
public Student() {
System.out.println("spring會調用類的無參數構造方法建立對象");
}
// 包名.類名.方法名稱
// com.bjpowernode.ba02.Student.setName()
public void setName(String name) {
System.out.println("setName:"+name);
this.name = name;
}
public void setAge(int age) {
System.out.println("setAge:"+age);
this.age = age;
}
public void setSchool(School school) {
System.out.println("setSchool:"+school);
this.school = school;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
單元測試:
@Test
public void test02(){
System.out.println("===test02===");
Student student = new Student();
student.setName("lisi");
student.setAge(20);
School school = new School();
school.setName("門頭溝");
school.setAddress("北京");
student.setSchool(school);
// setSchool(mySchool)
System.out.println("student==="+student);
}
2.2 構造注入
上面兩種指派情況都是基于無參構造方法的,而當對象的構造方法重寫為有參構造方法後,怎麼在bean中配置呢?那就是利用<constructor-arg/>标簽配置參數。
首先,重寫Student類中的構造方法:
package com.bjpowernode.ba03;
public class Student {
private String name;
private int age;
//聲明一個引用類型
private School school;
public Student() {
System.out.println("spring會調用類的無參數構造方法建立對象");
}
/**
* 建立有參數構造方法
*/
public Student(String myname,int myage, School mySchool){
System.out.println("=====Student有參數構造方法======");
//屬性指派
this.name = myname;
this.age = myage;
this.school = mySchool;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
其次,spring配置檔案spring.xml中配置使用<constructor-arg/>标簽配置bean:
<constructor-arg/>設定參數有三種方法:
第一種:根據參數名設定,name--->value;
第二種:根據參數位置,index--->value,index從0開始;
第三種:位置預設,按照構造方法中預設的參數位置序号。
<!--使用name屬性實作構造注入-->
<bean id="myStudent" class="com.bjpowernode.ba03.Student" >
<constructor-arg name="myage" value="20" />
<constructor-arg name="mySchool" ref="myXueXiao" />
<constructor-arg name="myname" value="周良"/>
</bean>
<!--使用index屬性-->
<bean id="myStudent2" class="com.bjpowernode.ba03.Student">
<constructor-arg index="1" value="22" />
<constructor-arg index="0" value="李四" />
<constructor-arg index="2" ref="myXueXiao" />
</bean>
<!--省略index-->
<bean id="myStudent3" class="com.bjpowernode.ba03.Student">
<constructor-arg value="張強強" />
<constructor-arg value="22" />
<constructor-arg ref="myXueXiao" />
</bean>
<!--聲明School對象-->
<bean id="myXueXiao" class="com.bjpowernode.ba03.School">
<property name="name" value="清華大學"/>
<property name="address" value="北京的海澱區" />
</bean>
單元測試:
@Test
public void test03(){
System.out.println("=====test03========");
String config="ba03/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//從容器中擷取Student對象
Student myStudent = (Student) ac.getBean("myStudent");
System.out.println("student對象="+myStudent);
File myFile = (File) ac.getBean("myfile");
System.out.println("myFile=="+myFile.getName());
}
2.3 引用類型的自動注入
關于上面引用類型的注入,spring提供了自動注入的方式,通過為<bean/>标簽設定autowire屬性值,為引用類型屬性進行隐式自動注入(預設是不自動注入引用類型屬性)。根據自動注入判斷标準的不同,可以分為兩種:
🍉byName:根據名稱自動注入
🍊 byType: 根據類型自動注入
2.3.1 ByName 自動注入
容器是通過調用者的bean類的屬性名與配置檔案的被調用者bean的id進行比較而實作自動注入的。例如下面的Student類,隻有當School對象的bean的id屬性設定為和java代碼中Student類裡面的學校參數名一樣,就可以使用byName屬性值進行自動注入。
spring配置檔案spring.xml中Student類的bean設定autowire如下:
<!--byName-->
<bean id="myStudent" class="com.bjpowernode.ba04.Student" autowire="byName">
<property name="name" value="李四" />
<property name="age" value="26" />
<!--引用類型-->
<!--<property name="school" ref="mySchool" />-->
</bean>
<!--聲明School對象-->
<bean id="school" class="com.bjpowernode.ba04.School">
<property name="name" value="清華大學"/>
<property name="address" value="北京的海澱區" />
</bean>
2.3.2 ByType 自動注入
除了上述的可以使用參數名進行自動注入外,還可以利用引用對象的類型進行注入,此時對bean配置中School的id屬性值不再要求保持一緻。但是,配置檔案中隻能注冊一個School類型的對象,無論子類還是父類,像下面的配置中如果把School的子類primarySchool也注冊到容器中,由于子類父類同源的關系,spring容器就不知道應該注入哪一個了。
spring配置檔案spring.xml中Student類的bean設定autowire如下:
<!--byType-->
<bean id="myStudent" class="com.bjpowernode.ba05.Student" autowire="byType">
<property name="name" value="張飒" />
<property name="age" value="26" />
<!--引用類型-->
<!--<property name="school" ref="mySchool" />-->
</bean>
<!--聲明School對象-->
<bean id="mySchool" class="com.bjpowernode.ba05.School">
<property name="name" value="人民大學"/>
<property name="address" value="北京的海澱區" />
</bean>
<!--聲明School的子類-->
<!--<bean id="primarySchool" class="com.bjpowernode.ba05.PrimarySchool">
<property name="name" value="北京國小" />
<property name="address" value="北京的大興區" />
</bean>-->
2.4 Spring多配置檔案
在大型項目開發中,往往都是子產品化,其中建立的對象不僅數量龐大,而且不同子產品之間負責的業務也是互不相同的,是以,如果把所有的子產品中的對象全部配置到一個spring檔案中,不僅使得配置檔案非常臃腫,而且在後續的開發和使用中也非常不友善。是以,spring提供了類似檔案夾的方式來區分不同子產品中的bean配置。實作多配置檔案的步驟如下:
第一步:建立一個總配置檔案,例如spring-total.xml;
第二步:建立各個子配置檔案,例如spring-pojo.xml,spring-dao.xml,spring-service.xml;
第三步:總配置檔案中使用<import>将各個子配置檔案引入。
spring-total.xml:
注意:這裡的resources的屬性值必須設定成各個子配置檔案的類路徑
<?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">
<!--加載的是檔案清單-->
<import resource="classpath:ba06/spring-school.xml" />
<import resource="classpath:ba06/spring-student.xml" />
</beans>
如上面所示,僅僅隻有兩個子配置檔案,當開發的子產品比較多,此時就要寫很多的子配置檔案引入,那麼有沒有可以節省的寫法呢?答案是有的,觀察上面兩個子配置檔案的格式可以發現他們具有很多相同的部分,于是spring提供了通配符的寫法可以非常快速的引入各個子配置檔案,但是要求我們的總配置檔案不能和各個子配置檔案格式相同,否則陷入循環依賴中,即自己循環加載自己, 例如将總配置檔案spring-total更名為total.xml。
total.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">
<!--
在包含關系的配置檔案中,可以通配符(*:表示任意字元)
注意: 主的配置檔案名稱不能包含在通配符的範圍内(不能叫做spring-total.xml)
-->
<import resource="classpath:ba06/spring-*.xml" />
</beans>
3、基于注解的依賴注入
基于xml的依賴注入方式最重要也是最麻煩的部分就是各種bean的配置,數量少的時候還可以忍受,但是數量非常多的時候,顯然就會很麻煩。是以,spring提供了另一種實作依賴注入的方式,也就是基于注解的依賴注入。但是,使用注解的前提是要給各個類開啟支援注解,這就需要在spring的配置檔案中配置一個元件掃描器,告訴spring給哪些類支援注解。
spring配置檔案spring.xml中添加元件掃描器:
設定component-scan屬性聲明元件掃描:base-package:指定注解的項目包名。
<context:component-scan base-package="com.bjpowernode.ba02" />
3.1 定義Bean的注解@Component
有了元件掃描器後,各個包下的類就可以使用注解進行bean的聲明、指派操作了。針對我們上面用到的Student實體類,隻需要在類的上面添加@component注解即可,
注意到前面用到了實體類這個詞,聰明的各位想必已經猜到針對不同類,聲明bean用到的注解也是不一樣的。這裡的不同主要是根據開發時不同的接口來區分的,如下表:
注解 | 類 | 示例 |
@component | 實體類 | Student |
@Repository | dao接口的實作類 | StudentDaoImpl |
@Service | service接口的實作類 | StudentServiceImpl |
@Controller | controller接口的實作類 | StudentController |
3.2 簡單類型屬性注入@value
對于簡單類型注入,需要在屬性上使用注解@Value,該注解的value屬性用于指定要注入的值。 使用該注解完成屬性注入時,類中無需setter。當然,若屬性有setter,則也可将其加到setter上。
Student類注解:
@Component("myStudent")
public class Student {
/**
* @Value: 簡單類型的屬性指派
* 屬性: value 是String類型的,表示簡單類型的屬性值
* 位置: 1.在屬性定義的上面,無需set方法,推薦使用。
* 2.在set方法的上面
*/
@Value("lisi") //使用屬性配置檔案中的資料
private String name;
@Value("20") //使用屬性配置檔案中的資料
private Integer age;
public Student() {
System.out.println("==student無參數構造方法===");
}
}
利用@value注解實作屬性值的注入,确實非常友善,不需要再去配置bean,寫一個個的property了,然而當我們想要更改屬性值的時候,卻必須進到java代碼中修改,這貌似有億點點不友好。熟悉jdbc的朋友們相比都是用過jdbc屬性配置檔案, 這裡,spring針對value注解同樣提供了屬性配置檔案的方式,然後我們就可以像jdbc的屬性配置檔案一樣通過${屬性名}的方式設定value的值,下面就看一下怎麼操作😁
👉第一步:和spring的配置檔案同級的目錄下建立一個屬性配置檔案,test.properties
👉第二步:在spring配置檔案中加載屬性配置檔案,類似于mybatis配置中加載jdbc屬性配置檔案
👉第三步:@value注解用${myname},${myage}設定屬性值
2.3.3 byType自動注入@AutoWired
需要在引用屬性上使用注解@Autowired,該注解預設使用按類型自動裝配Bean的方式。 使用該注解完成屬性注入時,類中無需setter。當然,若屬性有setter,則也可将其加到setter上。
School類添加注解:
@Component("mySchool")
public class School {
@Value("北京大學")
private String name;
@Value("北京的海澱區")
private String address;
public void setName(String name) {
this.name = name;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
Student類添加注解:
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
@Autowired
private School school;
public Student() {
System.out.println("==student無參數構造方法===");
}
public void setName(String name) {
this.name = name;
}
@Value("30")
public void setAge(Integer age) {
System.out.println("setAge:"+age);
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
3.4 byName自動注入@AutoWired和@Qualifier
byName自動注入的方式比byType稍微複雜一點,需要使用兩個注解。@AutoWired表示自動注入,@Qualifier通過别名表示注入的是哪一個引用。
修改Student類注解:
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
//byName自動注入
@Autowired
@Qualifier("mySchool")
private School school;
public Student() {
System.out.println("==student無參數構造方法===");
}
public void setName(String name) {
this.name = name;
}
@Value("30")
public void setAge(Integer age) {
System.out.println("setAge:"+age);
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
個人感覺byName這種雖然會麻煩一點,但是對School類沒有同源的限制,且用且體會吧。
3.5 JDK注解@Resource自動注入
Spring提供了對jdk中@Resource注解的支援。@Resource注解既可以按名稱比對Bean,也可以按類型比對Bean。預設是按名稱注入。使用該注解,要求JDK必須是6及以上版本。@Resource可在屬性上,也可在set方法上。
@Resource注解預設支援的是byType型的,如果給@Resource注解設定屬性值的話,自動轉換為byName型的,聽到這腦海裡就一句話,"前面的又白學了😵"。
byType👉修改Student類注解:
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
@Resource
private School school;
//...
}
byname👉修改Student類注解:
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
//隻使用byName
@Resource(name = "mySchool")
private School school;
//...
}
4、XML和注解兩種方式的對比
兩種依賴注入的實作方式各有長短,對比如下:
注解優點是:
👉友善
👉 直覺
👉 高效(代碼少,沒有配置檔案的書寫那麼複雜)。
其弊端也顯而易見:以寫死的方式寫入到Java代碼中,修改是需要重新編譯代碼的。
XML方式優點是:
👉配置和代碼是分離的
👉 在xml中做修改,無需編譯代碼,隻需重新開機伺服器即可将新的配置加載。
其弊端也顯而易見:編寫麻煩,效率低,大型項目過于複雜。
具體怎麼選擇相信根據項目的大小、場景甚至個人喜好,大家都會找到最合适自己選擇。
5、小結
了解了spring中實作依賴注入的兩種方式,不僅能夠體會到控制反轉的妙處,也能大大加快我們的開發效率。關于⭐set注入⭐、⭐多檔案配置⭐、⭐屬性配置檔案⭐以及⭐自動注入的兩種類型byName和byType⭐都是後續過程中經常使用的,建議重點掌握。感謝大家的觀看,一起加油,一起進步!