前言
對于任何一個 Java 開發(fā)人員,Spring 的大名一定如雷貫耳,在行業(yè)中可謂是無人不知、無人不曉,說它是 Java 領(lǐng)域第一框架毫不為過。
(圖片來自 Spring 官網(wǎng))
Spring 概念誕生于 2002 年,創(chuàng)始人 Rod Jahnson 在其著作《Expert One-on-One J2EE Design and Development》中第一次提出了 Spring 的核心思想,于 2003 年正式發(fā)布第一個版本 Spring Framework 0.9。
經(jīng)過十幾年的優(yōu)化迭代,Spring Framework 已經(jīng)從最初的取代 EJB 框架逐步發(fā)展為一套完整的生態(tài),最新的版本是 5.X,支持現(xiàn)代 Java 開發(fā)的各個技術(shù)領(lǐng)域,家族兩大核心成員 Spring Boot 和 Spring Cloud 更是當(dāng)下 Java 領(lǐng)域最為熱門的技術(shù)棧。
毋庸置疑,Spring 已經(jīng)成為 Java 開發(fā)的行業(yè)標(biāo)準(zhǔn),無論你是初級程序員還是架構(gòu)師,只要是做 Java 開發(fā)的,工作中或多或少一定會接觸到 Spring 相關(guān)技術(shù)棧。
我們所說的 Spring 全家桶各個模塊都是基于 Spring Framework 衍生而來,通常所說的 Spring 框架一般泛指 Spring Framework,它包含 IoC 控制反轉(zhuǎn)、DI 依賴注入、AOP 面向切面編程、Context 上下文、bean 管理、Spring Web MVC 等眾多功能模塊,其他的 Spring 家族成員都需要依賴 Spring Framework。
可以簡單理解 Spring Framework 是一個設(shè)計層面的框架,通過分層思想來實(shí)現(xiàn)組件之間的解耦合,開發(fā)者可以根據(jù)需求選擇不同的組件,并且可以非常方便的進(jìn)行集成,Spring Framework 的這一特性使得企業(yè)級項目開發(fā)變得更加簡單方便。
Spring 的兩大核心機(jī)制是 IoC(控制反轉(zhuǎn))和 AOP(面向切面編程),對于初學(xué)者來講,搞清楚這兩個核心機(jī)制就掌握了 Spring 的基本應(yīng)用。這兩大核心機(jī)制也是 Java 設(shè)計模式的典型代表,其中 IoC 是工廠模式,AOP 是代理模式。
什么是 IoC 和 AOP
下面來詳細(xì)了解 IoC,IoC 是 Spring 框架的靈魂,非常重要,理解了 IoC 才能真正掌握 Spring 框架的使用。
IoC 也叫控制反轉(zhuǎn),首先從字面意思理解,什么叫控制反轉(zhuǎn)?反轉(zhuǎn)的是什么?
在傳統(tǒng)的程序開發(fā)中,需要獲取對象時,通常由開發(fā)者來手動創(chuàng)建實(shí)例化對象,但是在 Spring 框架中創(chuàng)建對象的工作不再由開發(fā)者完成,而是交給 IoC 容器來創(chuàng)建,我們直接獲取即可,整個流程完成反轉(zhuǎn),因此是控制反轉(zhuǎn)。
舉個例子:超市購物。
- 傳統(tǒng)方式:你去超市買東西,需要自己拿著袋子去超市購買商品,然后自己把袋子提回來。
- IoC 容器:你只需要把袋子放在家門口,袋子里面會自動裝滿你需要的商品,直接取出來用就可以了。
我們通過創(chuàng)建一個 Student 對象的例子來對比兩種方式的區(qū)別。
傳統(tǒng)方式
(1)創(chuàng)建 Student 類
public class Student {
private int id;
private String name;
private int age;
}
(2)測試方法中調(diào)用構(gòu)造函數(shù)創(chuàng)建對象
Student student = new Student();
IoC 容器
實(shí)現(xiàn)步驟
- 在 pom.xml 中添加 Spring 依賴
- 創(chuàng)建配置文件,可以自定義文件名 spring.xml
- 在 spring.xml 中配置 bean 標(biāo)簽,IoC 容器通過加載 bean 標(biāo)簽來創(chuàng)建對象
- 調(diào)用 API 獲取 IoC 創(chuàng)建的對象
IoC 容器可以調(diào)用無參構(gòu)造或者有參構(gòu)造來創(chuàng)建對象,我們先來看無參構(gòu)造的方式。
無參構(gòu)造
<!-- 配置 student 對象-->
<bean id="stu" class="com.southwind.entity.Student"</bean>
配置一個 bean 標(biāo)簽:
- id,對象名
- class,對象的模板類
接下來調(diào)用 API 獲取對象,Spring 提供了兩種方式來獲取對象:id 或者運(yùn)行時類。
(1)通過 id 獲取對象
//1.加載 spring.xml 配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//2.通過 id 值獲取對象
Student stu = (Student) applicationContext.getBean("stu");
System.out.println(stu);
第一步:加載 spring.xml 配置文件,生成 ApplicationContext 對象。
第二步:調(diào)用 ApplicationContext 的 getBean 方法獲取對象,參數(shù)為配置文件中的 id 值。程序在加載 spring.xml 時創(chuàng)建 stu 對象,通過反射機(jī)制調(diào)用無參構(gòu)造函數(shù),所有要求交給 IoC 容器管理的類必須有無參構(gòu)造函數(shù)。
運(yùn)行結(jié)果請點(diǎn)擊《Spring 全家桶》內(nèi)容查看。
可以看到,此時 stu 對象的屬性全部為空,因為調(diào)用無參構(gòu)造只會創(chuàng)建對象而不會進(jìn)行賦值,如何賦值呢?只需要在 spring.xml 中進(jìn)行相關(guān)配置即可,如下所示。
<!-- 配置 student 對象 -->
<bean id="stu" class="com.southwind.entity.Student">
<property name="id" value="1"></property>
<property name="name" value="張三"></property>
<property name="age" value="23"></property>
</bean>
添加 property 標(biāo)簽:name 對應(yīng)屬性名,value 是屬性的值。若包含特殊字符,比如 name="<張三>",使用 <![CDATA[<張三>]]> 進(jìn)行配置,如下所示。
<!-- 配置 student 對象 -->
<bean id="stu" class="com.southwind.entity.Student">
<property name="id" value="1"></property>
<property name="name">
<value><![CDATA[<張三>]]></value>
</property>
<property name="age" value="23"></property>
</bean>
運(yùn)行結(jié)果請點(diǎn)擊《Spring 全家桶》內(nèi)容查看。
Spring 通過調(diào)用每個屬性的 setter 方法來完成屬性的賦值,因此實(shí)體類必須有 setter 方法,否則加載時報錯,getter 方法可省略。
(2)通過運(yùn)行時類獲取對象
//1.加載 spring.xml 配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//2.通過運(yùn)行時類獲取對象
Student stu = applicationContext.getBean(Student.class);
System.out.println(stu);
此方法有一個弊端,當(dāng) spring.xml 中配置兩個 Student 的 bean 時程序會拋出異常,因為此時兩個 bean 都是由 Student 類生成的,IoC 容器無法將兩個 bean 都返回,必須指定一個唯一的 bean。
<bean id="stu" class="com.hzit.entity.Student">
<property name="id" value="1"></property>
<property name="name">
<value><![CDATA[<張三>]]></value>
</property>
<property name="age" value="23"></property>
</bean>
<bean id="stu2" class="com.hzit.entity.Student">
<property name="id" value="1"></property>
<property name="name" value="李四"></property>
<property name="age" value="23"></property>
</bean>
異常信息如下圖所示。
運(yùn)行結(jié)果請點(diǎn)擊《Spring 全家桶》內(nèi)容查看。
以上是 IoC 容器通過無參構(gòu)造創(chuàng)建對象的方式,同時 IoC 容器也可以調(diào)用有參構(gòu)造來創(chuàng)建對象。
有參構(gòu)造
(1)在實(shí)體類中創(chuàng)建有參構(gòu)造
public Student(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
(2)spring.xml 中進(jìn)行配置
<!-- 通過有參構(gòu)造函數(shù)創(chuàng)建對象 -->
<bean id="stu3" class="com.hzit.entity.Student">
<constructor-arg name="id" value="3"></constructor-arg>
<constructor-arg name="name" value="小明"></constructor-arg>
<constructor-arg name="age" value="22"></constructor-arg>
</bean>
(3)此時 IoC 容器會根據(jù) constructor-arg 標(biāo)簽去加載對應(yīng)的有參構(gòu)造函數(shù),創(chuàng)建對象并完成屬性賦值。name 的值需要與有參構(gòu)造的形參名對應(yīng),value 是對應(yīng)的值。除了使用 name 對應(yīng)參數(shù)外,還可以通過下標(biāo) index 對應(yīng),如下所示。
<!-- 通過有參構(gòu)造函數(shù)創(chuàng)建對象 -->
<bean id="stu3" class="com.hzit.entity.Student">
<constructor-arg index="0" value="3"></constructor-arg>
<constructor-arg index="1" value="小明"></constructor-arg>
<constructor-arg index="2" value="22"></constructor-arg>
</bean>
以上是 IoC 容器通過有參構(gòu)造創(chuàng)建對象的方式,獲取對象同樣有兩種方式可以選擇:id 和運(yùn)行時類。
如果 IoC 容器管理多個對象,并且對象之間有級聯(lián)關(guān)系,如何實(shí)現(xiàn)?
(1)創(chuàng)建 Classes 類
public class Classes {
private int id;
private String name;
}
(2)在 Student 類中添加 Classes 屬性
public class Student {
private int id;
private String name;
private int age;
private Classes classes;
}
(3)spring.xml 中配置 classes 對象,然后將該對象賦值給 stu 對象
<!-- 創(chuàng)建 classes 對象 -->
<bean id="classes" class="com.hzit.entity.Classes">
<property name="id" value="1"></property>
<property name="name" value="Java班"></property>
</bean>
<!-- 創(chuàng)建 stu 對象 -->
<bean id="stu" class="com.hzit.entity.Student">
<property name="id" value="1"></property>
<property name="name">
<value><![CDATA[<張三>]]></value>
</property>
<property name="age" value="23"></property>
<!-- 將 classes 對象賦給 stu 對象 -->
<property name="classes" ref="classes"></property>
</bean>
再次獲取 Student 對象,運(yùn)行結(jié)果請點(diǎn)擊《Spring 全家桶》內(nèi)容查看。
在 spring.xml 中,通過 ref 屬性將其他 bean 賦給當(dāng)前 bean 對象,這種方式叫做依賴注入(DI),是 Spring 非常重要的機(jī)制,DI 是將不同對象進(jìn)行關(guān)聯(lián)的一種方式,是 IoC 的具體實(shí)現(xiàn)方式,通常 DI 和 IoC 是緊密結(jié)合在一起的,因此一般說的 IoC 包括 DI。
如果是集合屬性如何依賴注入?
(1)Classes 類中添加 List<Student> 屬性。
public class Classes {
private int id;
private String name;
private List<Student> students;
}
(2)spring.xml 中配置 2 個 student 對象、1 個 classes 對象,并將 2 個 student 對象注入到 classes 對象中。
<!-- 配置 classes 對象 -->
<bean id="classes" class="com.hzit.entity.Classes">
<property name="id" value="1"></property>
<property name="name" value="Java班"></property>
<property name="students">
<!-- 注入 student 對象 -->
<list>
<ref bean="stu"/>
<ref bean="stu2"/>
</list>
</property>
</bean>
<bean id="stu" class="com.hzit.entity.Student">
<property name="id" value="1"></property>
<property name="name">
<value><![CDATA[<張三>]]></value>
</property>
<property name="age" value="23"></property>
</bean>
<bean id="stu2" class="com.hzit.entity.Student">
<property name="id" value="2"></property>
<property name="name" value="李四"></property>
<property name="age" value="23"></property>
</bean>
運(yùn)行結(jié)果請點(diǎn)擊《Spring 全家桶》內(nèi)容查看。
集合屬性通過 list 標(biāo)簽和 ref 標(biāo)簽完成注入,ref 的 bean 屬性指向需要注入的 bean 對象。
總結(jié)
這一講我們講解了 Spring IoC 的基本概念以及如何使用,IoC 是 Spring 的核心,這很重要。使用 Spring 開發(fā)項目時,控制層、業(yè)務(wù)層、DAO 層都是通過 IoC 來完成依賴注入的。