Spring IOC 容器的基本使用
本文的代碼過多,但是每個點我會儘可能的寫的很詳細
一、爲什麼要使用 Spring?
1.1 傳統的 MVC 架構的程序
我們平時編寫一個小項目的時候,一般會採用 MVC 三層架構來編寫一個項目
- M —— modal 模型層(實體類)
- V —— views 視圖層 (界面)
- C —— controller 控制層 (用來完執行一些操作)
這三層架構各自分工,獨自完成相對應的功能,但是這樣的程序寫出來會導致程序之間耦合性過高
1.2 程序耦合性過高?
耦合性過高實際上說的是程序之間的依賴性過高,解耦說的就是降低程序之間的依賴關係
我們使用 Java 通常都是寫好一個類,構造方法,setter 和 getter 等等,我們在其他的類中使用該類就得 創建一個 該類的對象,然後通過對象調用該類的各種方法。這樣整個程序之間就會產生多個類,對應的也會產生多個對象互相進行調用,因此我們整體的程序就會體現出耦合性過高的特點。
1.3 如何解耦?
- 我們在 Java SE 中學習過 JDBC,也學習過 properties 對象, 我們可以把 jdbc 的一些配置寫進文件中。
- 我們傳統一般都是創建對象,我們可以換一種方式,通過 Java 的反射機制獲取類的信息,並且創建對象
- 讀取 xml 文件
1.4 Spring IOC 的依賴注入
Spring 框架正式爲了解決這樣的情況出現了,它提供了 讀取 xml配置,以及註解 兩種方式實現 bean 的自動注入,而被注入的容器叫做 IOC 容器
依賴注入:
Dependency Injection
IOC 的作用:
降低程序鍵的耦合(依賴關係)
依賴關係的管理:
以後都交給 spring 來維護
在當前類需要用到其他類的對象,由Spring來爲我們提供,我們只需要在配置文件中說明
依賴關係的維護:
就稱爲依賴注入
依賴注入:
能注入的數據,有三類
基本類型和 string
其他 bean 類型(在配置文件中或者註解配置過的 bean—)
複雜類型、集合類型
注入的方式:有三種
第一種:使用構造函數提供
第二種:使用 set方法提供
第三種:使用註解提供
二、Spring IOC 的依賴注入(使用 xml 完成注入)
2.1 使用構造函數完成依賴注入
2.1.1 標籤的使用講解
- 使用的標籤:constructor-arg
- 標籤出現的位置:bean標籤的內部
- 標籤中的屬性
1. type:用於指定要注入的數據的數據類型,該數據類型可以是構造函數中某個或某些參數的類型
2. index: 用於指定要注入的數據給構造函數中指定索引位置的參數賦值,索引的位置也是從 0 開始
3. name:用於指定構造函數中指定名稱的參數賦值
以上三個標籤用於指定給構造函數中哪個參數賦值
5. value: 用於給基本類型和 String類型的數據
6. ref:用於指定其它的 bean 類型數據,它指的就是 spring IOC 核心容器中出現過的 bean 對象
2.1.2 構造函數依賴注入的優缺點
- 優勢:
在獲取 bean 對象時,注入數據是必須的操作,否則對象無法創建成功 - 弊端:
改變了bean 對象的實例化方式,使我們在創建對象時,如果用不到,也必須創建
2.1.3 使用構造函數完成依賴注入的實例
注意:後面的修改全部都是基於此類的修改在這裏插入代碼片
- 編寫 bean.xml 配置文件
- 編寫一個 AccountService 接口,後面的三種方法都要實現該接口
- 編寫 AccountServiceImpl 實現該接口,並且記住該類的名稱和位置
- 編寫一個 Client 類,用來測試該接口中的方法
bean.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 使用構造函數注入
使用的標籤:constructor-arg
標籤出現的位置:bean標籤的內部
標籤中的屬性
type:用於指定要注入的數據的數據類型,該數據類型可以是構造函數中某個或某些參數的類型
index: 用於指定要注入的數據給構造函數中指定索引位置的參數賦值,索引的位置也是從 0 開始
name:用於指定構造函數中指定名稱的參數賦值
============== 以上三個用於指定給構造函數中哪個參數賦值 ============
value: 用於給基本類型和 String類型的數據
ref:用於指定其它的 bean 類型數據,它指的就是 spring IOC 核心容器中出現過的 bean 對象
優勢:
在獲取 bean 對象時,注入數據是必須的操作,否則對象無法創建成功
弊端:
改變了bean 對象的實例化方式,使我們在創建對象時,如果用不到,也必須創建
-->
<bean id="accountService" class="com.itheima.service.impl.IAccountServceImpl">
<constructor-arg name="name" value="text"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!-- 配置一個日期對象,上面會引用到 now -->
<bean id="now" class="java.util.Date"></bean>
</beans>
IAccountService 接口編寫
package com.itheima.service;
public interface IAccountService {
void saveAccount();
}
IAccountServceImpl 接口實現類的編寫
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
import java.util.Date;
/**
* 賬戶業務層的實現類
* 構造函數的注入
* */
public class IAccountServceImpl implements IAccountService {
// 經常變化的數據,並不適用於注入的方式
private String name;
private Integer age;
private Date birthday;
// 創建有參的構造方法,這個方法必須存在, 在xml中,數據就是通過有參的構造方法擦混入的
public IAccountServceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public IAccountServceImpl() {
System.out.println("對象創建了");
}
public void saveAccount() {
System.out.println("service 中的 saveAccount 方法執行了"+this.name + " "+ this.age + " " + this.birthday);
}
@Override
public String toString() {
return "IAccountServceImpl{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
}
Client 類的編寫
package com.itheima.client;
import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 模擬一個表現層,用於調用業務層
* */
public class Client {
public static void main(String[] args) {
// 使用 mvc 三層架構,編寫 (耦合性過高)
// IAccountService as = new IAccountServceImpl();
// =============== 劃重點 ===============
// 1. 獲取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// ApplicationContext ac = new FileSystemXmlApplicationContext("xxx"); // 這裏填寫配置文件,在你本機上的物理地址,很少用戶
// 2. 根據 id 獲取 Bean 對象 (方式一)
IAccountService as = (IAccountService) ac.getBean("accountService");
System.out.println(as);
as.saveAccount();
//2. 根據 id 獲取 Bean 對象 (方式二)
// IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);
// as.saveAccount();
// System.out.println(adao);
}
}
運行結果:
我們沒有使用 傳統的方式,接用 Spring 框架完成了 bean 的實例化
2.2 使用 setter 完成注入
2.2.1 使用 setter 完成依賴注入的功能
涉及的標籤:property
出現的位置:bean 標籤的內部
標籤的屬性:
name:用於指定注入時所用的 set 方法名稱
== 以上三個用於指定給構造函數中哪個參數賦值 ==
value: 用於給基本類型和 String類型的數據
ref:用於指定其它的 bean 類型數據,它指的就是 spring IOC 核心容器中出現過的 bean 對象
2.2.2 基於 setter 完成依賴注入的分析
- 優勢:
創建對象時沒有明確的限制,可以直接使用默認構造函數 - 弊端:
如果某個成員必須有值,則獲取對象可能 set 方法沒有執行
有了前面的內容做鋪墊,接下來做 setter 注入就會輕鬆很多,我們需要做如下步驟
- 在 bean.xml 添加依賴
<!-- setter 方法注入
涉及的標籤:property
出現的位置:bean 標籤的內部
標籤的屬性:
name:用於指定注入時所用的 set 方法名稱
============== 以上三個用於指定給構造函數中哪個參數賦值 ============
value: 用於給基本類型和 String類型的數據
ref:用於指定其它的 bean 類型數據,它指的就是 spring IOC 核心容器中出現過的 bean 對象
優勢:
創建對象時沒有明確的限制,可以直接使用默認構造函數
弊端:
如果某個成員必須有值,則獲取對象可能 set 方法沒有執行
-->
<bean class="com.itheima.service.impl.IAccountServiceImpl2" id="accountService2">
<property name="name" value="小紅"></property>
<property name="age" value="19"></property>
<property name="birthday" value="2000/4/12"></property>
</bean>
- 編寫 IAccountServiceImpl2
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
import java.util.Date;
/**
* setter 注入
* */
public class IAccountServiceImpl2 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "IAccountServiceImpl2{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
public void saveAccount() {
System.out.println("service 中的 saveAccount 方法執行了");
}
}
- Client 內容修改
package com.itheima.client;
import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 模擬一個表現層,用於調用業務層
* */
public class Client {
public static void main(String[] args) {
// 1. 獲取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2. 根據 id 獲取 Bean 對象 (兩種方式)
IAccountService as = (IAccountService) ac.getBean("accountService2");
System.out.println(as);
as.saveAccount();
}
}
- 效果圖(數據成功通過 setter 注入)
2.3 複雜數據類型注入
2.3.1 集合數據類型注入使用場景
複雜類型的注入,集合類型的注入
常用 list 和 map
用於給 List 結構集合注入的標籤
list array set
用於給 Map 結構集合注入的標籤
map props
結構相同,標籤可以互換
2.3.2 集合類型的數據注入
- bean.xml 的配置
<!--
複雜類型的注入,集合類型的注入
常用 list 和 map
用於給 List 結構集合注入的標籤
list array set
用於給 Map 結構集合注入的標籤
map props
結構相同,標籤可以互換
IOC 內容結束
-->
<bean id="accountService3" class="com.itheima.service.impl.IAccountServiceImpl3">
<!-- 字符串注入 -->
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 集合 List 的注入 -->
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 集合 Set 注入-->
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- Map 注入 -->
<property name="myMap">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
<!-- Properties 注入-->
<property name="myPros">
<props>
<prop key="testC">CCC</prop>
<prop key="testD">DDD</prop>
</props>
</property>
</bean>
- IAccountServiceImpl3 編寫
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
import java.util.*;
public class IAccountServiceImpl3 implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myPros;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyPros(Properties myPros) {
this.myPros = myPros;
}
public void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myPros);
}
@Override
public String toString() {
return "IAccountServiceImpl3{" +
"myStrs=" + Arrays.toString(myStrs) +
", myList=" + myList +
", mySet=" + mySet +
", myMap=" + myMap +
", myPros=" + myPros +
'}';
}
}
- Client 編寫
package com.itheima.client;
import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
// 1. 獲取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2. 根據 id 獲取 Bean 對象 (兩種方式)
IAccountService as = (IAccountService) ac.getBean("accountService3");
System.out.println(as);
as.saveAccount();
}
}
- 效果圖
三、使用註解完成 Spring 的 IOC 注入
bean.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 使用註解來實現注入 -->
<!-- 告知 spring 在創建容器時要掃描的包,配置所需要的標籤不是 在 beans 約束中,而是一個名爲 context
名稱空間和約束中( 一個標籤 + 註解的方式完成 IOC 注入)-->
<context:component-scan base-package="com.itheima"></context:component-scan>
</beans>
編寫 IAccountService 接口
package com.itheima.service;
public interface IAccountService {
void saveAccount();
}
編寫 IAccountDao 接口
package com.itheima.dao;
/**
* 賬戶持久層接口
* */
public interface IAccountDao {
/**
* 模擬保存賬戶
* */
void saveAccount();
}
3.1 用於創建對象的註解
我們在這裏實現 IAccountService 接口 創建 IAccountServiceImpl類
3.1.1 普通方法創建對象
package com.itheima.service.impl;
/* 曾經的 xml 配置,我們要在這裏協商這麼一大段的 xml 纔可以完成注入
* <bean id="accountService"
* class="com.itheima.service.impl.IAccountServceImpl
* <property name="" value="" | ref =""></property>
* "/>
*/
import com.itheima.service.IAccountService;
import com.itheima.dao.IAccountDao;
public class IAccountServiceImpl implements IAccountService {
//1、 採用 new 創建對象,在外面還是需要通過類來創建對象解決
private IAccountDao accountDao = new IAccountDaoImpl();
public IAccountServiceImpl() {
System.out.println("對象創建了");
}
public void saveAccount() {
int i =1;
accountDao.saveAccount();
System.out.println(i);
i++;
}
@Override
public String toString() {
return "IAccountServiceImpl{" +
"accountDao=" + accountDao +
'}';
}
}
3.1.2 Componet 註解(還有和它功能一樣的三層註解)
和它功能相同的還有
- Controller: 一般用在表現層
- Service: 一般用在業務層
- Respository: 一般用在持久層
三層註解的解讀:
* 用於創建對象的註解
* 他們的作用就和在 XML 配置文件中編寫一個 <bean> 標籤實現的功能是一樣的
* @Componet
* 作用:用於把當前類對象存入spring 容器中
* 屬性:
* value 用於指定 bean 的 id,當我們不寫時,它的默認是當前類名,(AccountService => accountService)
* 我的是兩個首字母都是大寫,因此不用改 IAccountServceImpl
* Controller: 一般用在表現層
* Service: 一般用在業務層
* Respository: 一般用在持久層
* 以上三個註解的作用和屬性與 Component 是一模一樣的,
* 他們三個是 spring 框架爲我們提供明確的三層使用的註解,使我們三層對象更加清晰
他們的用法都是一樣的,如下
實現: IAccountService 接口
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
import com.itheima.dao.IAccountDao;
import org.springframework.stereotype.Component;
@Component(value = "accountService")
//@Service(value = "accountService") // 該註解和上面的註解功能是一模一樣的,只是用來區分使用情景的,如果有兩個註解,則 value 不能省去
public class IAccountServiceImpl implements IAccountService {
// 這裏的值爲空,等會可以看到,因爲我們只是通過 Spring 創建了對象,但是並沒有把對象注入
private IAccountDao accountDao;
public void saveAccount() {
int i =1;
System.out.println(i);
i++;
}
@Override
public String toString() {
return "IAccountServiceImpl{" +
"accountDao=" + accountDao +
'}';
}
}
實現:IAccountDao 接口
package com.itheima.dao.impl;
import com.itheima.dao.IAccountDao;
import org.springframework.stereotype.Repository;
/**
* 賬戶持久層的實現類
* */
@Repository("accountDao") // 該註解功能同 Componet ,使用在持久層
public class IAccountDaoImpl implements IAccountDao {
/**
* 模擬保存賬戶
*/
public void saveAccount() {
System.out.println("保存了賬戶");
}
@Override
public String toString() {
return "IAccountDaoImpl{}";
}
}
創建 Client 類
package com.itheima.client;
import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 模擬一個表現層,用於調用業務層
* */
public class Client {
/**
* 獲取 spring 的 Ioc 核心容器,並根據 id 獲取對象
* ApplicationCpmtext 的三個常用實現類
* ClassPathXMLApplicationContext:它可以加載類路徑下的配置文件,要求配置文件必須在類路徑下,不在的話,加載不了
* FileSystemXmlApplicationContext:它可以加載磁盤任意路徑下的配置文件(必須有訪問權限)
* AnnotationConfigApplicationContext:它是用於讀取註解解耦容器的
*
* 核心容器的兩個接口引發出的問題
* ApplicationContext:單例 採用此接口
* 它在構建核心容器時,創建對象採用的策略是利用加載的方式,也就是說,只要一讀取完配置文件馬上就創建配置文件中的配置對象
* BeanFactory: 多例
* 它在構建核心容器時:創建對象採取的策略是採用延遲加載的方式。也就是說,什麼時候根據 id 獲取對象了,什麼時候才真正的創建對象
* */
public static void main(String[] args) {
// 1. 獲取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2. 根據 id 獲取 Bean 對象 (兩種方式)
IAccountService as = (IAccountService) ac.getBean("accountService");
System.out.println(as);
as.saveAccount();
}
}
運行結果:
(因爲我並沒有進行對象注入,所以這裏的值爲 null)
3.2 用於注入數據的註解
3.2.1 數據方式剖析
* 他們的作用就和在 xml 配置文件中的 bean 標籤寫一個 <property>標籤的作用是一樣的
* Autowired:
* 作用:自動按照類型注入。只要容器中有唯一的 bean 對象類型和要注入的變量類型匹配,就可以注入成功
* 如果 IOC 容器中沒有任何 bean 的類型和要注入的變量類型匹配,則報錯
* 如果 IOC 容器中有多個類型匹配時
* 出現位置:
* 可以是變量上,也可以是方法上
* 細節:
* 在使用註解注入時,set 方法就不是必須的了。
* Qualifier:
* 作用:在按照類中注入的基礎之上再按照名稱注入,它在給類成員注入時不能單獨使用,但是再給方法參數注入時可以使用
* 屬性:
* value: 用於指定注入的 bean 的 id
* 補充:必須和 Autowired 一起使用
* Resource
* 作用:直接按照 Bean 的id 注入,它可以獨立使用 (一個搞定上面兩個)
* 屬性:
* name:用於指定 bean 的id
3.2.2 注入方式一(Autowired + Qualifier)
重新編寫
package com.itheima.service.impl;
/* 注入方式一
* Autowired:
* 作用:自動按照類型注入。只要容器中有唯一的 bean 對象類型和要注入的變量類型匹配,就可以注入成功
* 如果 IOC 容器中沒有任何 bean 的類型和要注入的變量類型匹配,則報錯
* 如果 IOC 容器中有多個類型匹配時
* 出現位置:
* 可以是變量上,也可以是方法上
* 細節:
* 在使用註解注入時,set 方法就不是必須的了。
* Qualifier:
* 作用:在按照類中注入的基礎之上再按照名稱注入,它在給類成員注入時不能單獨使用,但是再給方法參數注入時可以使用
* 屬性:
* value: 用於指定注入的 bean 的 id
* 補充:必須和 Autowired 一起使用
*/
import com.itheima.service.IAccountService;
import com.itheima.dao.IAccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Service(value = "accountService") // 看 Component
public class IAccountServiceImpl implements IAccountService {
// 2、 採用註解的方式
@Autowired
@Qualifier("accountDao")
private IAccountDao accountDao;
public void saveAccount() {
int i =1;
// accountDao.saveAccount();
System.out.println(i);
i++;
}
@Override
public String toString() {
return "IAccountServiceImpl{" +
"accountDao=" + accountDao +
'}';
}
}
運行效果
3.2.3 基於 Resource 的註解
這個註解就是將我們在上面用到兩個註解替換成一個:@Resource(name = "accountDao")
,產生的結果是相同的,這裏我就不放截圖了
四、Spring IOC 注入總結
- 首先在配置文件中加入:
<context:component-scan base-package="com.itheima"></context:component-scan>
,告知 Spring,我們要使用註解,然後我們在裏面填寫:base-package
的值,告知 Spring 他要掃描的包 - 然後在接口的實現類中的類上添加註解(創建對象的注入):
@Component(value = "accountService")
並指定其 value,(如果命名規範的話,比如我寫的是 AccountService,默認值也就是上面的 value值),即可完成創建對象的操作,如果想劃分的更細一點,可以使用如下三種註解,劃分功能- @Controller: 一般用在表現層
- @Service: 一般用在業務層
- @Respository: 一般用在持久層
- 然後在類相對應的方法,完成數據的注入,使用兩種方法都可以
- @Autowired + @Qualifier(“accountDao”) 完成對象的注入
- @Resource(name = “accountDao”) 完成對象的注入