IOC的概念和作用
這兩行代碼就明顯的揭露出IOC的含義
// private IAccountDao dao = new AccountDaoImpl();
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
這兩種代碼代表兩種截然不同創建對象的方式
當我們用new創建對象的時候 我們APP直接和資源取得聯系 他們直接有必然的聯系 資源獨立和應用獨立變得很難 它有明顯的依賴關系
當我們用第二種方式創建對象的時候 APP斷開了與資源的聯系 而是找工廠要資源 讓工廠與資源取得聯系 并把想要的對象轉給應用 從而實現了資源與應用必然的依賴關系
基于這種思想 就是我們所說的IOC
為什么叫控制反轉而不是降低依賴呢?
我們在編寫Service的時候 用new 或者 工廠 都是自己決定的AccountServiceImpl 這個類可以有自主使用的權力 它在尋找Dao的時候可以自主選擇自己想要的Dao的(通過 new)
但是它把自主選擇Dao的權利交給了BeanFactory 然后由固定的名稱找到bean對象 這個bean對象是不是我們能用的 我們就不得而知了 我們不能自主控制 通過 控制權發生了轉移 所以是控制反轉
帶來的好處就是 降低耦合
Spring中IOC的前期準備
IOC只能解耦 降低程序間的依賴關系
前面講了工廠模式來解耦 現在使用配置的方式來實現這些
準備Spring的開發包
下載地址: https://repo.spring.io/libs-release-local/org/springframework/spring/
打開它可以看見目錄結構
doc:API和開發規范
libs:jar包和源碼
schema:約束
spring基于XML的IOC環境搭建和入門
新建一個工程
之前代碼里的dao和service都能用,copy過來,刪掉Factory,原來使用工廠創建對象 改為用new創建
搭建環境
導入denpendency以后 多了這些jar包 spring把你可能會用到的jar包都導進來了
通過 IDEA maven 的 show dependencies 功能 可以看到 beans core aop expreesion 這四個核心容器
簡單來說核心容器就是個map 里面放著封裝的對象
仍然需要一個配置文件,所以創建beans.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來管理
接下來 在Client這個類中,之前的代碼需要改造下.
這個類要做到兩件事:
1.獲取核心容器對象
2.根據ID獲取Beans
獲取核心容器的 ApplicationContext類 有兩個實現類 分別是
①ClassPathXmlApplicationContext
②FileSystemXmlApplicationContext
這兩個都是基于Xml配置的
public class Client {
/**
* 獲取核心容器對象并據ID獲取對象
* @param args
*/
public static void main(String[] args) {
//獲取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
//根據id獲取Bean對象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao adao = ac.getBean("acccountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
}
getBean的另外一種方式,傳入一字節碼,根據字節碼來獲取對象類型,另外一種是Object類型 我們自己強轉.
ApplicationContext的三個實現類
①ClassPathXmlApplicationContext:它可以加載類路徑下的所有配置文件,要求配置文件必須在類路徑下.不在的話加載不了
②FlieSystemXmlApplicationContext:它可以加載磁盤任意路徑下的配置文件(必須要有訪問權限)
③AnotationXmlApplicationContext:它是用注解創建容器的
所以用FileSystemXmlApplicationContext來獲取核心容器的話,改動僅需把
ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\WorkSpace\\day01_eesy_03spring\\src\\main\\resources\\beans.xml");
這里改動即可
public class Client {
/**
* 獲取核心容器對象并據ID獲取對象
* @param args
*/
public static void main(String[] args) {
//獲取核心容器對象
//ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\WorkSpace\\day01_eesy_03spring\\src\\main\\resources\\beans.xml");
//根據id獲取Bean對象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
}
BeanFactory和ApplicationContext的區別
核心容器的兩個接口引發出的問題
ApplicationContext:它在構建核心容器的時候,采用的立即加載的方式,也就是說只要一讀取完配置文件就馬上加載.
BeanFactory:它在構建核心容器的時候,采用的是延遲加載的方式,也就是說什么時候根據id獲取對象了,什么時候才創建.
驗證 ApplicationContext 的加載:
在Service中增加一個構造函數
public class AccountServiceImpl implements IAccountService {
/**
* 模擬保存賬戶
*/
private IAccountDao accountDao = new AccountDaoImpl();
public AccountServiceImpl() {
System.out.println("AccountServiceImpl創建了");
}
@Override
public void saveAccount() {
}
}
在Client中打上斷點 可以看到剛讀取完配置文件,對象就被創建了
BeanFactory接口:
首先 先定義BeanFactory factory = null; Alt + Ctrl + B 可以看見BeanFactory的實現類,我們選擇了 XmlBeanFactory,雖然它已經過時了
于是 BeanFactory factory = new XmlBeanFactory();
XmlBeanFactory 需要一個參數對象 ,ctrl + 左鍵 可以看到 需要一個Resource對象
查看類的實現 是一個很好的思維方式,從老師這里學到的.
選擇了 ClassPathResource 我們需要去類路徑尋找我們的bean,xml
再往后的步驟都一樣了
public static void main(String[] args) {
/*--------------------BeanFactory------------------------*/
Resource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
//通過id獲取Bean對象
IAccountService as = (IAccountService) factory.getBean("accountService");
System.out.println(as);
}
打斷點 看執行的時候什么時候創建對象 可以看到 再讀完配置文件 配置好工廠后 都沒有創建好對象 通過id獲取Bean對象的時候 才真正創建了對象
所以兩種創建對象的時間點是不一樣的
立即加載 在工廠模式下 Service和Dao由于沒有類成員,不存在線程安全的問題,所以在此基礎上,可以直接選用單例模式創建對象,既然是單例模式,對象只會創建一次,所以可以使用立即加載的方式.只要容器創建,就馬上創建對象,適用于單例對象.
延遲加載 什么時候用,什么時候才真正的創建對象 適用于多例模式
如果在一加載容器的時候就創建對象,第二次使用的時候又再創建一次對象,不如在什么時候用的時候什么時候創建更合適
Spring 可以根據配置上的不同 可以改變對象創建的方式
BeanFactory 是個頂層接口 功能不是那么完善 實現類和子接口會在BeanFactory的功能上進行拓展,所以實際開發中使用applicationContext比較多
applicationContext 如何判定單例還是多例
spring中bean的細節之三種創建Bean對象的方式
準備一個新的工程
找到03里的一些源碼直接復制到04工程中,bean.xml也復制一份.刪掉dao.(為了更簡潔)
Spring對Bean的管理細節
-
創建Bean的三種方式
-
使用默認構造函數創建
在Spring的配置文件中 使用bean標簽中 配以id和class屬性以后 沒有其他的屬性和標簽時 采用的就是使用默認構造函數創建對象 此時如果類中沒有默認的構造函數 則對象無法創建.
在原來的代碼中 已存在 AccountServiceImpl類的構造函數 在原有的默認構造函數上添加一個變量 讓它不再是默認的構造函數 此時點擊運行 報錯信息為:No default constructor found;
驗證默認構造函數創建Bean對象,
這個時候在bean,xml 中 也可以看到配置文件中的報錯 它沒有找到這個類的默認構造函數
beam,xml中也在報錯 - 使用普通工廠中的方法創建對象(使用某個類中的方法創建對象并存入Spring容器
-
/**
* 模擬一個工廠類(該類存在于jar包中 我們無法通過修改源碼的方式來提供默認構造函數),,
*/
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
模擬一個工廠類(該類存在于jar包中 我們無法通過修改源碼的方式來提供默認構造函數)
在實際開發中 有可能遇上別人寫好的類 這中類存在jar包中 屬于字節碼文件 我們無法修改 同時里面可能提供一個方法,比如說 getAccountService 這個方法可以得到一個 accountService ,如果這個類存在與Jar包,如何獲得這個service對象?
在bean.xml中,我們通過instanceFactory這個id 通過反射創建這個類的對象 ,但我們是需要用這個工廠對象嗎?不是要用這個類中方法的返回值的對象嗎?
<bean id="instanceFactory" class="itheima.factory.InstanceFactory" ></bean>
我們要用的是accoutService,只不過accountService不再是通過我們accountServiceImpl來得到的 ,我們要用工廠類的方法 于是,完整版bean,xml的配置是
<bean id="instanceFactory" class="itheima.factory.InstanceFactory" ></bean>
<!--指定工廠beans 和 方法-->
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
- 使用靜態工廠中的靜態方法創建對象(或者某個類中的靜態方法創建對象并存入spring容器)
Copy一份InstanceFactory的代碼,只需要把方法 加一個static的修飾即可.
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
此時在beam.xml中,通過bean 的id反射得到實例對象是staticFactory,而不是service的實例,所以需要配置factory-method 這個屬性來獲得這個方法返回的service實例
<bean id="accountService" class="itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
-
Bean對象的作用范圍
- bean的作用范圍:
bean標簽的scope屬性
作用:指定bean的作用范圍
- bean的作用范圍:
取值:
- singleton:單例的(默認的)(常用)
- prototype:多例的(常用)
- request:作用于web的請求請求范圍
- session:作用于wdb的會話的請求范圍
- globe-session:作用于集群環境的范圍
單例的
<bean id="accountService" class="itheima.factory.StaticFactory" factory-method="getAccountService" scope="singleton"></bean>
多例的
<bean id="accountService" class="itheima.factory.StaticFactory" factory-method="getAccountService" scope="singleton"></bean>
在Client中再實例化一個對象,然后看看單例與多例的區別
public static void main(String[] args) {
Resource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
//通過id獲取Bean對象
IAccountService as = (IAccountService) factory.getBean("accountService");
IAccountService as1 = (IAccountService) factory.getBean("accountService");
System.out.println(as == as1);
//as.saveAccount();
}
}
單例的結果:
AccountServiceImpl創建了
true
多例的結果:
AccountServiceImpl創建了
AccountServiceImpl創建了
false
-
Bean對象的生命周期
單例的
1.創建:容器創建時 對象出生
2.活著:只要容器還在 對象一直活著
3.銷毀:容器銷毀,對象消亡.
在 AccountServiceImpl 中 新增 init() 和destory()方法
public class AccountServiceImpl implements IAccountService {
/**
* 模擬保存賬戶
*/
public AccountServiceImpl() {
System.out.println("AccountServiceImpl創建了");
}
@Override
public void saveAccount() {
System.out.println("service中的saveAccount執行了");
}
public void init(){
System.out.println("對象初始化了");
}
public void destory(){
System.out.println("對象銷毀了");
}
}
在Client中執行
public static void main(String[] args) {
Resource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
//通過id獲取Bean對象
IAccountService as = (IAccountService) factory.getBean("accountService");
//IAccountService as1 = (IAccountService) factory.getBean("accountService");
//System.out.println(as == as1);
as.saveAccount();
}
}
得到結果:
AccountServiceImpl 創建了
AccountServiceImpl 對象初始化了
service中的saveAccount執行了
為什么desrory方法沒有執行 main方法是一切應用程序的入口 當main方法結束后 當前應用中線程占用的內存全部釋放 也包括我們的容器 但是此時還沒有調用銷毀方法 就已經釋放了內存
如果想讓銷毀方法出現 需要手動釋放
(然后我發現我的Client中使用的是BeanFactory這個接口來獲取核心容器的,下面改成為用ApplicationContext的方式)
public static void main(String[] args) {
//獲取核心容器對象
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
//獲取Bean對象
IAccountService accountService = (IAccountService) classPathXmlApplicationContext.getBean("accountService");
accountService.saveAccount();
//手動關閉容器
classPathXmlApplicationContext.close();
}
}
輸出結果
AccountServiceImpl 創建了
AccountServiceImpl 對象初始化了
service中的saveAccount執行了
AccountServiceImpl對象銷毀了

如果 以這樣的方式獲取核心容器對象,是得不到close方法的.
ApplicationContext ApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
因為我們把ClassPathXmlApplicationContext看成了接口類型,如果看成父類對象的時候,只能調用父類方法,(多態性)所以要讓這個對象是自己的對象.
所以用了ClassPathXmlApplicationContext自己的對象
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
- 多例的
1.創建:使用的時候創建
2.活著:在使用過程中就一直活著
3.銷毀:當對象長時間不用且沒有別的對象引用時,由Java的垃圾回收期來執行回收.
spring的依賴注入
依賴注入是指 Dpendency Injection
IOC的作用 是降低程序間的耦合
依賴關系的管理 是指依賴關系以后都交給spring來維護,在當前類所需要用到其他類都由spring為我們提供,我們只需要在配置文件中說明依賴關系的維護,這就稱之為依賴注入-
依賴注入
- 能注入的數據有三類
- 基本類型和String
- 其他bean類型 (在配置文件中或者注解配置過的bean
- 復雜類型/集合類型
- 能注入的數據有三類
-
注入的方式有三種
構造函數注入
由set方法提供
-
由注解提供
- 構造函數注入
使用的標簽是 constructor-arg
標簽出現的位置:bean標簽內部
標簽中的屬性
type :用于要指定所注入的數據的數據類型,該數據類型也是構造函數中某個或某些參數的類型(如果參數1和參數3是同一類型,它不會知道是賦值給哪一個參數,所以不能獨立的實現注入的功能)
index :用于指定要注入的數據給構造函數中指定位置的參數賦值,索引的位置是從0開始(但這個方式要記住參數的位置,所以有點麻煩)
name : 用于指定給構造函數中指定名稱的參數賦值(常用)
==================以上三個用于指定給構造函數中哪個參數賦值===================
value:用于提供基本類型和String類型的數據
ref:用于指定其他的bean類型數據,它指的就是在spring的Ioc核心容器中出現過的bean對象.
- 構造函數注入
優勢:在獲取bean對象時,注入數據是必須的操作,否則對象無法創建成功 弊端:改變了類或者bean對象的實例化方式,使我們在創建對象時如果用不到這些數據也必須提供
老規矩,新建一個工程,spring01_eesy_05DI,copy上一個工程的代碼.先在AccountServiceImpl中創建構造函數
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl() {
System.out.println("AccountServiceImpl 創建了");
}
public AccountServiceImpl(String name,Integer age,Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
System.out.println("AccountServiceImpl 創建了"+name+"=="+age+"==="+birthday);
}
在bean,xml中,為參數賦值
第一個參數
<constructor-arg name="name" value="小強"></constructor-arg>
第二個參數賦值的時候,賦值的12,和Interge類型并不匹配,在xml中 value 的值都是字符串,是spring它把這些數據類型幫我們轉換了.
第三個參數 Date 賦值,如果寫成2019-09-22 是無法為Date類型賦值的,因為它是字符串類型
所以構造函數賦值,只能賦值基本類型和字符串
<constructor-arg name="birthday" value="2019-09-2"></constructor-arg>
那要如何為Date類型賦值呢?可以用引用.
我們可以配置一個日期對象,它會通過class的全限定名反射創建對象并存入spring容器中,通過now id取出
<bean id ="now" class="java.util.Date"></bean>
所以 beam.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="accountService" class="itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="小強"></constructor-arg>
<constructor-arg name="age" value="12"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id ="now" class="java.util.Date"></bean>
</beans>
-
Set方法注入(更常用)
- 涉及的標簽:property
- 出現的位置:bean標簽內部
- 標簽的屬性:
- name:用于指定注入時所調用的set方法名稱,
- value:用于提供基本類型和Sting類型的數據
- ref:用于指定其他bean類型數據.它指的就是在spring的Ioc核心容器中出現過的bean對象.
優勢:創建對象時沒有明確的限制,可以直接使用默認構造函數
弊端:如果有某個成員必須有值,則獲取對象是有可能set方法方法沒有執行.
Copy一個AccountServiceImpl,為成員變量生成set方法.
public class AccountServiceImpl2 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl2() {
System.out.println("AccountServiceImpl2 創建了");
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount() {
System.out.println("service中的saveAccount執行了"+name + age + birthday);
}
}
在Beans.xml中,寫法如下
<bean id="now" class="java.util.Date"></bean>
<bean id="accountService2" class="itheima.service.impl.AccountServiceImpl2">
<property name="name" value="xiaoming"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
</bean>
- name:用于指定注入時所調用的set方法名稱,如果把setName 改成為setUsername的話,beam.xml中的自動聯想也從name變成username,由此可見name屬性用于指定注入時所調用set方法名稱,
執行結果
AccountServiceImpl2 創建了
service中的saveAccount執行了xiaoming18Mon Sep 23 10:35:44 GMT+08:00 2019
- 復雜類型/集合類型的注入
再復制一個accountserviceImpl3,加上數組,list,map,set.properties等數據的成員變量,并生成set方法.
public class AccountServiceImpl3 implements IAccountService {
private String[] mystrs;
private List<String> mylist;
private Map<String, String> mymap;
private Set<String> myset;
private Properties myprops;
public AccountServiceImpl3() {
System.out.println("AccountServiceImpl2 創建了");
}
public void setMystrs(String[] mystrs) {
this.mystrs = mystrs;
}
public void setMylist(List<String> mylist) {
this.mylist = mylist;
}
public void setMymap(Map<String, String> mymap) {
this.mymap = mymap;
}
public void setMyset(Set<String> myset) {
this.myset = myset;
}
public void setMyprops(Properties myprops) {
this.myprops = myprops;
}
public void saveAccount() {
System.out.println("service中的saveAccount執行了");
//數組類型需要tostring方法轉換一下 否則直接打印是內存地址
System.out.println(Arrays.toString(mystrs));
System.out.println(mylist);
System.out.println(mymap);
System.out.println(myset);
System.out.println(myprops);
}
}
在beam.xml中,因為都不是基本類型和String類型,所以<property>標簽里的value賦值也不再有意義
<property name="list" value="xiaoming"></property>
<properties>標簽內,有更多的子標簽提供使用.
array類型,array標簽,value賦值
<property name="mystrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
list類型,list標簽,value賦值
<property name="mylist">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
set類型,set標簽,value賦值
<property name="mylist">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
其中,如果把list,array,set的標簽呼喚,也能正常運行,
map類型,用map標簽,里面有entry標簽,entry有key和value兩個屬性.
<property name="mymap">
<map>
<entry key="AAA" value="111"></entry>
<entry key="BBB" value="222"></entry>
<entry key="CCC" value="333"></entry>
</map>
</property>
\之前這樣的寫法是有問題的,輸出的結果格式會不一樣,然后發現自己寫錯了
<property name="myset">
<set>
<value>
AAA,BBB,CCC
</value>
</set>
</property>
properties類型 ,里面有<props>標簽,props里有<prop>的子標簽,賦值只有一個屬性,是<key>
<property name="myprops">
<props>
<prop key="AAA">aaa</prop>
<prop key="BBB">bbb</prop>
<prop key="CCC">ccc</prop>
</props>
</property>
如果map標簽與prop標簽呼喚,執行結果也不會有問題.
<property name="myprops">
<map>
<entry key="AAA" value="111"></entry>
<entry key="BBB" value="222"></entry>
<entry key="CCC" value="333"></entry>
</map>
</property>
<property name="mymap">
<props>
<prop key="AAA">aaa</prop>
<prop key="BBB">bbb</prop>
<prop key="CCC">ccc</prop>
</props>
</property>