什麼是Spring框架
Spring是一種輕量級開發框架,旨在簡化開發以及系統的可維護性。
Spring框架的優點:1、非侵入式設計,可以使應用程序代碼對框架的依賴最小化。2、方便解耦、簡化開發,將所有對象的創建和依賴關係的維護工作都交給Spring容器的管理。3、支持AOP,提高了程序的複用性。4、支持聲明式事務處理,只需要通過配置就可以完成對事物的管理。5、方便程序的測試,Spring提供了對Junit4的支持,可以通過註解方便的測試Spring程序。6、方便集成各種優秀框架。
Spring是很多模塊的集合,包括核心容器、數據訪問/集成、Web、AOP(面向切面編程)、工具、消息和測試模塊。
核心容器
Spring的核心容器是其他模塊建立的基礎,有spring-core、spring-beans、spring-context等模塊組成。
spring-core 模塊:提供了框架的基本組成部分,包括控制反轉(Inversion of Control,IOC)和依賴注入(Dependency Injection,DI)功能。
spring-beans 模塊:主要用來管理bean,這個模塊實現BeanFactory的工廠模式,Spring中Bean形式是普通Java類.
Spring Context 模塊:此模塊表示Spring應用的環境,通過此模塊可訪問任意Bean,ApplicationContext
接口是模塊的關鍵組成.
Spring表達式語言(SpEL):這個模塊提供對表達式語言(SpEL)支持
AOP
spring-aop 模塊:是 Spring 的另一個核心模塊,提供對面向切面編程的支持。
Aspects 模塊: 提供與AspectJ集成,AspectJ是另一個面向切面編程的框架。
數據訪問與集成
spring-jdbc 模塊:對Java JDBC接口再次包裝,讓Spring應用中使用JDBC更簡單。
spring-orm 模塊:ORM代表對象關係映射,該模塊提供對ORM的支持。Hibernate
spring-oxm 模塊:OXM代表對象XML映射器,將 java 對象映射成 XML 數據, 或者將 XML 數據映射成 java 對象,該模塊提供對OXM的支持
spring-jms模塊(Java Messaging Service):指Java消息傳遞服務,包含用於生產和使用消息的功能。
Transations 模塊:事務模塊,該模塊提供數據庫事務的支持。
Web
spring-web 模塊:提供了基本的Web開發集成功能,如文件下載、rest接口支持等。
servlet 模塊:提供MVC(Model-View-Controller)功能實現
WebSocket 模塊:提供Socket通信,web端的的推送功能;
portlet 模塊:實現web模塊功能的聚合,(如網站首頁(Port)下面可能會有不同的子窗口(Portlet))。
Test
spring-test 模塊:主要爲測試提供支持的,支持使用JUnit或TestNG對Spring組件進行單元測試和集成測試。
Spring核心思想:IOC/DI、AOP
1、IOC/DI是什麼
傳統Java SE程序設計,我們直接在對象內部通過new進行創建對象,是程序主動去創建對象和設置依賴關係。問題:導致類與類之間高耦合,難於測試。而IOC理念,將對象的創建和設置依賴關係交由Spring容器來進行管理,開發人員只需要關注具體實現就可以了。
Spring框架中控制反轉(Inversion of Control / IoC)與依賴注入(Dependency Injection / DI)實際上講的是同一個事情,只是角度不同。
DI依賴注入:依賴類不由程序員實例化,而是通過spring容器幫我們創建指定實例並且將實例動態的注入到需要該對象的類中。
2、Spring IOC 原理
Spring 通過一個配置文件描述 Bean 及 Bean 之間的依賴關係,利用 Java 語言的反射功能實例化Bean 並建立 Bean 之間的依賴關係。Java 1.3之後一個重要特徵是反射(reflection),它允許程序在運行的時候動態的生成對象、執行對象的方法、改變對象的屬性,spring就是通過反射來實現注入的。
Spring 啓動時讀取應用程序提供的 Bean 配置信息,並在 Spring 容器中生成一份相應的 Bean 配置註冊表,然後根據這張註冊表實例化 Bean,裝配好 Bean 之間的依賴關係,爲上層應用提供準備就緒的運行環境。其中 Bean 緩存池爲 HashMap 實現。
3、Spring 框架中的依賴注入方式
(1)Spring框架依賴注入可以通過XML配置文件和註解的方式進行。
①XML文件注入
示例Demo:創建一個玩家類,該玩家可以使用武器有刀劍和槍。
傳統寫法:玩家的武器只能是劍Sword
,而不能把Sword
替換成槍Gun
。想替換涉及代碼修改。
class Player{
Weapon weapon;
Player(){
// 與 Sword類緊密耦合
this.weapon = new Sword();
}
public void attack() {
weapon.attack();
}
}
依賴注入:將對象的創建和依賴關係的設置交由Spring容器進行管理。消除類之間依賴關係。A類要依賴B類,A類不再直接創建B類,而是把這種依賴關係配置在外部xml文件(或java config文件)中,然後由Spring容器根據配置信息創建、管理bean類。
class Player{
Weapon weapon;
// weapon 被注入進來
Player(Weapon weapon){
this.weapon = weapon;
}
public void attack() {
weapon.attack();
}
public void setWeapon(Weapon weapon){
this.weapon = weapon;
}
}
Weapon
類的實例並不在代碼中創建,而是外部通過構造函數傳入,傳入類型是父類Weapon
,所以傳入的對象類型可以是任何Weapon
子類。Spring容器根據配置信息創建所需子類實例,並注入Player
類中,如下所示:
<bean id="player" class="com.qikegu.demo.Player">
<construct-arg ref="weapon"/>
</bean>
<bean id="weapon" class="com.qikegu.demo.Gun">
</bean>
上面代碼中<construct-arg ref="weapon"/>
ref指向id="weapon"
的bean,傳入的武器類型是Gun
,如果想改爲Sword
,可以作如下修改:
<bean id="weapon" class="com.qikegu.demo.Sword"> </bean>
② 註解方式注入:Bean也可以通過Java註解的方式配置。Java註解直接加在需要裝配的Bean Java類上。
要定義一個Bean,可以通過@Bean
註解,Spring容器會註冊這個Bean,並將方法名作爲Bean ID。
示例:SpringConfig.java
@Configuration
public class SpringConfig {
// 定義 App Bean
@Bean
public App app() {
return new App(logger()); // 調用Bean方法logger()注入Logger Bean實例
}
// 定義 Database Bean
@Bean
public Database database() {
return new Database();
}
// 定義 Logger Bean
@Bean
public Logger logger() {
return new Logger();
}
}
可以使用AnnotationConfigApplicationContext
讀取配置類。
public class Test {
public static void main(String[] args) {
// 使用`AnnotationConfigApplicationContext`讀取配置類
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
App app = context.getBean("app", App.class);
}
}
(2)Spring 框架中的常見依賴注入方式:
①xml常見注入方式:Setter方法注入、構造函數注入以及工廠方法注入
Setter方法注入:要求 Bean 提供一個默認的構造函數,併爲需要注入的屬性提供對應的 Setter 方法 。Spring 先調用 Bean 的默認構造函數實例化 Bean 對象,然後通過反射來調用 Setter 方法注入屬性值 。 下面我們舉一個例子:
public class Book {
/**
* 書名
*/
private String name;
/**
* 定價
*/
private double price;
/**
* 作者
*/
private Author author;
public void setName(String name) {
this.name = name;
}
...省略get、set
}
<bean id="book" class="net.deniro.spring4.bean.Book">
<property name="name">
<value>面紗</value>
</property>
<property name="price">
<value>25.5</value>
</property>
<property name="wordNum">
<value>80000</value>
</property>
</bean>
構造函數注入:使用構造函數注入的前提是 Bean 必須提供帶參的構造函數。構造函數注入保證一些必要的屬性在 Bean 實例化時就得到設置,這樣 Bean 在實例化後就可以使用啦。我們爲 “書” 這個類,添加一個帶參的構造函數:
public Book(String name, double price) {
this.name = name;
this.price = price;
}
public Book(String name, Author author) {
this.name = name;
this.author = author;
}
配置1:<constructor-arg>
的元素中有一個 type 屬性,它表示構造函數中參數的類型,這是 spring 用來判斷配置項與構造函數入參對應關係的 “橋樑”。
<bean id="book2" class="net.deniro.spring4.bean.Book">
<constructor-arg type="java.lang.String">
<value>人生的枷鎖</value>
</constructor-arg>
<constructor-arg type="double">
<value>35</value>
</constructor-arg>
</bean>
配置2:如果 Book 類定義的構造函數具有多個類型相同入參,那麼就需要依賴配置順序咯,如果想定義的更加靈活,那麼就可以使用 “ 按索引匹配入參”。注意: Spring 底層是採用 Java 反射能力來實現依賴注入的,但 Java 反射無法獲知構造函數的參數名,所以只能通過入參類型與索引信息來間接地確定構造函數的配置項與入參之間的對應關係。
<bean id="book3" class="net.deniro.spring4.bean.Book">
<constructor-arg index="0">
<value>人生的枷鎖</value>
</constructor-arg>
<constructor-arg index="1">
<value>上海譯文出版社</value>
</constructor-arg>
</bean>
配置3:如果 Bean 構造函數的入參類型不是基礎數據類型,而且入參類型各異,那麼可以通過 Java 反射機制獲取構造函數的入參類型。
<bean id="author" class="net.deniro.spring4.bean.Author">
<constructor-arg value="毛姆"/>
</bean>
<bean id="book5" class="net.deniro.spring4.bean.Book">
<constructor-arg>
<value>人生的枷鎖</value>
</constructor-arg>
<constructor-arg>
<ref bean="author"/>
</constructor-arg>
</bean>
工廠方法注入:工廠類負責創建目標類實例,它對外屏蔽了目標類的實例化細節,返回的類型一般是接口或抽象類的形式。
public class BookFactory {
public Book create() {
return new Book("面紗", "重慶出版社");
}
}
配置1:非靜態
<bean id="bookFactory" class="net.deniro.spring4.bean.BookFactory"/>
<!-- factory-bean:指定工廠類;factory-method:指定工廠方法-->
<bean id="book11" factory-bean="bookFactory" factory-method="create"/>
配置2:靜態
<!--class:指定工廠類; factory-method:指定靜態工廠方法 -->
<bean id="book12" class="net.deniro.spring4.bean.BookFactory" factory-method="createBook"/>
②基於註解的注入
註解方式註冊bean,注入依賴 ,主要有四種註解可以註冊bean,每種註解可以任意使用,只是語義上有所差異:
- @Component:可以用於註冊所有bean
- @Repository:主要用於註冊dao層的bean
- @Controller:主要用於註冊控制層的bean
- @Service:主要用於註冊服務層的bean
描述依賴關係主要有兩種:
- @Resource:java的註解,默認以byName的方式去匹配與屬性名相同的bean的id,如果沒有找到就會以byType的方式查找,如果byType查找到多個的話,使用@Qualifier註解(spring註解)指定某個具體名稱的bean。
-
@Resource @Qualifier("userDaoMyBatis") private IUserDao userDao; public UserService(){
- @Autowired:spring註解,默認是以byType的方式去匹配與屬性名相同的bean的id,如果沒有找到,就通過byName的方式去查找
-
@Autowired @Qualifier("userDaoJdbc") private IUserDao userDao;
注1:懶加載,即當對象需要用到的時候再去加載。初始化時不進行加載。
註解方式:@Lazy
xml配置方式:
<bean id="app" class="App" lazy-init="true"></bean>
注2:集合類型屬性的注入
<!--注入list集合-->
<property name="list" >
<list>
<value>張三</value>
<value>李四</value>
<value>王五</value>
</list>
</property>
<!--注入set集合-->
<property name="set">
<set>
<value>aaa</value>
<value>bbb</value>
<value>xxx</value>
</set>
</property>
<!--注入Map集合-->
<property name="map">
<map>
<entry key="aa" value="11"/>
<entry key="bb" value="22"/>
<entry key="cc" value="33"/>
</map>
</property>
4、Bean 作用域
Spring定義了多種Bean作用域,可以基於這些作用域創建bean,包括:
- 單例(Singleton):在整個應用中,只創建bean的一個實例。
- 原型(Prototype):每次注入或者通過Spring應用上下文獲取的時候,都會創建一個新的bean實例。
- 會話(Session):在Web應用中,爲每個會話創建一個bean實例。
- 請求(Rquest):在Web應用中,爲每個請求創建一個bean實例。
分爲註解和xml兩種形式:在默認情況下,Spring應用上下文中所有bean都是作爲以單例(singleton)的形式創建的。
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyIsBean{...}
<bean id="BEANID" class = "com.my.beans" scope="prototype">
5、Spring Bean 生命週期【重點】
Bean 的生命週期概括起來就是 4 個階段:
- 實例化(Instantiation)
- 屬性賦值(Populate)
- 初始化(Initialization)
- 銷燬(Destruction)
如上圖所示,Bean 的生命週期還是比較複雜的,下面來對上圖每一個步驟做文字描述:
-
Spring啓動,查找並加載需要被Spring管理的bean,進行Bean的實例化
-
Bean實例化後對將Bean的引入和值注入到Bean的屬性中
-
檢查Aware的相關接口並設置相關依賴:①如果Bean實現了BeanNameAware接口的話,Spring將Bean的Id傳遞給setBeanName()方法 ②如果Bean實現了BeanFactoryAware接口的話,Spring將調用setBeanFactory()方法,將BeanFactory容器實例傳入 ③如果Bean實現了ApplicationContextAware接口的話,Spring將調用Bean的setApplicationContext()方法,將bean所在應用上下文引用傳入進來。
-
如果Bean實現了BeanPostProcessor接口,Spring就將調用他們的postProcessBeforeInitialization()方法。
-
如果Bean 實現了InitializingBean接口,Spring將調用他們的afterPropertiesSet()方法。類似的,如果bean使用init-method聲明瞭初始化方法,該方法也會被調用
-
如果Bean 實現了BeanPostProcessor接口,Spring就將調用他們的postProcessAfterInitialization()方法。
-
註冊Destruction相關回調接口,不是真正意義上的銷燬(還沒使用呢),而是先在使用前註冊了銷燬的相關調用接口,爲了後面第9步真正銷燬 bean 時再執行相應的方法。
-
此時,Bean已經準備就緒,可以被應用程序使用了。他們將一直駐留在應用上下文中,直到應用上下文被銷燬。
-
如果bean實現了DisposableBean接口,Spring將調用它的destory()接口方法,同樣,如果bean使用了destory-method 聲明銷燬方法,該方法也會被調用。
下面我們結合代碼來直觀的看下,在 doCreateBean() 方法中能看到依次執行了這 4 個階段:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 1. 實例化
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object exposedObject = bean;
try {
// 2. 屬性賦值
populateBean(beanName, mbd, instanceWrapper);
// 3. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
// 4. 銷燬-註冊回調接口
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
return exposedObject;
}
Spring Aware系列接口:主要用於輔助Spring bean獲取Spring容器中的資源
容器管理的 Bean 一般不需要了解容器的狀態和直接使用容器, 但是在某些情況下, 是需要在 Bean 中直接對IOC容器進行操作的, 可以通過特定的 Aware
接口來完成。
Spring中提供了一些以Aware結尾的接口,實現了Aware接口的bean再被初始化之後,可以獲取相應資源,通過Aware接口,可以對Spring相應資源進行操作。
接口名 | 描述 |
ApplicationContextAware | 實現了這個接口的類都可以獲取到一個 ApplicationContext 對象. 可以獲取容器中的所有 Bean |
BeanClassLoaderAware | 獲取 bean 的類加載器 |
BeanFactoryAware | 獲取 bean 的工廠 |
BeanNameAware | 獲取 bean 在容器中的名字 |
BeanPostProcessor:解決同一個接口有多個實現類問題
BeanPostProcessor也稱爲Bean後置處理器,它是Spring中定義的接口,在Spring容器的創建過程中(具體爲Bean初始化前後)會回調BeanPostProcessor中定義的兩個方法。BeanPostProcessor的源碼如下:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
其中postProcessBeforeInitialization方法會在每一個bean對象的初始化方法調用之前回調;
postProcessAfterInitialization方法會在每個bean對象的初始化方法調用之後被回調。
文章參考:
https://www.toutiao.com/a6759714946667250179/
https://www.toutiao.com/a6795466742278652419/