Spring筆記5 IoC

https://www.bilibili.com/video/av47952931
p15~34


IoC的概念和作用

上次工廠模式中創建對象有2種方式

// 方式1
IAccountService as = new AccountServiceImpl();
// 方式2
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");

方式1是主動的;方式2是被動的(根據配置創建),控制權轉移給了工廠

IoC (Inversion of Control 控制反轉)把創建對象的權利交給框架
其作用是降低程序的耦合
但只能是降低,而不能完全消除。如果兩個類之間任何關係都沒有,那有一個類一定是多餘的。
自己寫時用工廠模式實現,Spring中的Ioc如何實現?


Spring中的IoC

控制反轉 Inversion of Control, IoC

解決的問題:降低程序耦合(減少依賴關係)
解決的方法:使用配置的方式

1.導入依賴

導入Spring依賴

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>5.0.2.RELEASE</version>
	</dependency>
</dependencies>

在查看引入的依賴

包含了常用的核心組件

Core Container簡單的說就是一個Map,封裝了要用的對象

2.創建配置文件&導入約束&配置bean

然後創建bean.xml(沒有固定要求,習慣這樣命名,因爲是管beans的)
在spring-framework-5.0.2.RELEASE-docs/spring-framework-reference文件夾中找到index.html

點core,搜索xmlns(導入xml schema約束的關鍵字)

把它複製粘貼到配置文件中

<?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">
</beans>

配置bean,把對象的創建交給Spring來管理
同工廠模式,需要唯一標誌和對象的全限定類名(包名+類名)

	<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
	<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>

剩下的解析配置文件的事就由Spring做了

3.取出容器&獲取對象

// Client.java

public class Client {
	public static void main(String[] args) {
        // 1.獲取核心容器對象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 2.根據id獲取Bean對象
        // 方法一:拿到一個Object類,自己強轉
        IAccountService as  = (IAccountService)ac.getBean("accountService");
        // 方法二:給一個類型,直接得到該類型對象
        IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
    }
}

tip: ApplicationContext的繼承關係

Idea中,在類上右鍵->Diagrams->Show Diagram
在接口上右鍵->Show Implementations
在這裏插入圖片描述
可以看到,ApplicationContext繼承自BeanFactory,ClassPathXmlApplicationContext是它的實現類

ApplicationContext的三個常用實現類:
* ClassPathXmlApplicationContext:它可以加載類路徑下的配置文件(配置文件必須在類路徑下,不在的加載不了)(更常用)
* FileSystemXmlApplicationContext:它可以加載磁盤任意路徑下的配置文件(必須有訪問權限)
* AnnotationConfigApplicationContext:它是用於讀取註解創建容器的(後面講)

tip: ApplicationContext和BeanFactory的區別

ApplicationContext

  • 構建核心容器時,創建對象採取的策略是採用立即加載的方式。即,一讀取完配置文件馬上就創建配置文件中配置的對象
  • 單例對象適用

BeanFactory

  • 構建核心容器時,創建對象採取的策略是採用延遲加載的方式。即,什麼時候根據id獲取對象了,什麼時候才真正的創建對象
  • 多例對象使用

bean的細節

創建bean對象的三種方式

方式一:使用默認構造函數創建

在spring的配置文件中使用bean標籤,配以id和class屬性之後,且沒有其他屬性和標籤時。採用的就是默認構造函數創建bean對象,此時如果類中沒有默認構造函數,則對象無法創建。

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>

方式二:使用普通工廠中的方法創建對象

實際開發中,可能要用到別人寫好的jar包中的類。(不知道有沒有默認構造函數,也無法通過修改源碼來提供默認構造函數)。例如:

// 模擬一個工廠類(該類可能是存在於jar包中的,我們無法通過修改源碼的方式來提供默認構造函數)
public class InstanceFactory {
    public IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}

有這麼一個InstanceFactory類,需要拿到AccountServiceImpl
如果像方法一那樣創建對象,即

<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>

得到的並不是AccountServiceImpl對象,而是InstanceFactory對象

此時應該修改創建對象的方式
使用普通工廠中的方法創建對象(使用某個類中的方法創建對象,並存入spring容器)

<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>

先創建工廠,再通過工廠的方法創建所需對象

方式三:使用工廠中的靜態方法創建對象

模擬一個工廠類,它有一個返回對象的靜態方法

// 模擬一個工廠類(該類可能是存在於jar包中的,我們無法通過修改源碼的方式來提供默認構造函數)
public class StaticFactory {
    public static IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}

使用工廠中的靜態方法創建對象(使用某個類中的靜態方法創建對象,並存入spring容器)

<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>

這裏的factory-method是一個靜態方法

Q:這兩個工廠中不都new對象了嗎?
A:把這兩個對象看作jar包中的類(此處只是模擬它),jar包中不是.java文件而是.class,都是無法修改的。實際開發中,有些對象就得用方法二或方法三來創建

指定bean對象的作用範圍:bean標籤的scope屬性

取值:
singleton:單例的(默認值)(常用)
prototype:多例的(常用)
request:作用於web應用的請求範圍
session:作用於web應用的會話範圍
global-session:作用於集羣環境的會話範圍(全局會話範圍),當不是集羣環境時,它就是session

例子:

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>

global-session的含義

bean對象的生命週期

單例對象

  • 出生:當容器創建時對象出生
  • 活着:只要容器還在,對象一直活着
  • 死亡:容器銷燬,對象消亡

總結:單例對象的生命週期和容器相同

多例對象

  • 出生:當我們使用對象時spring框架爲我們創建
  • 活着:對象只要是在使用過程中就一直活着。
  • 死亡:當對象長時間不用,且沒有別的對象引用時,由Java的垃圾回收器回收

可以通過bean標籤指定在特定生命週期執行的方法

例子:

// 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">
	<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
          scope="singleton" init-method="init" destroy-method="destroy"></bean>
</beans>
// Client.java

public class Client {

    public static void main(String[] args) {
        // 1.獲取核心容器對象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");  // 執行完這句,單例對象就創建了
        // 2.根據id獲取Bean對象
        IAccountService as  = (IAccountService)ac.getBean("accountService");  // 執行完這句,多例對象才創建
        as.saveAccount();
    }
    
}
// AccountServiceImpl.java

public class AccountServiceImpl implements IAccountService {

    public AccountServiceImpl(){
        System.out.println("對象創建了...");
    }

    public void  saveAccount(){
        System.out.println("service中的saveAccount方法執行了...");
    }

    public void  init(){
        System.out.println("對象初始化了...");
    }
    public void  destroy(){
        System.out.println("對象銷燬了...");
    }

}

執行輸出

對象創建了…
對象初始化了…
service中的saveAccount方法執行了…

Q:爲什麼沒有執行銷燬方法?
A:main()結束之後,當前進程佔用的內存全部釋放(包括容器),此時並沒有調用銷燬方法就已經把內存釋放了。這裏要想調用銷燬方法,可以手動關閉容器

在main()中手動關閉容器

爲什麼沒有呢?
這裏ApplicationContext是個接口,調不到子類的方法

// 1.獲取核心容器對象
// ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2.根據id獲取Bean對象
IAccountService as  = (IAccountService)ac.getBean("accountService");
as.saveAccount();
// 3.手動關閉容器
ac.close();

這樣就可以了

對象創建了…
對象初始化了…
service中的saveAccount方法執行了…
對象銷燬了…

但是,此時如果把對象改爲多例的,其它都不變,不會執行銷燬方法


Spring中的依賴注入(DI)

依賴注入 Dependency Injection, DI

能注入的三類數據

  1. 基本類型和String
  2. 其他bean類型(在配置文件中或者註解配置過的bean)
  3. 複雜類型/集合類型

注入的三種方式

  1. 使用構造函數提供
  2. 使用set方法提供
  3. 使用註解提供

構造函數注入

使用的標籤:constructor-arg
標籤出現的位置:bean標籤的內部
標籤中的屬性(指定參數)

  • type:指定數據類型
  • index:指定構造函數中的索引位置
  • name:指定名稱(常用)

標籤中的屬性(提供賦值)

  • value:用於提供基本類型和String類型的數據
  • ref:用於指定其他的bean類型數據(在spring的Ioc核心容器中出現過的bean對象)

優勢
在獲取bean對象時,注入數據是必須的操作,否則對象無法創建成功(必須某些數據時,不會被忽略)
弊端
改變了bean對象的實例化方式,創建對象時,即使以後用不到這些數據,也必須提供

例:

AccountServiceImpl類有3個變量,沒有無參構造函數

public class AccountServiceImpl implements IAccountService {

    // 如果是經常變化的數據,並不適用於注入的方式
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String name,Integer age,Date birthday){
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

}

bean.xml對應的bean配置

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
	<constructor-arg name="name" value="test"></constructor-arg>
	<constructor-arg name="age" value="18"></constructor-arg>
	<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>

<!-- 配置一個日期對象 -->
<bean id="now" class="java.util.Date"></bean>

配置中的value都是字符串,對於String、Integer類型,Spring可以自動轉換
但Date無法直接轉換,需另外配置

set方法注入

比構造函數常用

使用的標籤:property
出現的位置:bean標籤的內部
標籤的屬性

  • name:用於指定注入時所調用的set方法名稱(不管變量名)
  • value:用於提供基本類型和String類型的數據
  • ref:用於指定其他的bean類型數據

優勢
創建對象時沒有明確的限制,可以直接使用默認構造函數
弊端
如果有某個成員必須有值,但沒有注入

例:

public class AccountServiceImpl2 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;}

}
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
	<property name="name" value="TEST" ></property>
	<property name="age" value="21"></property>
	<property name="birthday" ref="now"></property>
</bean>

複雜類型注入

用於給List結構集合注入的標籤:
list array set

用於個Map結構集合注入的標籤:
map props

! 結構相同,標籤可以互換

例:

public class AccountServiceImpl3 implements IAccountService {

	// list結構
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    // map結構
    private Map<String,String> myMap;
    private Properties myProps;

    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 setMyProps(Properties myProps) { this.myProps = myProps; }

}
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
	<!--用array、list、set都行-->
	<property name="myStrs">
		<array>
			<value>AAA</value>
			<value>BBB</value>
			<value>CCC</value>
		</array>
	</property>

	<!--用map、prop都行-->
	<property name="myMap">
		<map>
			<entry key="testA" value="aaa"></entry>
			<entry key="testB">
				<value>BBB</value>
	        </entry>
		</map>
	</property>
</bean>

基於註解的IoC配置

註解配置和xml配置要實現的功能都是一樣的:降低程序耦合

曾經XML的配置:

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
	scope=""  init-method="" destroy-method="">
	<property name=""  value="" | ref=""></property>
</bean>

使用註解時,不用上面的,但需要告訴Spring開啓註解掃描

<?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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--告知spring在創建容器時要掃描的包,配置所需要的標籤不是在beans的約束中,而是一個名稱爲
    context名稱空間和約束中-->
    <context:component-scan base-package="com.itheima"></context:component-scan>
</beans>

其中context名稱空間和前面一樣,在文檔中搜索找

用於創建對象的註解

@Component
作用:
用於把當前類對象存入spring容器中

屬性:
value:用於指定bean的id(不寫時,默認值是當前類名,且首字母小寫)

例:

@Component(value = "accountService")
public class AccountServiceImpl implements IAccountService {
	//...
}

細節:
當只給一個value屬性賦值時,value可以不寫

@Component("accountService")
public class AccountServiceImpl implements IAccountService {
	//...
}

@Controller:一般用在表現層
@Service:一般用在業務層
@Repository:一般用在持久層

以上三個註解的作用和屬性與@Component一模一樣
是Spring框架提供的明確的三層使用的註解,使三層對象更加清晰

用於注入數據的註解

@Autowired
作用:
自動按照類型注入。只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配,就可以注入成功。如果ioc容器中沒有任何bean的類型和要注入的變量類型匹配,則報錯
(當有多個bean類型匹配時,用變量名和bean的id匹配,如果恰好有一個匹配,也可以成功,否則報錯)

出現位置:
可以是變量上,也可以是方法上

例:

@Autowired
private IAccountDao accountDao = null;

細節:
在使用註解注入時,set方法就不是必須的了

@Qualifier
作用:
在按照類中注入的基礎之上再按照名稱注入。在給類成員注入時不能單獨使用,必須和@Autowired一起。但是在給方法參數注入時可以單獨使用

屬性:
value:用於指定注入bean的id

例:

@Autowired
@Qualifier("accountDao1")
private IAccountDao accountDao = null;

@Resource
作用:
直接按照bean的id注入,可以獨立使用

屬性:
name:用於指定bean的id
(注意:這裏是name,不是value)

例:

 @Resource(name = "accountDao1")
 private IAccountDao accountDao = null;

以上三個註解都只能注入其他bean類型的數據,而基本類型和String類型無法注入
另外,集合類型的注入只能通過XML來實現

@Value
作用:
用於注入基本類型和String類型的數據

屬性:
value:用於指定數據的值
可以使用Spring中SpEL(即Spring的el表達式)寫法:${表達式}

用於改變作用範圍的註解

@Scope
作用:
用於指定bean的作用範圍

屬性:
value:指定範圍的取值(常用:singleton prototype)

例:

@Service("accountService")
@Scope("prototype")
public class AccountServiceImpl implements IAccountService {
	//...
}

生命週期相關的註解

@PreDestroy
作用:用於指定銷燬方法

@PostConstruct
作用:用於指定初始化方法

和bean標籤中使用init-method和destroy-methode一樣

例:

@Service("accountService")
@Scope("singleton")
public class AccountServiceImpl implements IAccountService {

    @Resource(name = "accountDao1")
    private IAccountDao accountDao = null;

    @PostConstruct
    public void  init(){
        System.out.println("初始化方法執行了");
    }

    @PreDestroy
    public void  destroy(){
        System.out.println("銷燬方法執行了");
    }

    public void  saveAccount(){
        accountDao.saveAccount();
    }
}

同樣,如果類是多例的,可能不會看到銷燬方法執行的輸出,因爲它的銷燬是JVM管的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章