文章目錄
收藏這三篇筆記,完整回顧Spring常見問題及使用方式速查:
0. 基本概念
- 控制反轉(IoC):被調用者的實例不再由調用者創建,而是由 Spring 容器創建,這稱爲控制反轉。
- 依賴注入(DI):Spring 容器在創建被調用者的實例時,會自動將調用者需要的對象實例注入給調用者,這樣,調用者通過 Spring 容器獲得被調用者實例,這稱爲依賴注入。
文檔、JAR包:官方倉庫地址。
1. 示例pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
2. IOC容器
二者相比,前者功能簡單且沒有初始化自檢。
2.1 BeanFactory(不常用)
Resource resource = new ClassPathResource("ApplicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
beanFactory.getBean("$BeanName");
2.2 ApplicationContext(示例)
ApplicationContext.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="service0" class="Service">
<constructor-arg name="name" type="java.lang.String" value="service0:name"/>
</bean>
<!-- 構造標籤詳見下 -->
<bean id="dao0" class="DAO">
<constructor-arg name="id" type="java.lang.Integer" value="7"/>
<constructor-arg name="name" type="java.lang.String" value="dao0"/>
<constructor-arg name="service" type="Service" ref="service0"/> <!-- 實例依賴-->
</bean>
</beans>
Service.java
:
@Data // 使用了 lombok
@AllArgsConstructor
public class Service {
private String name;
}
DAO.java
:
@Data
@AllArgsConstructor
public class DAO {
private Integer id;
private String name;
private Service service; // 依賴Service,採用`ref`
}
測試代碼:
public class Main {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
DAO a = (DAO)context.getBean("dao0");
System.out.println(a);
}
}
目錄結構爲:
2.3 通過註解進行裝配
通過註解進行裝配,編寫方便、無需寫xml標籤文件,但若有更改需要重編譯。
2.3.1 常用註解
@Component
:可以使用此註解描述 Spring 中的 Bean,但它是一個泛化的概念,僅僅表示一個組件(Bean),並且可以作用在任何層次。使用時只需將該註解標註在相應類上即可;將會將其實例化和注入依賴並加入IoC容器。@Repository
:用於將數據訪問層(DAO層)的類標識爲 Spring 中的 Bean,其功能與@Component
相同。@Service
:通常作用在業務層(Service 層),用於將業務層的類標識爲 Spring 中的 Bean,其功能與@Component
相同。@Controller
:通常作用在控制層(如 Struts2 的 Action),用於將控制層的類標識爲 Spring 中的 Bean,其功能與@Component
相同。@Autowired
:用於對 Bean 的屬性變量、屬性的 Set 方法及構造函數進行標註,配合對應的註解處理器完成 Bean 的自動配置工作。默認按照 Bean 的類型進行裝配。@Resource
:其作用與 Autowired 一樣。其區別在於@Autowired
默認按照 Bean 類型裝配,而@Resource
默認按照 Bean 實例名稱進行裝配。@Qualifier
:與@Autowired
註解配合使用,會將默認的按 Bean 類型裝配修改爲按 Bean 的實例名稱裝配,Bean 的實例名稱由@Qualifier
註解的參數指定。
附 · 使用JacaConfig類配置Bean所用到的註解:
@Configuation
等價於<beans></beans>
,不可用於標註final
、匿名類,必須爲靜態類。@Bean
等價於<bean></bean>
。@ComponentScan
等價於<context:component-scan base-package="com.dxz.demo"/>
。
2.3.2 示例:使用註解裝配的MVC
目錄結構:
此處可以選擇構建一個配置類以取代 ApplicationContext.xml
:
@Configuration // 該註解同樣也需要被掃描到,因此同樣要在xml文件中進行配置
// @ComponentScan("MVC") // 等價於<context:component-scan base-package="MVC"/> //由於目錄結構中該配置類與MVC同包,會被xml配置直接掃描到,因此此處不需要。
public class SpringConfiguration {
// 等價於
// <bean id="user0" class="xxx.User">
// <constructor-arg name="id" type="java.lang.Integer" value="0"></constructor-arg>
// <constructor-arg name="name" type="java.lang.String" value="Jack"></constructor-arg>
// </bean>
@Bean(name = "user0")
public User UserBeanJack(){
return new User(0, "Jack");
}
}
構建一個控制器:
@Controller
public class UserController {
@Resource // 此處使用Resource,默認採用首字母小寫的形式去找到類, 等價於 getBean("userService")
private UserService userService;
public void acceptRequest(ApplicationContext context, Object request){
System.out.println("Accept request.");
User user = (User)context.getBean("user0"); // 假設根據Request構建Model
System.out.println("Controller find user: " + user);
this.userService.service(user); // 找到Model對應的業務
System.out.println("MVC Controller Over.");
}
}
構建Model層的各個組件:,首先是業務邏輯:
@Service("userService") // 默認是類名首字母小寫, 等價於 <bean id="userService" class="xxx.UserService"/>
public class UserService {
private UserDao userDao;
@Autowired // 此處使用自動裝配的形式
public UserService(UserDao userDao){
this.userDao = userDao;
}
public void service(User user){
System.out.println("MVC Service sth. with " + user);
this.userDao.save(user);
System.out.println("MVC Service Over.");
}
}
數據持久層:
@Repository("userDao") // 等價於 <bean id="userDao" class="xxx.UserDao"/>
public class UserDao {
public void save(User user){
System.out.println("數據庫已保存" + user);
}
}
一個POJO類:
@Data // 使用了lombok簡化代碼編寫
@AllArgsConstructor
public class User {
private Integer id;
private String name;
}
最後,還需要讓Spring掃描到這些註解:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 增加第3, 8, 9行-->
<context:component-scan base-package="MVC"/>
</beans>
測試:
public class Main {
public static void main(String[] args){
final ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
UserController userController = (UserController)context.getBean("userController");
// 注意不能用 UserController userController = new UserController();
userController.acceptRequest(context, null);
}
}
/*打印結果如下:
Accept request.
Controller find user: User(id=0, name=Jack)
MVC Service sth. with User(id=0, name=Jack)
數據庫已保存User(id=0, name=Jack)
MVC Service Over.
MVC Controller Over.*/
- 需要注意的是,所有的實例都要從IoC容器裏獲取,自行new出來的實例不會被Spring注入,會導致Null異常。
2.3.3 示例所使用的流程圖
2.4 往容器內配置Bean的三種方式
- 使用xml進行配置,bean的名字爲
id
字段。 - 使用JavaConfig進行配置(
@Configuration
),默認bean的名字爲@Bean
所標註的方法名。 - 使用註解進行配置,默認Bean的名字爲類名(首字母小寫)。
2.5 @Autowired 和 @Resource 的區別
- 前者默認按類型裝配,當容器內存在多種同一類型的Bean時,需要使用
@Qualifier(value = "$BeanName")
纔可以找到被注入的對象。 - 後者在不指定
name
字段時,也是以同一類型的Bean進行注入,若存在多個同類型對象,會報錯;當指定name
字段時等同於getBean("$BeanName")
(某些框架中,找不到會再根據類型尋找)。 - 二者都可以標註在
setter()
或構造器方法上;@Autowired
不被推薦在字段上直接標註(缺點見下文)。
2.5.1 注入方式及字段注入的缺點
對於二者,注入方式都有三種:構造器注入、setter注入、field注入,區別如下:
public class UserController {
@Autowired // field注入,也稱爲反射注入或字段注入,對訪問級別無要求
private UserService userService;
@Autowired // 構造器注入(需要爲public) 適用於強制依賴項
public UserController(UserService userService){
this.userService = userService;
}
@Autowired // setter注入(需要爲public) 適用於可選依賴項
public void setUserService(UserService userService){
this.userService = userService;
}
}
使用字段注入,其最大缺點爲:
- 類會和Spring強耦合,由於無構造器或setter方法,無法在容器外使用。
- 無法使用屬性注入的方式構建不可變對象。
- 同第一小點,在測試時無法脫離容器進行測試,即 new 出來的 Mock 對象無法被引入測試單元。
- 強依賴和可選依賴都被隱藏,無法區分,違背單一職責原則。
3. Bean的常見概念
3.1 Bean的常用屬性
屬性名稱 | 描述 |
---|---|
id | 是一個 Bean 的唯一標識符,Spring 容器對 Bean 的配置和管理都通過該屬性完成 |
name | Spring 容器同樣可以通過此屬性對容器中的 Bean 進行配置和管理,name 屬性中可以爲 Bean 指定多個名稱,每個名稱之間用逗號或分號隔開 |
class | 該屬性指定了 Bean 的具體實現類,它必須是一個完整的類名,使用類的全限定名 |
scope | 用於設定 Bean 實例的作用域,其屬性值有 singleton(單例)、prototype(原型)、request、session 和 global Session。其【默認值】是 singleton。 |
property | 元素的子元素,用於調用 Bean 實例中的 Set 方法完成屬性賦值,從而完成依賴注入。該元素的 name 屬性指定 Bean 實例中的相應屬性名 |
constructor-arg | 元素的子元素,可以使用此元素傳入構造參數進行實例化。該元素的 index 屬性指定構造參數的序號(從 0 開始),type 屬性指定構造參數的類型 |
constructor-arg:name/index | 標定一個bean的屬性名稱/構造函數的第index個參數,用於初始化實例 |
ref/value | 和 等元素的子元素,用於直接指定一個引用/常量值;亦可作爲初始化時注入集合類元素的標籤 |
list | 用於封裝 List 或數組類型的依賴注入 |
set | 用於封裝 Set 或數組類型的依賴注入 |
map | 用於封裝 Map 或數組類型的依賴注入 |
entry | |
factory-bean | 當class是一個工廠類時,指定其作爲工廠 |
factory-method | 當class是一個工廠類時,使用該標籤標註的靜態方法(或若已指定一個factory-bean標註的實例)得到實例 |
autowire | 是否自動裝配,詳見下文表格 |
3.1.1 自動裝配
屬性取值 | 描述 |
---|---|
byName | 根據 Property 的 name 自動裝配,如果一個 Bean 的 name 和另一個 Bean 中的 Property 的 name 相同,則自動裝配這個 Bean 到 Property 中。 |
byType | 根據 Property 的數據類型(Type)自動裝配,如果一個 Bean 的數據類型兼容另一個 Bean 中 Property 的數據類型,則自動裝配。 |
constructor | 根據構造方法的參數的數據類型,進行 byType 模式的自動裝配。 |
autodetect | 如果發現默認的構造方法,則用 constructor 模式,否則用 byType 模式。 |
no | 【默認】情況下,不使用自動裝配,Bean 依賴必須通過 ref 元素定義。 |
3.2 Bean的作用域
Spring 容器在初始化一個 Bean 的實例時,同時會指定該實例的作用域。Spring3 爲 Bean 定義了五種作用域,具體如下。
singleton
:單例模式,使用 singleton 定義的 Bean 在 Spring 容器中只有一個實例,這也是 Bean 【默認】的作用域。prototype
:原型模式,每次通過 Spring 容器獲取 prototype 定義的 Bean 時,容器都將創建一個新的 Bean 實例。request
:在一次 HTTP 請求中,容器會返回該 Bean 的同一個實例。而對不同的 HTTP 請求,會返回不同的實例,該作用域僅在當前 HTTP Request 內有效。session
:在一次 HTTP Session 中,容器會返回該 Bean 的同一個實例。而對不同的 HTTP 請求,會返回不同的實例,該作用域僅在當前 HTTP Session 內有效。global Session
:在一個全局的 HTTP Session 中,容器會返回該 Bean 的同一個實例。該作用域僅在使用 portlet context 時有效。
3.3 Bean的生命週期
- 根據配置情況調用 Bean 構造方法或工廠方法實例化 Bean,包含以下四個預處理項目:
ResouceLoader
加載配置信息;BeanDefintionReader
解析配置信息,生成若干個BeanDefintion
;BeanDefintion
由BeanDefintionRegistry
管理起來;BeanFactoryPostProcessor
對配置信息進行加工(也就是處理配置的信息,一般通過PropertyPlaceholderConfigurer
來實現);
- 利用依賴注入完成 Bean 中所有屬性值的配置注入。
- 如果 Bean 實現了
BeanNameAware
接口,則 Spring 調用 Bean 的setBeanName()
方法傳入當前 Bean 的id
值。 - 如果 Bean 實現了
BeanFactoryAware
接口,則 Spring 調用setBeanFactory()
方法傳入當前工廠實例的引用。 - 如果 Bean 實現了
ApplicationContextAware
接口,則 Spring 調用setApplicationContext()
方法傳入當前ApplicationContext
實例的引用。 - 如果
BeanPostProcessor
(BBP) 和 Bean 關聯,則 Spring 將調用該接口的預初始化方法postProcessBeforeInitialzation()
對 Bean 進行加工操作,此處非常重要,Spring 的 AOP 就是利用它實現的。 - 如果 Bean 實現了
InitializingBean
接口,則 Spring 將調用afterPropertiesSet()
方法。 - 如果在配置文件中通過
init-method
屬性指定了初始化方法,則調用該初始化方法。 - 如果
BeanPostProcessor
和 Bean 關聯,則 Spring 將調用該接口的初始化方法postProcessAfterInitialization()
。此時,Bean 已經可以被應用系統使用了。 - 如果在
<bean>
中指定了該 Bean 的作用範圍爲scope="singleton"
,則將該 Bean 放入 Spring IoC 的緩存池中,將觸發 Spring 對該 Bean 的生命週期管理;如果在<bean>
中指定了該 Bean 的作用範圍爲scope="prototype"
,則將該 Bean 交給調用者,調用者管理該 Bean 的生命週期,Spring 不再管理該 Bean。 - 如果 Bean 實現了
DisposableBean
接口,則 Spring 會調用destory()
方法將 Spring 中的 Bean 銷燬;如果在配置文件中通過destory-method
屬性指定了 Bean 的銷燬方法,則 Spring 將調用該方法對 Bean 進行銷燬。