上一篇闡述了Spring相關的一些概念,入門內容Spring是什麼?能做什麼?爲什麼要學?優點與是什麼?來入門吧
這一篇是關於Spring Bean管理的
IOC容器
工廠只負責創建對象,而Spring當然不僅僅是一個對象工廠,其核心是一個對象容器,其具備控制反轉的能力,所以也稱爲IOC容器。
幫助我們存放對象,並且管理對象,包括:創建、銷燬、裝配,這樣就將原本由程序自己完成的工作,交給了框架來完成,稱爲IOC容器。
學習的核心也就在於如何將對象放在Spring中,以及如何從Spring中取出來。
Spring有兩個容器接口:
- ApplicationContext
- BeanFactory
ApplicationContext是BeanFactory的子接口,它們都可以作爲Spring的容器。
區別:
- BeanFactory採取懶加載方式(會有延遲),在獲取對象時纔會實例化
- ApplicationContext在工廠初始化時立即實例化對象
- BeanFactory作爲頂級接口主要面向於Spring框架本身,僅提供了基本的容器功能,如DI
- ApplicationContext時BeanFactory的子接口,意味着功能比BeanFactory更多,諸如國際化(根據用戶的地理位置對產品的表現形式進行一些變化,例如文字、樣式),註解配置,XML配置等等,因此ApplicationContext使用更多
- ApplicationContext的個實現類的區別:
- ClassPath表示從類路徑獲取配置文件
- FileSystem表示從文件系統獲取配置文件
Spring Bean管理
Bean的實例化(初始化,相當於我們之前的new)
1.創建一個maven項目
2.配置maven
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- Maven會自動下載所有Spring核心容器和aop的依賴-->
</dependencies>
3.在resource下創建一個名爲applicationContext.xml的配置文件
自動生成:在這裏配置Bean
<?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>
4.創建contoller、service對應的包
準備工作已完成,下面書寫Bean實例化的方式
方式一 使用無參構造器
使用該方式時,Bean類中必須要有無參構造
<!--使用無參構造實例化Bean-->
<bean id="userService" class="cx.service.UserServiceImpl" />
測試
public class UserController {
private UserService service;
public UserController() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//通過指定文件路徑加載Spring配置文件,一般很少用
//FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("D:\\ideaProject\\SpringDemo2\\src\\main\\resources\\applicationContext.xml");
//根據id或name獲取
service = (UserService) context.getBean("userService");
//通過接口類來獲取
//service = context.getBean(UserService.class);
//通過具體類來獲取
//service = context.getBean(UserServiceImpl.class);
}
public void say(){
service.say();
}
public static void main(String[] args) {
new UserController().say();
}
}
方式二 使用靜態工廠方式
創建一個工廠類
public class factory {
public static UserService getService(){
System.out.println("執行了靜態工廠方法");
return new UserServiceImpl();
}
}
xml配置
<!--使用靜態工廠方式-->
<bean id="userService2" class="cx.service.factory" factory-method="getService"/>
方式三 使用實例工廠方式
xml文件
<!--通過實例工廠方式 工廠Bean-->
<bean id="factory" class="cx.service.factory" />
<!--配置userService 調用factory 指定getService2 指定使用的工廠對象時哪一個-->
<bean id="userService3" factory-bean="factory" factory-method="getService2" />
調用:
service = (UserService) context.getBean("userService3");
首先需配置工廠類,然後通過factory-bean調用剛剛命名的那個Bean,寫入id,用factory-method指定要使用的工廠的方法。
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="com.cx.service.UserService"/>
可以使用 getBean(“com.cx.service.UserService”)返回該實例。 -
如果存在多個id和name都沒有指定,且類都一樣的,如:
則可以通過getBean(“完整類名#索引”)來獲得,如:getBean(“com.cx.service.UserService#1”),索引從0開始,若要獲取第一個則可以忽略索引,直接寫類名。
-
name中可以使用分號(“;”)、空格(“ ”)或逗號(“,”)來給這個Bean添加多個名稱(相當於別名 alias 的作用)。如:name=“a b c d”等同於 name=“a,b,c,d” 這樣寫相當於有 1 2 3 4(4個)個標識符標識當前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就直接創建新的。
-
request、session和 application三種作用域僅在基於web的應用中使用
生命週期
init和destory
Spring提供了非入侵(不強制類繼承或實現)方式的生命週期方法,可以在Bean的初始化以及銷燬時做一些額外的操作
<!--init-method 用於初始化操作的方法
destroy-method 用於銷燬操作的方法
這兩個都是指定方法名-->
<bean id="userService" class="cx.service.UserServiceImpl" scope="singleton" init-method="init" destroy-method="destroy"/>
注意:
destroy僅僅在scope爲singleton時有效
Bean的完整生命週期
1 構造對象
2 設置屬性
3 瞭解Bean在容器中的name
4 瞭解關聯的beanFactory
5 初始化前處理
6 屬性設置完成
7 自定義初始化方法
8 初始化後處理
9 業務方法
10 Bean銷燬方法
11 自定義銷燬方法
依賴注入
依賴指的是檔期啊對象在運行過程中需要使用到的其他參數或者對象;
Spring可以幫助我們完成這個依賴關係的建立;
說的簡單點即把你需要的參數給你,而你不管參數是怎麼來的,只管用即可,以達到儘可能地解耦。
for example
controller中需要service對象,Spring可以把service自動丟到controller中,你不需要關注service對象是怎麼來的,用就可以了
要使用依賴注入,必須先在需要依賴的一方(controller)中爲被依賴的一方(service)定義屬性,用於接收注入;
構造方法注入
bean:
書寫user
public class User {
private Integer id;
private String name;
private String sex;
private PC pc;
}
還有有參/無參構造,getter/setter方法,以及toString
user中需要的pc類
public class PC {
private String model;
}
在applicationContext.xml中注入,構造方法注入
<!--依賴注入構造方法注入-->
<bean id="user" class="cx.pojo.User">
<!--按參數名稱注入-->
<constructor-arg name="id" value="1"/>
<!--按參數位置注入-->
<constructor-arg index="1" value="張三"/>
<constructor-arg index="2" value="男" />
<!--參數類型爲其他bean對象時,value換爲ref-->
<constructor-arg index="3" ref="pc"/>
</bean>
<bean id="pc" class="cx.pojo.PC">
<constructor-arg index="0" value="小米"/>
</bean>
測試代碼:
public void test1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
public static void main(String[] args) {
new TestController().test1();
}
setter方法注入
在上述中,還可以使用下述注入
<!--使用setter方法注入(屬性注入)-->
<bean id="user2" class="cx.pojo.User">
<property name="id" value="2"/>
<property name="name" value="李四"/>
<property name="sex" value="男"/>
<property name="pc" ref="pc" />
</bean>
注意:setter方法注入配置要求User中必須有無參構造方法
C和P命名標籤
上面的兩種依賴注入方法,如果在需要注入的依賴較多時導致xml顯得很臃腫,C名稱空間來簡化xml中標籤的書寫。
需要先在xml中進行聲明
xmlns:c="http://www.springframework.org/schema/c"
使用:
<!--c命名空間的使用-->
<!--c:id 指定爲id參數賦值
c:_1 指定爲構造函數的第2個參數賦值
c:p-ref 指定爲構造函數的pc參數賦值爲id爲"pc"的Bean -->
<bean id="user3" class="cx.pojo.User" c:id="3" c:_1="王五" c:_2="男" c:pc-ref="pc" />
同理p命名標籤也需要在xml中進行聲明
xmlns:p="http://www.springframework.org/schema/p"
```![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200518103738464.png)
使用:
```java
<!--p:id 指定爲id屬性賦值
p:name name屬性賦值
p:sex sex屬性賦值
p:pc-ref 爲pc屬性賦值爲id爲"pc"的Bean-->
<bean id="user4" class="cx.pojo.User" p:id="4" p:name="趙六" p:sex="女" p:pc-ref="pc"/>
SpEL注入
SpEL即Spring Expression Language的縮寫,與JSTL一樣是表達式語言,可以支持使用更加複雜的語法注入依賴,包括標準數學運算符,關係運算符,邏輯運算符,條件運算符,集合和正則表達式等;
語法: #{表達式}
用例:
<!--SpEL -->
<bean id="user" class="com.cx.pojo.User">
<!--<property name="name" value="#{'cx'}"/>--> <!--字符常量-->
<!--<property name="age" value="#{100.0}"/>--> <!--數字常量-->
<!--<property name="phone" value="#{phone}"/>--> <!--對象引用-->
<!--<property name="name" value="#{phone.model.concat(' cx')}"/>--> <!--方法調用-->
<!--<property name="age" value="#{1+100}"/>--> <!--算數符-->
<!--<property name="name" value="#{1>100}"/>--> <!--比較符-->
<!--<property name="name" value="#{true or false}"/>--> <!--邏輯符-->
<!--<property name="name" value="#{1 > 0?1:0}"/>--> <!--三目-->
</bean>
容器類型的注入
xml:
<bean id="userdemo" class="com.lbb.pojo.UserDemo">
<!--注入set-->
<property name="set">
<set>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
</set>
</property>
<!--注入list-->
<property name="list">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
</list>
</property>
<!--注入map-->
<property name="map">
<map>
<entry key="1" value="1"/>
<entry key="2" value="2"/>
<entry key="3" value="3"/>
<entry key="4" value="4"/>
</map>
</property>
<!--注入properties-->
<property name="properties">
<props>
<prop key="1">1</prop>
<prop key="2">2</prop>
<prop key="3">3</prop>
<prop key="4">4</prop>
</props>
</property>
</bean>
上述寫法同樣適用於構造函數注入:
<bean class="service.Person">
<constructor-arg name="name" value="jerry"/>
<constructor-arg name="properties">
<props>
<prop key="pwd">123</prop>
</props>
</constructor-arg>
</bean>
強調:java Spring的依賴注入要麼通過構造函數,要麼通過setter
接口注入不是一種注入方式,只不過由於OOP的多態,Spring在按照類型注入時,會在容器中查找類型匹配的Bean,如果沒有則查找該類的子類,如果容器中有多個匹配的子類Bean時會拋出異常。
註解配置
註解配置Bean
通用註解
@Component 用於在Spring中加入Bean
MVC場景下
-
@Controller 等價於 @Component 標註控制層
-
@Service 等價於 @Component 標註業務層
-
@Repository 等價於 @Component 標註數據訪問層(DAO)
在實現上暫時沒有任何不同,在底層源碼裏就是給Component取了別名,僅僅是爲了對Bean進行分層是結構更清晰
使用步驟:
1.需要依賴context和aop兩個jar包
2.配置applicationContext.xml
添加命名空間(idea下 輸入<context:可自動補全,指定掃描的註解所在的包:
<?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
https://www.springframework.org/schema/context/spring-context.xsd">
<!--指定要掃描註解的包 會自動掃描子包 -->
<context:component-scan base-package="com.cx.pojo"/>
</beans>
要註冊的Bean
@Component
public class User {
private Integer id;
private String name;
private String sex;
private PC pc;
}
測試代碼
public void test1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
如果註解中沒有指定id,則默認使用簡單類名且小寫開頭,例如:上述的user。
註解注入
- @value用於對基本類型的注入
- @Autowired將容器中的其他Bean注入到屬性中,自動裝配
- @Qualifier(“BeanId”)指定要注入的Bean的Id,需要和Autowired一起使用
- @Resource 相當於: @Autowired 和@Qualifier 一起使用.
@value
@Value("12")
private int age;
@Value("tom")
private String name;
@Value("#{1+2+3}")
private String pwd;
@Value("${jdbc.user}") //注入屬性配置
private String user;
上述注入屬性配置使用${}取配置文件裏面的值:
首先需要加載配置文件,在applicationContext.xml加入:
<context:property-placeholder location="jdbc.properties"/>
@Autowired
自動注入一個Bean,默認按照類型來注入
required屬性用於設置屬性是否是必須的默認爲true,如果爲true找不到會報錯,如果false則會爲空,不會報錯
使用:
User 類:
@Component
public class User {
@Value("1")
private Integer id;
@Value("cc")
private String name;
@Value("男")
private String sex;
@Value("小米")
private PC pc;
}
dao層:
@Repository
public class PersonDao {
public void smile(){
System.out.println("ta笑了");
}
}
service實現層:
@Service
public class PerSonServiceImpl implements PersonService {
@Autowired
private PersonDao personDao;
public void smile(){
personDao.smile();
}
}
接口層
public interface PersonService {
public void smile();
}
controller層
@Controller
public class PersonController {
@Autowired
private PersonService personService;
public void smile() {
personService.smile();
}
}
測試:
@Test
public void test1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
PersonController bean = context.getBean(PersonController.class);
bean.smile();
}
@Qualifier
當我們需要指定注入的bean的id時,Autowired不能配置,這時候要和Qualifier一起使用,用於明確指定要注入的Bean的ID(注意一定要一起使用)
使用:
上面PersonService可以這樣寫:
@Service
public class PerSonServiceImpl implements PersonService {
@Autowired
@Qualifier("personDaoImpl")
private PersonDao personDao;
public void smile(){
personDao.smile();
}
}
注意:
若通過類型注入,則當Spring中存在多個類型都匹配的Bean時直接報錯,演示如下:
接口:
public interface PersonDao {
}
兩個實現類:
@Repository()
public class PersonDaoImpl1 implements PersonDao{
}
@Repository()
public class PersonDaoImpl2 implements PersonDao{
}
注入:
@Component
public class UserService {
@Autowired
private PersonDao personDao;
}
這時候就會報錯,如果要這樣用必須指定Bean的name,通過name來區分。
@Resource
Qualifier和Autowired書寫繁瑣,@Resource可將兩個標籤的功能整合,即注入指定ID的Bean:
@Resource標準註解是java註解,但是在jdk中沒有導入,要配置導入,在pom.xml配置:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
註解:
Resource默認按照使用屬性名稱作爲name查找,查找失敗則使用類型查找
可以利用name屬性指定要查找的id
也可通過type指定類型,當出現相同類型的多個Bean時拋出異常
@Service
public class PerSonServiceImpl implements PersonService {
@Resource(name = "personDaoImpl")
private PersonDao personDao;
public void smile(){
personDao.smile();
}
}
Resource可以設置name,也可以設置type.如果不設置的話,會先按照類型找,找不到會再按照屬性名來尋找。下面兩個是一樣的效果
@Resource
private PersonDao personDaoImpl;
@Resource(name = "personDaoImpl")
private PersonDao personDao;
Scope
用於標註Bean的作用域
@Repository()
@Scope("prototype") //每次get都創建新的
public class PersonDao {
public void smile(){
System.out.println("笑了");
}
}
XML與註解配合使用
因爲註解的表達能力有限,很多時候無法滿足使用需求;我們可以將註解和XML配合使用,讓XML負責管理Bean,註解僅負責依賴注入;
與註解掃描的不同之處,註解掃描用於將Bean放入容器中同時進行依賴注入,而後者僅僅是在已存在的Bean中進行依賴注入;
配置applicationContext.xml文件,開啓註解:
<!--使用xml默認是不掃描註解的,所以要配置下面開啓註解-->
<context:annotation-config/>
多配置文件的使用:
當配置文件中的內容過多是不便於維護,Spring支持多配置文件
方法1:
在初始化容器時指定要加載的所有配置文件
@Test
public void test3(){
//讀取配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml","applicationContext2.xml");
//獲取bean
PersonController personController= (PersonController) context.getBean("personController");
personController.smile();
}
方法2:
在配置文件中使用import來引入其他的配置文件
<import resource="applicationContext2.xml"/>
import 裏的其他寫法:
前綴 含義
classpath: 從classpath加載第一個匹配的文件
classpath*: 從classpath加載所有匹配的文件(用於成功分爲多個jar包時)
file: 按絕對路徑加載文件
http: 按URL路徑加載文件
最後來一波~~~~~~
Spring的Bean管理的方式比較
基於XML配置
- Bean的實例化:
<bean id="" class="" />
- Bean的命名:通過id或name指定
- Bean的注入:構造方法或setter方法注入(p或c命名標籤)
- 生命週期,Bean的作用範圍:init-method,destroy-method,圍scope屬性,destroy僅僅在scope爲singleton時有效
- 使用場景:Bean來自於第三方
基於註解配置 - Bean的實例化:@component、@Controller、@Service、@Repository
- Bean的命名:@component(“name")
- Bean的注入:@AutoWired按類型注入、@Qualifier按名稱注入
- 生命週期,Bean的作用範圍:@PostConstruct初始化、@PreDestroy銷燬、@Scope設置作用範圍
- 使用場景:Bean的實現由自己開發