IOC容器 工廠只負責創建對象,而Spring當然不僅僅是一個對象工廠;其核心是一個對象容器,由於具備控制反轉的能力,所以也叫它IOC容器; 容器可以理解為存放對象的地方,當然不僅僅是存儲,還有對象的管理,包括 創建 銷毀 裝配; 這樣原本程式要做的事情交給了Spring,所以這屬於IOC,稱之為I ...
IOC容器
工廠只負責創建對象,而Spring當然不僅僅是一個對象工廠;其核心是一個對象容器,由於具備控制反轉的能力,所以也叫它IOC容器;
容器可以理解為存放對象的地方,當然不僅僅是存儲,還有對象的管理,包括-創建-銷毀-裝配; 這樣原本程式要做的事情交給了Spring,所以這屬於IOC,稱之為IOC容器;
Spring有兩個容器介面ApplicationContext是BeanFactory的子介面。它們都可以作為Spring的容器;
兩種容器的區別:
- BeanFactory採取的懶載入的方式,在獲取對象時才會實例化
- ApplicationContext會在工廠初始化時立即實例化對象
BeanFactory作為頂級介面主要面向於Spring框架本身,僅提供了基礎基本的容器功能如DI
- ApplicationContext,是BeanFactory的子介面,意味著功能比BeanFactory更多,諸如國際化,註解配置,XML配置等等,因此ApplicationContext使用更多
- ApplicationContext的兩個實現類區別:
- ClassPath表示從類路徑中獲取配置文件,
- FileSystem表示從文件系統獲取配置文件
SpringBean的管理
Bean的實例化
1. 使用類構造器(預設無參數)
該方式Bean類中必須存在無參構造器
<bean id="UserService1" class="com.yyh.serviceimpl.UserServiceImpl"/>
2. 使用靜態工廠方法
xml配置:
<bean id="userService" class="com.yyh.serviceimpl.ServiceFactory" factory-method="getService"/>
工廠:
import com.yyh.service.UserService;
public class ServiceFactory {
public static UserService getService() {
System.out.println("factory static run!");
return new UserServiceImpl();
}
}
3. 使用實例工廠方法
xml配置:
<!--工廠Bean-->
<bean id="serviceFactory" class="com.yyh.serviceimpl.ServiceFactory"/>
<!--Service Bean-->
<bean id="userService2" factory-bean="serviceFactory" factory-method="getService2"/>
工廠添加方法:
public UserService getService2() {
System.out.println("factory instance run!");
return new UserServiceImpl();
}
Bean的命名
配置Bean時,可以使用 id 或者 name 屬性給bean命名。 id 和 name 屬性作用上一樣,推薦使用id。
id取值要求嚴格些,必須滿足XML的命名規範。id是唯一的,配置文件中不允許出現兩個id相同的bean。
name取值比較隨意,甚至可以用數字開頭。在配置文件中允許出現多個name相同的bean,在用getBean()返回實例時,最後的一個Bean將被返回。
註意:在spring5中name和id一樣也不允許有重覆的名稱。
如果沒有id,name,則用類的全名作為name
如
<bean class="test.Test">
,可以使用 getBean("test.Test") 返回該實例。如果存在多個id和name都沒有指定,且類都一樣的,如:
<bean class="com.yh.service.UserService"/> <bean class="com.yh.service.UserService"/> <bean class="com.yh.service.UserService"/>
則可以通過getBean(“完整類名#索引”)來獲得,如:
getBean("com.yh.service.UserService#1")
,索引從0開始,若要獲取第一個則可以忽略索引,直接寫類名name中可以使用分號(“;”)、空格(“ ”)或逗號(“,”)來給這個Bean添加多個名稱(相當於別名 alias 的作用)。如:
" name=“a b c d”等同於 name=“a,b,c,d” 這樣寫相當於有 1 2 3 4(4個)個標識符標識當前bean id=“1 2 3 4” 這樣寫相當於有 “1 2 3 4”(1個)個標識符標識當前bean "
而id中的任何字元都被作為一個整體 ;
如果既配置了 id ,也配置了 name ,則兩個都生效。當然也不能重覆;
當註解中出現與xml配置中相同的id或相同name時,優先是用xml中的配置
Bean的作用域
類別 | 說明 |
---|---|
singleton | 預設值; 在Spring IoC容器中僅存在一個Bean實例,Bean以單例方式存在。 |
prototype | 每次從容器中調用Bean時,都返回一個新的實例; |
request | 每次HTTP請求都會創建一個新的Bean,該作用域僅適用於WebApplicationContext環境 |
session | 同一個HTTP Session 共用一個Bean,不同Session使用不同Bean,僅適用於WebApplicationContext 環境 |
application | Bean的作用域為ServletContext ,僅適用於WebApplicationContext環境。 |
作用域就是指作用範圍:單例則表示對象的作用範圍是整個Spring容器,而prototype則表示不管理作用範圍,每次get就直接創建新的
生命周期
init和destroy
Spring提供了非入侵(不強制類繼承或實現)方式的生命周期方法,可以在Bean的初始化以及銷毀時做一些額外的操作
<bean id="service" class="com.yh.service.UserService" scope="singleton"
init-method="init" destroy-method="destroy"/>
<!--
init-method 用於初始化操作
detroy-method 用於銷毀操作
註意:destroy僅在scope為singleton時有效 因為多例情況下
Bean的完整生命周期
執行順序及其含義:
1 構造對象
2 設置屬性
3 瞭解Bean在容器中的name
4 瞭解關聯的beanFactory
5 初始化前處理
6 屬性設置完成
7 自定義初始化方法
8 初始化後處理
9 業務方法
10 Bean銷毀方法
11 自定義銷毀方法
依賴註入
依賴指的是當前對象在運行過程中需要使用到的其他參數,Spring可以幫助我們來完成這個依賴關係的建立,說的簡單點就是把你需要參數的給你,而你不用管參數怎麼來的,已達到儘可能的解耦 ;
舉個例子:
Controller 中需要Service對象,Spring可以把Service自動丟到你的Controller中,你不需要關係Service是怎麼來的,用就完了;
要使用依賴註入,必須現在需要依賴的一方(Controller)中為被依賴的一方(Service)定義屬性,用以接收註入;
構造方法註入
bean:
public class User2 {
private String name;
private int age;
private Phone phone;
public User2(String name, int age, Phone phone) {
this.name = name;
this.age = age;
this.phone = phone;
}
@Override
public String toString() {
return "User2{" +
"name='" + name + '\'' +
", age=" + age +
", phone=" + phone +
'}';
}
}
xml:
<!--依賴註入-->
<bean id="user" class="com.yh.demo2.User2">
<!--按參數名稱註入 -->
<constructor-arg name="name" value="jerry"/>
<!--按參數位置註入 -->
<constructor-arg index="1" value="18"/>
<!--參數類型為其他bean對象時value換成ref -->
<constructor-arg name="phone" ref="phone"/>
<!--type指定類型不常用 -->
<!--<constructor-arg type="java.lang.String" name="name" value="jerry"/>-->
</bean>
<!--user需要的依賴phoneBean-->
<bean id="phone" class="com.yh.demo2.Phone"/>
setter方法註入
依然對上面的User2類的依賴進行註入
<!--setter方法註入(屬性註入) -->
<bean id="user2" class="com.yh.demo2.User2">
<property name="name" value="jerry"/> <!--註入常量值-->
<property name="age" value="20"/>
<property name="phone" ref="phone"/> <!--註入其他Bean-->
</bean>
註意:上述配置要求User2必須存在空參構造器
c命名標簽
上面通過嵌套標簽constructor的方式註入依賴,在需要註入的依賴較多時導致xml顯得很臃腫,C名稱空間來簡化xml中<constructor-arg>
標簽的書寫
使用前需要先在xml頭部進行聲明
xmlns:c="http://www.springframework.org/schema/c"
使用:
<!--c命名空間的使用-->
<bean id="user3" class="com.yh.demo2.User2" c:name="jerry" c:_1="21" c:phone-ref="phone"></bean>
<!--
c:name 指定為name參數賦值
c:_1 指定為構造函數的第2個參數賦值
c:phone-ref 指定為構造函數的第phone參數賦值為id為"phone"的Bean
-->
p命名標簽
同樣的p命名空間則是用於簡化<property>
標簽的書寫
聲明:
xmlns:p="http://www.springframework.org/schema/p"
使用:
<bean id="user4" class="com.yh.demo2.User2" p:name="jerry" p:age="20" p:phone-ref="phone"/>
<!--
p:name 指定為name屬性賦值
p:age 指定為age屬性賦值
p:phone-ref 為phone屬性賦值為id為"phone"的Bean
-->
SpEL註入
SpEL即Spring Expression Language的縮寫,與JSTL一樣是表達式語言,可以支持使用更加複雜的語法註入依賴,包括標準數學運算符,關係運算符,邏輯運算符,條件運算符,集合和正則表達式等;
語法:#{表達式}
用例:
<!--SpEL -->
<bean id="user5" class="com.yh.demo2.User2">
<!--<property name="name" value="#{'jerry'}"/>--> <!--字元常量-->
<!--<property name="age" value="#{100.0}"/>--> <!--數字常量-->
<!--<property name="phone" value="#{phone}"/>--> <!--對象引用-->
<!--<property name="name" value="#{phone.model.concat(' jerry')}"/>--> <!--方法調用-->
<!--<property name="age" value="#{1+100}"/>--> <!--算數符-->
<!--<property name="name" value="#{'11' > '22'}"/>--> <!--比較符-->
<!--<property name="name" value="#{true or false}"/>--> <!--邏輯符-->
<!--<property name="name" value="#{1 > 0?1:0}"/>--> <!--三目-->
</bean>
容器類型的註入
<!-- 容器數據類型註入-->
<bean id="user100" class="com.yh.demo2.User3">
<!--set註入 -->
<property name="set">
<set>
<value>3</value>
<value>3</value>
<value>a</value>
</set>
</property>
<!--list註入 -->
<property name="list">
<list>
<value>3</value>
<value>3</value>
<value>a</value>
</list>
</property>
<!--map註入 -->
<property name="map">
<map>
<entry key="name" value="jerry"/>
<entry key="age" value="18"/>
<entry key="sex" value="man"/>
</map>
</property>
<!--properties註入 -->
<property name="properties">
<props>
<prop key="jdbc.user">root</prop>
<prop key="jdbc.password">admin</prop>
<prop key="jdbc.driver">com.mysql.jdbc.Driver</prop>
</props>
</property>
</bean>
強調:Spring的依賴註入要麼通過構造函數,要麼通過setter,什麼介面註入都tm扯犢子;
介面註入不是一種註入方式,只不過由於OOP的多態,Spring在按照類型註入時,會在容器中查找類型匹配的Bean,如果沒有則查找該類的子類,如果容器中有多個匹配的子類Bean時會拋出異常,坑了一堆人,然後就開始意淫給這個問題取個名字吧.....介面註入.....
註解配置
註解配置Bean
通用註解
@Component 用於在Spring中加入Bean
MVC場景下
@Controller 等價於 @Component 標註控制層
@Service 等價於 @Component 標註業務層
@Repository 等價於 @Component 標註數據訪問層(DAO)
在實現上沒有任何不同,僅僅是為了對Bean進行分層是結構更清晰
使用步驟:
1.需要依賴context和aop兩個jar包
2.添加命名空間
3.指定掃描的註解所在的包
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--指定要掃描註解的包 -->
<context:component-scan base-package="com.yh.demo"/>
</beans>
要註冊的Bean:
import org.springframework.stereotype.Component;
@Component("userService)
public class UserService {
public String hello(String name){
return "hello " + name;
}
}
測試:
public class Tester {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) context.getBean("userService");
System.out.println(service.hello("jerry"));
}
}
若註解中沒有指定id則預設使用簡單類名且小寫開頭,userService
註解註入
@Value用於對基本類型屬性進行註入
@Autowired將容器中的其他Bean註入到屬性中
@Qualifier("BeanID") 指定要註入的Bean的ID
準備UserDAO類:
import org.springframework.stereotype.Repository;
@Repository("userDAO")
public class UserDAO {
public void save(){
System.out.println("user saved!");
}
}
UserService類:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("userService")
public class UserService {
@Value("hello")//基本類型
private String info;
//@Autowired(required = false) //預設為true表示屬性時必須的不能為空
@Autowired //註入類型匹配的Bean
//@Qualifier("userDAO") //明確指定需要的BeanID
private UserDAO userDAO;
//set/get.....
}
測試:
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Tester {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) context.getBean("userService");
System.out.println(service.getInfo());//普通屬性測試
service.getUserDAO().save();//對象屬性測試
}
}
Autowired預設自動註入類型一致的Bean;required屬性用於設置屬性是否是必須的預設為true
Qualifier需要和Autowired搭配使用,用於明確指定要註入的Bean的ID
註意:
當Spring中存在多個類型都匹配的Bean時直接報錯
介面:
public interface PersonDao {
}
兩個實現類:
@Repository()
public class PersonDaoImpl1 implements PersonDao{
}
@Repository()
public class PersonDaoImpl2 implements PersonDao{
}
註入:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("userService")
public class UserService {
@Autowired
private PersonDao personDao;
}
@Resource
Qualifier和Autowired書寫繁瑣,@Resource可將兩個標簽的功能整合,即註入指定ID的Bean
@Resource標準註解的支持是JSR-250中定義的,所以時使用需要導入擴展包,Maven依賴如下:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
Resource預設按照使用屬性名稱作為id查找,查找失敗則使用類型查找
可以利用name屬性指定通過id查找
也可通過type指定類型,當出現相同類型的多個Bean時拋出異常
import javax.annotation.Resource;
@Component("userService")
public class UserService {
//@Resource()//預設按照id/name
//@Resource(name="xx")//指定name
//@Resource(type = PersonDaoImpl1.class) //指定type
@Resource(name="xx",type = PersonDaoImpl1.class)//同時指定name和type
private PersonDao personDao;
}
@Scope
用於標註Bean的作用域
@Repository()
@Scope("prototype") //每次get都創建新的
public class UserDAO {
public void save(){
System.out.println("user saved!");
}
}
因為註解的表達能力有限,很多時候無法滿足使用需求;我們可以將註解和XML配合使用,讓XML負責管理Bean,註解僅負責屬性註入;