Author: ACatSmiling
Since: 2024-07-19
IoC
IoC
:Inversion of Control,控制反轉。是物件導向程式設計中的一種設計原則/設計思想
,旨在降低程式碼之間的耦合度,提高系統的靈活性和可維護性
。其核心思想是透過反轉物件的控制權,將物件建立與物件之間的呼叫過程交給專門的容器進行管理
(如 Spring 框架中的 IoC 容器),而不是由程式程式碼直接控制(即由容器來負責物件的建立和依賴關係的注入,從而實現物件之間的解耦)。
實現方式
依賴注入
:Dependency Injection,是 IoC 的一種實現方式,透過容器在執行期動態地將依賴關係注入到元件之中。依賴注入可以透過建構函式注入
、Setter 方法注入
和介面注入
等方式實現。
依賴查詢
:IoC 的另一種實現方式,但不如依賴注入常用。在這種方式下,元件透過容器提供的 API 來查詢並獲取它所依賴的物件。
事件驅動
:Event-Driven,應用程式元件對特定事件的發生做出響應,而不是直接呼叫其他元件的方法。這種方式下,元件之間的耦合度降低,因為它們不直接呼叫對方的方法,而是透過事件來互動。事件驅動是一種程式設計方式,IoC 是一種設計思想,二者都是旨在降低元件之間的耦合度,從而提高程式的靈活性和可用性,但本質上有所區別。
BeanFactory
BeanFactory
是 Spring 框架中的一個核心介面,它提供了高階的 IoC 容器機制。BeanFactory 負責例項化、配置和管理應用程式中的各種物件,這些物件在 Spring 中被稱為 Beans。
BeanFactory 介面定義了 Spring 容器的最基本功能,它提供了以下幾個核心方法:
-
getBean(String name)
:根據 Bean 的名稱獲取一個 Bean 例項。/** * Return an instance, which may be shared or independent, of the specified bean. * <p>This method allows a Spring BeanFactory to be used as a replacement for the * Singleton or Prototype design pattern. Callers may retain references to * returned objects in the case of Singleton beans. * <p>Translates aliases back to the corresponding canonical bean name. * <p>Will ask the parent factory if the bean cannot be found in this factory instance. * @param name the name of the bean to retrieve * @return an instance of the bean. * Note that the return value will never be {@code null} but possibly a stub for * {@code null} returned from a factory method, to be checked via {@code equals(null)}. * Consider using {@link #getBeanProvider(Class)} for resolving optional dependencies. * @throws NoSuchBeanDefinitionException if there is no bean with the specified name * @throws BeansException if the bean could not be obtained */ Object getBean(String name) throws BeansException;
-
getBean(Class<T> requiredType)
:根據 Bean 的型別獲取一個 Bean 例項。/** * Return the bean instance that uniquely matches the given object type, if any. * <p>This method goes into {@link ListableBeanFactory} by-type lookup territory * but may also be translated into a conventional by-name lookup based on the name * of the given type. For more extensive retrieval operations across sets of beans, * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}. * @param requiredType type the bean must match; can be an interface or superclass * @return an instance of the single bean matching the required type * @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws BeansException if the bean could not be created * @since 3.0 * @see ListableBeanFactory */ <T> T getBean(Class<T> requiredType) throws BeansException;
-
containsBean(String name)
:檢查容器中是否包含指定名稱的 Bean。/** * Does this bean factory contain a bean definition or externally registered singleton * instance with the given name? * <p>If the given name is an alias, it will be translated back to the corresponding * canonical bean name. * <p>If this factory is hierarchical, will ask any parent factory if the bean cannot * be found in this factory instance. * <p>If a bean definition or singleton instance matching the given name is found, * this method will return {@code true} whether the named bean definition is concrete * or abstract, lazy or eager, in scope or not. Therefore, note that a {@code true} * return value from this method does not necessarily indicate that {@link #getBean} * will be able to obtain an instance for the same name. * @param name the name of the bean to query * @return whether a bean with the given name is present */ boolean containsBean(String name);
-
isSingleton(String name)
:判斷指定名稱的 Bean 是否是單例(Singleton 模式)。/** * Is this bean a shared singleton? That is, will {@link #getBean} always * return the same instance? * <p>Note: This method returning {@code false} does not clearly indicate * independent instances. It indicates non-singleton instances, which may correspond * to a scoped bean as well. Use the {@link #isPrototype} operation to explicitly * check for independent instances. * <p>Translates aliases back to the corresponding canonical bean name. * <p>Will ask the parent factory if the bean cannot be found in this factory instance. * @param name the name of the bean to query * @return whether this bean corresponds to a singleton instance * @throws NoSuchBeanDefinitionException if there is no bean with the given name * @see #getBean * @see #isPrototype */ boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
-
isPrototype(String name)
:判斷指定名稱的 Bean 是否是多例(Prototype 模式)。/** * Is this bean a prototype? That is, will {@link #getBean} always return * independent instances? * <p>Note: This method returning {@code false} does not clearly indicate * a singleton object. It indicates non-independent instances, which may correspond * to a scoped bean as well. Use the {@link #isSingleton} operation to explicitly * check for a shared singleton instance. * <p>Translates aliases back to the corresponding canonical bean name. * <p>Will ask the parent factory if the bean cannot be found in this factory instance. * @param name the name of the bean to query * @return whether this bean will always deliver independent instances * @throws NoSuchBeanDefinitionException if there is no bean with the given name * @since 2.0.3 * @see #getBean * @see #isSingleton */ boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
-
getType(String name)
:獲取指定名稱的 Bean 的型別。/** * Determine the type of the bean with the given name. More specifically, * determine the type of object that {@link #getBean} would return for the given name. * <p>For a {@link FactoryBean}, return the type of object that the FactoryBean creates, * as exposed by {@link FactoryBean#getObjectType()}. This may lead to the initialization * of a previously uninitialized {@code FactoryBean} (see {@link #getType(String, boolean)}). * <p>Translates aliases back to the corresponding canonical bean name. * <p>Will ask the parent factory if the bean cannot be found in this factory instance. * @param name the name of the bean to query * @return the type of the bean, or {@code null} if not determinable * @throws NoSuchBeanDefinitionException if there is no bean with the given name * @since 1.1.2 * @see #getBean * @see #isTypeMatch */ @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException;
ApplicationContext
在實際使用中,BeanFactory 往往透過其子介面ApplicationContext
來實現,後者是一個更為功能豐富的容器介面。ApplicationContext 提供了事件釋出、國際化支援、環境抽象以及更多的企業級服務。在 Spring 應用中,通常推薦使用 ApplicationContext,因為它提供了比 BeanFactory 更完整的功能集合。
資源訪問
:ApplicationContext 提供了統一的資源訪問方式,例如讀取檔案和類路徑資源。國際化
:支援國際化(i18n)的訊息訪問,允許定義訊息的文字資源。事件釋出
:ApplicationContext 可以釋出事件給感興趣的事件監聽器。這是一個事件驅動程式設計的功能,可以用於應用程式元件之間的通訊。環境抽象
:提供了對外部化配置以及環境(如生產、開發環境)的抽象。應用層面的服務
:支援郵件服務、任務排程、JNDI 訪問等企業級服務。AOP(面向切面程式設計)整合
:ApplicationContext 支援 AOP 服務,可以非侵入性地新增橫切關注點(如日誌、事務管理)。宣告式事務管理
:提供了宣告式事務管理的功能,允許使用者透過配置來管理事務。Web 應用整合
:對於 Web 應用,Spring 提供了專門的 WebApplicationContext,它是 ApplicationContext 的擴充套件,提供了 Web 應用所需的功能。
簡單來說,BeanFactory 是一個管理和配置 Beans 的工廠介面,它的實現類負責例項化、配置和組裝 Beans。Spring 透過依賴注入的方式管理這些 Beans 的依賴關係,從而將物件的建立和依賴關係的管理與應用程式的業務邏輯解耦。在更高階的 ApplicationContext 的實現中,BeanFactory 通常作為基礎框架部分被包含使用。
ApplicationContext 的 Diagrams 結構圖如下所示:
在 Spring 中,ApplicationContext 介面的實現類常用 ClassPathXmlApplicationContext 和 AnnotationConfigApplicationContext,它們提供了兩種不同的構建和管理 Spring 應用程式上下文的方式。另外,針對 Web 應用開發,ApplicationContext 介面還提供了一個 WebApplicationContext 子介面。
具體地說,ClassPathXmlApplicationContext 和 AnnotationConfigApplicationContext,二者都是 ApplicationContext 介面的子介面 ConfigurableApplicationContext 介面的實現類。
ClassPathXmlApplicationContext
ClassPathXmlApplicationContext
:是一個從類路徑下載入 XML 配置檔案並初始化 Spring 容器的實現類。這種方法主要用於基於 XML 的配置方式。當建立 ClassPathXmlApplicationContext 的例項時,需要提供一個或多個 Spring 配置檔案的路徑,Spring 容器會載入這些檔案,並根據其中定義的 Bean 和配置資訊來構建應用程式上下文。
建立 ClassPathXmlApplicationContext 的示例程式碼如下:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
在這個例子中,會在類路徑中查詢名為 applicationContext.xml 的配置檔案,Spring 容器會根據該檔案中定義的 Bean 配置來初始化應用程式上下文。
ClassPathXmlApplicationContext 的 Diagrams 結構圖如下所示:
AnnotationConfigApplicationContext
AnnotationConfigApplicationContext
:是針對 Java 註解配置的 ApplicationContext 實現,它允許開發者透過 Java 註解而不是 XML 檔案來配置 Spring 容器。這種方式支援更加現代的程式設計習慣,允許透過註解(如 @Configuration, @Component, @Service 等)來宣告 Bean 和它們的依賴。使用 AnnotationConfigApplicationContext 時,需要提供一個或多個帶有 @Configuration 註解的 Java 配置類,這些類中包含了 Bean 的定義和配置資訊。
建立 AnnotationConfigApplicationContext 的示例程式碼如下:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
在這個例子中,AppConfig 是一個帶有 @Configuration 註解的 Java 配置類,Spring 容器會根據這個類中的註解配置來初始化應用程式上下文。
AnnotationConfigApplicationContext 的 Diagrams 結構圖如下所示:
WebApplicationContext
WebApplicationContext
:為在 Web 應用程式環境中執行的 Spring 應用程式提供了上下文功能。與標準的 ApplicationContext 相比,WebApplicationContext 是專門為 Web 應用設計的,並新增了一些特有的特性和功能。
WebApplicationContext 的一些主要特性包括:
Servlet 生命週期的整合
:WebApplicationContext 與 Web 應用程式的生命週期緊密整合,它能夠在 Servlet 容器(如 Tomcat)初始化時啟動,並在 Servlet 容器關閉時清理資源。Web 資源的訪問
:提供了訪問 Web 資源(如 Servlet 上下文和 Servlet 配置)的能力。作用域擴充套件
:除了標準的 Spring 作用域(singleton、prototype 等),WebApplicationContext 還提供了 Web 相關的作用域,例如 request、session 和 application 作用域,分別對應於一個 HTTP 請求的生命週期、使用者的 HTTP 會話以及整個 Web 應用的生命週期。特定於 Web 的 Bean
:能夠定義和管理特定於 Web 的 Bean,如控制器、檢視解析器、本地化資源和主題解析器等。
在 Spring MVC 框架中,WebApplicationContext 是非常關鍵的一個概念。它通常在 Web 應用程式啟動時由 ContextLoaderListener 或者 DispatcherServlet 初始化。這些元件會載入 Spring 配置檔案(XML 或 Java 配置),並建立一個相應的 WebApplicationContext 例項。
建立 WebApplicationContext 的示例程式碼通常不會直接出現在應用程式程式碼中,而是在 web.xml 或 Spring 的 Java 配置中宣告。以下是透過 web.xml 使用 ContextLoaderListener 初始化 WebApplicationContext 的示例:
<web-app>
<!-- ... 其他配置 ... -->
<!-- ContextLoaderListener 載入應用上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定 Spring 配置檔案的位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- ... 其他配置 ... -->
</web-app>
在基於 Java 配置的 Spring MVC 應用中,可以透過繼承 AbstractAnnotationConfigDispatcherServletInitializer 類來替代 web.xml 配置,並在其中設定 WebApplicationContext:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
在上面的例子中,RootConfig 和 WebConfig 分別是根應用上下文和 DispatcherServlet 應用上下文的 Java 配置類。WebAppInitializer 會自動載入這些配置類,並初始化 WebApplicationContext。
WebApplicationContext 的 Diagrams 結構圖如下所示:
Bean 的生命週期
Spring Bean 的生命週期
:是指 Bean 從建立到銷燬過程中的各個階段。在 Spring 框架中,Bean 的完整生命週期經歷了多個步驟,包括例項化、屬性賦值、初始化和銷燬。
以下是 Bean 生命週期過程的主要步驟:
Bean 定義
:將 Bean 的配置資訊載入到容器中。這可以透過 XML 配置檔案、Java 註解或 Java 配置類來完成。Bean 例項化
:Spring 容器使用 Bean 的定義資訊建立 Bean 例項。這通常透過呼叫類的無參構造方法或工廠方法來完成。屬性賦值
:如果 Bean 配置中包含屬性(property)值,那麼這些屬性將會被設定到新建立的 Bean 例項上。BeanNameAware
:如果 Bean 實現了 BeanNameAware 介面,Spring 將會呼叫 setBeanName() 方法,傳遞 Bean 的 ID。BeanFactoryAware 和 ApplicationContextAware
:如果 Bean 實現了 BeanFactoryAware 或 ApplicationContextAware 介面,Spring 容器將呼叫 setBeanFactory() 或 setApplicationContext() 方法,傳遞 Spring BeanFactory 或 ApplicationContext 例項。其他 Aware 介面
:如 EnvironmentAware、ResourceLoaderAware、MessageSourceAware 等,如果 Bean 實現了這些介面,相應的方法也會被呼叫。BeanPostProcessor(前置處理)
:BeanPostProcessor 的 postProcessBeforeInitialization 方法將會被呼叫。這給了開發者在 Bean 初始化之前對其進行額外處理的機會。InitializingBean
:如果 Bean 實現了 InitializingBean 介面,afterPropertiesSet() 方法將被呼叫。自定義 init 方法
:如果在 Bean 的配置中指定了 init 方法,該方法將被呼叫。BeanPostProcessor(後置處理)
:BeanPostProcessor 的 postProcessAfterInitialization 方法將會被呼叫。這給了開發者在 Bean 初始化之後對其進行額外處理的機會。Bean 使用
:此時,Bean 已經準備好被應用程式使用了。DisposableBean
:當容器關閉時,如果 Bean 實現了 DisposableBean 介面,destroy() 方法將被呼叫。自定義 destroy 方法
:如果在 Bean 的配置中指定了 destroy 方法,該方法將在 Bean 銷燬時被呼叫。
透過這些步驟,Spring 容器管理 Bean 的整個生命週期,確保了資源的正確分配和釋放。開發者可以透過實現特定的介面或指定方法來插入自己的邏輯,從而在 Bean 的生命週期的各個階段執行自定義的行為。
值得注意的是,Spring Boot 專案中往往透過註解和自動配置來簡化 Spring Bean 的宣告和管理,但上述的生命週期概念仍然適用。
Bean 的迴圈依賴
在 Spring 官方文件對Circular dependencies
進行了明確的說明(當前最新版本為 6.1.11):
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.
For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.
One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.
Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).
案例演示
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>
<parent>
<groupId>cn.zero.cloud</groupId>
<artifactId>zeloud-self-studies</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>zeloud-self-study-spring</artifactId>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
</project>
說明:本文中的程式碼,基於 Spring 6.1.5 版本。
建立兩個類 A 和 B:
package cn.zero.cloud.spring.circular;
/**
* @author Xisun Wang
* @since 2024/7/13 14:46
*/
public class A {
private B b;
public A() {
System.out.println("Class A was created successfully");
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
package cn.zero.cloud.spring.circular;
/**
* @author Xisun Wang
* @since 2024/7/13 14:46
*/
public class B {
private A a;
public B() {
System.out.println("Class B was created successfully");
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
建立 Spring 配置檔案 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="a" class="cn.zero.cloud.spring.circular.A" scope="singleton">
<property name="b" ref="b"/>
</bean>
<bean id="b" class="cn.zero.cloud.spring.circular.B" scope="singleton">
<property name="a" ref="a"/>
</bean>
</beans>
- scope 預設為 singleton。
建立 Spring 容器測試類:
package cn.zero.cloud.spring.circular;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Spring 容器測試類
*
* @author Xisun Wang
* @since 2024/7/13 21:16
*/
public class ClientSpringContainer {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A a = context.getBean("a", A.class);
B b = context.getBean("b", B.class);
}
}
輸出結果:
2024-07-14 09:00:25.888 [main] DEBUG o.s.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3745e5c6
2024-07-14 09:00:26.046 [main] DEBUG o.s.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [applicationContext.xml]
2024-07-14 09:00:26.082 [main] DEBUG o.s.b.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a'
Class A was created successfully
2024-07-14 09:00:26.095 [main] DEBUG o.s.b.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b'
Class B was created successfully
修改配置檔案中 A 和 B 的 scope 為 prototype:
<?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="a" class="cn.zero.cloud.spring.circular.A" scope="prototype">
<property name="b" ref="b"/>
</bean>
<bean id="b" class="cn.zero.cloud.spring.circular.B" scope="prototype">
<property name="a" ref="a"/>
</bean>
</beans>
重新執行測試類,輸出結果:
2024-07-14 09:01:18.855 [main] DEBUG o.s.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3745e5c6
2024-07-14 09:01:18.984 [main] DEBUG o.s.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [applicationContext.xml]
Class A was created successfully
Class B was created successfully
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'b' while setting bean property 'b'
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:377)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1685)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1434)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:344)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1240)
at cn.zero.cloud.spring.circular.ClientSpringContainer.main(ClientSpringContainer.java:14)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'a' while setting bean property 'a'
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:377)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1685)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1434)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:344)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
... 9 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:266)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
... 17 more
透過以上測試,可以發現,當 Spring 容器中 Bean 的 scope 為 prototype 時,迴圈依賴的問題無法解決,Spring 容器建立物件時,丟擲了BeanCurrentlyInCreationException
。由此,可以得出結論:Spring 容器中,預設的單例(Singleton)場景,支援迴圈依賴,原型(Prototype)場景,不支援迴圈依賴,會丟擲 BeanCurrentlyInCreationException。
DefaultSingletonBeanRegistry
DefaultSingletonBeanRegistry
中,定義了三個 Map,俗稱三級快取
,這就是 Spring 解決迴圈依賴的方法:
/**
* Generic registry for shared bean instances, implementing the
* {@link org.springframework.beans.factory.config.SingletonBeanRegistry}.
* Allows for registering singleton instances that should be shared
* for all callers of the registry, to be obtained via bean name.
*
* <p>Also supports registration of
* {@link org.springframework.beans.factory.DisposableBean} instances,
* (which might or might not correspond to registered singletons),
* to be destroyed on shutdown of the registry. Dependencies between
* beans can be registered to enforce an appropriate shutdown order.
*
* <p>This class mainly serves as base class for
* {@link org.springframework.beans.factory.BeanFactory} implementations,
* factoring out the common management of singleton bean instances. Note that
* the {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}
* interface extends the {@link SingletonBeanRegistry} interface.
*
* <p>Note that this class assumes neither a bean definition concept
* nor a specific creation process for bean instances, in contrast to
* {@link AbstractBeanFactory} and {@link DefaultListableBeanFactory}
* (which inherit from it). Can alternatively also be used as a nested
* helper to delegate to.
*
* @author Juergen Hoeller
* @since 2.0
* @see #registerSingleton
* @see #registerDisposableBean
* @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory
*/
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
}
singletonObjects
:第一級快取,也叫單例池,是一個 ConcurrentHashMap,key 為 String(即 Bean 名稱),value 為 Object(即 Bean 例項),存放的是已經經歷了完整生命週期的 Bean 物件
(已例項化、屬性填充完整、初始化的成品
)。earlySingletonObjects
:第二級快取,是一個 ConcurrentHashMap,key 為 String(即 Bean 名稱),value 為 Object(即 Bean 例項),存放的是早期暴露出來的 Bean 物件
,Bean 的生命週期未結束(已例項化、屬性未填充完整、未初始化的半成品
)。singletonFactories
:第三級快取,是一個 HashMap,key 為 String(即 Bean 名稱),value 為 ObjectFactory<?>(即 Bean 工廠),存放的是可以生成 Bean 的工廠
。
解決迴圈依賴問題的條件:
- 迴圈依賴的 Bean 必須是 singleton,因為 prototype 的 Bean,每次都需要建立新的物件。
- 迴圈依賴的 Bean 不能全是構造器注入,且 beanName 的字母順序在前的 Bean 不能是構造器注入。
在 Spring 中建立 Bean 分三步:
例項化
:doCreateBean,即 new 一個物件。屬性注入
:populateBean, 即 set 一些屬性值。初始化
:initializeBean,即執行一些 aware 介面中的方法,如 initMethod,AOP 代理等。如果 Bean 全是構造器注入,比如
public A(B b)
,那表明在 new A 的時候,就需要先得到 B,此時需要 new B,但是 B 也是public B(A a)
,在 new B 的時候,需要先得到 A。由此,陷入一個套娃死迴圈,A 和 B 都無法建立,迴圈依賴問題無法解決。如果一個 Bean 是 Setter 注入,另一個 Bean 是構造器注入,則需要分情況:
假設 A 透過 Setter 注入 B,B 透過構造器注入 A,此時,迴圈依賴問題可以解決。
- 因為 A 是 Setter 注入,A 可以正常 new 一個例項,當其 Setter 注入屬性 B 時,需要先 new B,此時,因為 B 是構造器注入,可以將前面 new 出來的 A 例項注入,完成 B 的初始化。最後,初始化完成的 B,可以正常使 A 完成初始化。
假設 A 透過構造器注入 B,B 透過 Setter 注入 A,此時,迴圈依賴問題無法解決。
- 因為 A 是構造器注入,在 new A 的時候,需要先 new B,此時,因為 B 是 Setter 注入,B 在屬性注入 A 的時候,發現找不到 A,B 初始化也失敗。最終,A 和 B 都無法建立成功。或許,你會認為,可以先例項化 B 啊,然後能成功例項化 A。確實,思路沒錯,但是,
Spring 容器是按照字母序建立 Bean 的,A 的建立永遠排在 B 前面,
所以無法完成。
結論:
- Spring 容器中,
只有單例的 Bean 會透過三級快取提前暴露來解決迴圈依賴的問題
,而非單例的 Bean,每次從容器中獲取的都是一個新的物件,即都會重新建立,所以非單例的 Bean 是沒有快取的,不會將其放到三級快取中,也就無法解決快取依賴的問題。 - 如果迴圈依賴的 Bean 都是構造器注入,無法解決。
- 如果迴圈依賴的 Bean 不完全是構造器注入,則可能成功,可能失敗,具體跟 BeanName 的字母序有關係。
二級快取的必要性
Spring 在解決迴圈依賴的過程中,二級快取看起來是沒有必要的,只依靠一級快取和三級快取也可以做到解決迴圈依賴的問題。確實如此,只要兩個快取可以做到解決迴圈依賴的問題,但是有一個前提,即 Bean 沒被 AOP 進行切面代理,如果 Bean 被 AOP 進行了切面代理,那麼只使用兩個快取是無法解決問題。
原因是,對於被 AOP 進行切面代理的 Bean,每次執行singleFactory.getObject()
方法,都會返回一個新的代理物件,所以,需要藉助二級快取,將第一次呼叫產生的代理物件放入二級快取中,以保證迴圈依賴的問題被正常處理。
原始碼流程圖
getSingleton(beanName)
:獲取 Bean。doCreateBean(beanName, mbdToUse, args)
:例項化 Bean。populateBean(beanName, mbd, instanceWrapper)
:屬性填充 Bean。initializeBean(beanName, exposedObject, mbd)
:初始化 Bean。addSingleton(beanName, singletonObject)
:新增 Bean。
原始碼 Debug
在 ClientSpringContainer.java 測試類中,建立 ApplicationContext 的時候,設定斷點:
// 斷點 1
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
F7 Step Into 方法內部,定位到 ClassPathXmlApplicationContext.java 的構造方法,呼叫refresh()
方法:
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
// 斷點 2
refresh();
}
}
F7 Step Into,定位到 org.springframework.context.support.AbstractApplicationContext#refresh,呼叫finishBeanFactoryInitialization(beanFactory)
方法:
@Override
public void refresh() throws BeansException, IllegalStateException {
this.startupShutdownLock.lock();
try {
...
try {
...
// 斷點 3
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
}
...
}
F7 Step Into,定位到 org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization,呼叫preInstantiateSingletons()
方法:
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
...
// 斷點 4
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
F7 Step Into,定位到 org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons,呼叫getBean(beanName)
方法:
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// bd.isSingleton():要求是單例的 Bean
// bd.isLazyInit():判斷 Bean 是否是延遲載入
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
getBean(beanName);
}
}
else {
// 斷點 5
getBean(beanName);
}
}
}
...
}
此時,可以看到,所有需要初始化,且非延遲載入的單例 Bean 物件列表(優先建立的是 a):
F7 Step Into,定位到 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean,呼叫getSingleton(beanName)
方法:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 處理 Bean 別名解析
String beanName = transformedBeanName(name);
Object beanInstance;
// 斷點 6
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
...
}
Spring 原始碼中,以 doXxx 開頭的方法,往往都是實際的業務處理方法。
F7 Step Into,定位到 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean),此時,對於 Bean a,一級快取 singletonObjects 中明顯不存在,同時,isSingletonCurrentlyInCreation(beanName) 也為 false(Bean a 此時還未處於建立的狀態),因此,getSingleton 方法直接 return 一個 null:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 檢查 singletonObjects,即一級快取中是否存在 Bean a,很明顯,此時不存在,singletonObject 為 null
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
// 此時,判斷條件為 false,方法直接返回 singletonObject,是一個 null
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
...
}
return singletonObject;
}
getSingleton 方法返回了一個 null,說明 Spring 容器中暫時不存在 Bean a,接下來,我們需要建立一個 Bean a。此時,程式執行回到斷點 6 處:
F8 Step Over 繼續往下執行:
F8 Step Over 繼續往下執行,最終定位到如下位置:
// Create bean instance.
if (mbd.isSingleton()) {
// 斷點 7
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
F7 Step Into,定位到 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>),呼叫singletonFactory.getObject()
方法:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
// 因為一級快取中仍不存在 Bean a,方法繼續往下執行
if (singletonObject == null) {
...
try {
// 斷點 8
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
...
}
return singletonObject;
}
}
F7 Step Into,進入 singletonFactory.getObject() 方法內部,會回到斷點 7 處,也就是說,在斷點 7 處,如果 getSingleton() 方法沒有獲取到 Bean a,就會呼叫第二個引數,最終呼叫createBean(beanName, mbd, args)
方法:
F7 Step Into,定位到 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[]),呼叫doCreateBean(beanName, mbdToUse, args)
方法,開始建立 Bean a:
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
...
try {
// 斷點 9
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
...
}
F7 Step Into,定位到 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,呼叫createBeanInstance(beanName, mbd, args)
:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 斷點 10
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...
}
F7 Step Into,定位到 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance,此方法內部可以看到 Spring 使用了大量反射。
F8 Step Over,可以看到,Spring 對於單例的 Bean,預設支援迴圈依賴:
F8 Step Over,呼叫addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))
:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
...
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 斷點 11
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
...
}
F7 Step Into,定位到 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory,在此方法中,會將 Bean a 對應的 ObjectFactory<?>,新增到三級快取 singletonFactories 中:
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
斷點 11 處執行完後,繼續 F8 Step Over,呼叫populateBean(beanName, mbd, instanceWrapper)
方法,開始屬性填充:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
...
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 斷點 12,Bean a 屬性填充
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
}
F7 Step Into,定位到 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean,最終會呼叫applyPropertyValues(beanName, mbd, bw, pvs)
方法:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
...
// Bean a 中有一個屬性 b 需要填充
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
...
if (pvs != null) {
// 斷點 13
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
F7 Step Into,定位到 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues,呼叫valueResolver.resolveValueIfNecessary(pv, originalValue)
方法:
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
...
// Create a deep copy, resolving any references for values.
List<PropertyValue> deepCopy = new ArrayList<>(original.size());
boolean resolveNecessary = false;
for (PropertyValue pv : original) {
if (pv.isConverted()) {
deepCopy.add(pv);
}
else {
String propertyName = pv.getName();
Object originalValue = pv.getValue();
if (originalValue == AutowiredPropertyMarker.INSTANCE) {
Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod();
if (writeMethod == null) {
throw new IllegalArgumentException("Autowire marker for property without write method: " + pv);
}
originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true);
}
// 斷點 14
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Object convertedValue = resolvedValue;
boolean convertible = isConvertibleProperty(propertyName, bw);
...
}
}
...
}
F7 Step Into,定位到 org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveValueIfNecessary,呼叫resolveReference(argName, ref)
方法:
@Nullable
public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
// We must check each value to see whether it requires a runtime reference
// to another bean to be resolved.
if (value instanceof RuntimeBeanReference ref) {
// 斷點 15
return resolveReference(argName, ref);
}
}
F7 Step Into,定位到 org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveReference,呼叫this.beanFactory.getBean(resolvedName)
方法:
@Nullable
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
try {
...
else {
String resolvedName;
if (beanType != null) {
NamedBeanHolder<?> namedBean = this.beanFactory.resolveNamedBean(beanType);
bean = namedBean.getBeanInstance();
resolvedName = namedBean.getBeanName();
}
else {
resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));
// 斷點 16,Bean a 需要獲取 Bean b 進行屬性填充
bean = this.beanFactory.getBean(resolvedName);
}
this.beanFactory.registerDependentBean(resolvedName, this.beanName);
}
if (bean instanceof NullBean) {
bean = null;
}
return bean;
}
...
}
F7 Step Into,定位到 org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String),回到了最初獲取 Bean a 一樣的地方(從此處開始,暫時變為了 Bean b 的獲取和建立流程):
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
F7 Step Into,繼續執行,再次定位到 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean,繼續呼叫getSingleton(beanName)
方法,回到了斷點 6:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 處理 Bean 別名解析
String beanName = transformedBeanName(name);
Object beanInstance;
// 斷點 6,Bean b
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
...
}
後續的流程,重複和建立 Bean a 一樣的過程,此處不再贅述,直到 Bean b 的屬性需要填充 a 的時候:
在斷點 12 處,F7 Step Into,再次定位到 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean,最終呼叫applyPropertyValues(beanName, mbd, bw, pvs)
方法:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
...
// Bean b 中有一個屬性 a 需要填充
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
...
if (pvs != null) {
// 斷點 13,Bean b
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
F7 Step Into,再次定位到 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues,呼叫valueResolver.resolveValueIfNecessary(pv, originalValue)
方法:
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
...
// Create a deep copy, resolving any references for values.
List<PropertyValue> deepCopy = new ArrayList<>(original.size());
boolean resolveNecessary = false;
for (PropertyValue pv : original) {
if (pv.isConverted()) {
deepCopy.add(pv);
}
else {
String propertyName = pv.getName();
Object originalValue = pv.getValue();
if (originalValue == AutowiredPropertyMarker.INSTANCE) {
Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod();
if (writeMethod == null) {
throw new IllegalArgumentException("Autowire marker for property without write method: " + pv);
}
originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true);
}
// 斷點 14,Bean b
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Object convertedValue = resolvedValue;
boolean convertible = isConvertibleProperty(propertyName, bw);
...
}
}
...
}
F7 Step Into,再次定位到 org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveValueIfNecessary,呼叫resolveReference(argName, ref)
方法:
@Nullable
public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
// We must check each value to see whether it requires a runtime reference
// to another bean to be resolved.
if (value instanceof RuntimeBeanReference ref) {
// 斷點 15,Bean b
return resolveReference(argName, ref);
}
}
F7 Step Into,再次定位到 org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveReference,呼叫this.beanFactory.getBean(resolvedName)
方法:
@Nullable
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
try {
...
else {
String resolvedName;
if (beanType != null) {
NamedBeanHolder<?> namedBean = this.beanFactory.resolveNamedBean(beanType);
bean = namedBean.getBeanInstance();
resolvedName = namedBean.getBeanName();
}
else {
resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));
// 斷點 16,Bean b 需要獲取 Bean a
bean = this.beanFactory.getBean(resolvedName);
}
this.beanFactory.registerDependentBean(resolvedName, this.beanName);
}
if (bean instanceof NullBean) {
bean = null;
}
return bean;
}
...
}
F7 Step Into,再次定位到 org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String),又一次執行此方法,此處是為了獲取 Bean a:
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
F7 Step Into,繼續執行,第三次定位到 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean,第三次呼叫getSingleton(beanName)
方法,回到了斷點 6:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 處理 Bean 別名解析
String beanName = transformedBeanName(name);
Object beanInstance;
// 斷點 6,Bean a
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
...
}
F7 Step Into,第三次定位到 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean),此時,對於 Bean a,一級快取 singletonObjects 和二級快取 earlySingletonObjects 中仍不存在,但此時,isSingletonCurrentlyInCreation(beanName) 為 true(Bean a 此時處於建立的狀態),因此,最終在三級快取 singletonFactories 中找到 Bean a:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 三級快取中有 Bean a
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 將 Bean a 放入二級快取,如果是 AOP 進行切面代理的 Bean,此時是第一次呼叫 singletonFactory.getObject(),直接存入二級快取
this.earlySingletonObjects.put(beanName, singletonObject);
// 移除三級快取中的 Bean a
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
此時,Bean a 從三級快取,移入到二級快取中。
F8 Step Over,一路返回,直到 Bean b 屬性填充的地方populateBean(beanName, mbd, instanceWrapper)
方法,此時,屬性 a 填充完成,下一步,執行 Bean b 的初始化initializeBean(beanName, exposedObject, mbd)
方法:
F7 Step Into initializeBean(beanName, exposedObject, mbd) 方法,在此方法中,實現了 Bean b 的初始化,和後置處理器的相關方法呼叫:
F8 Step Over,一路回到 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>),Bean b 建立完成,呼叫addSingleton(beanName, singletonObject)
方法:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
try {
// 斷點 8
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 斷點 17
addSingleton(beanName, singletonObject);
}
}
F7 Step Into,定位到 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton,此時,會將 Bean b 放入一級快取,並移除二級快取和三級快取中的 Bean b(實際上,二級快取中並沒有 Bean b,只有 Bean a,而此時的三級快取中也只有 Bean b,沒有 Bean a):
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
- 可以看到,Bean b 是直接從三級快取移入一級快取中的,它沒有出現在二級快取中。而 Bean a 則是從三級快取到二級快取,再到一級快取的變化過程。
F8 Step Over,一路返回,直到 Bean a 屬性填充的地方populateBean(beanName, mbd, instanceWrapper)
方法,此時,屬性 b 填充完成,下一步,執行 Bean a 的初始化initializeBean(beanName, exposedObject, mbd)
方法:
F8 Step Over,一路返回,直到呼叫addSingleton(beanName, singletonObject)
方法,即斷點 17 處,F7 Step Into,此時,會將 Bean a 放入一級快取,並移除二級快取和三級快取中的 Bean a:
至此,迴圈依賴的 Bean a 和 Bean b,透過三級快取機制,都完成了建立。
AOP
AOP
:Aspect-Oriented Programming,面向切面程式設計。是一種程式設計正規化
,它允許開發者將那些橫切整個應用程式的關注點(cross-cutting concerns)與業務邏輯(核心關注點)分離開來
。在傳統的物件導向程式設計(OOP)中,實現某些功能(如日誌、安全、事務等)時,程式碼往往會散佈在多個模組或類中,這可能導致程式碼重複且難以維護。AOP 透過提供一種將這些功能模組化的方式,幫助開發者提高程式碼的模組化程度和可維護性。
AOP 主要概念包括:
Aspect(切面)
:一個關注點的模組化,這個關注點可能會橫切多個物件。比如,日誌記錄可以是一個切面,因為幾乎每個方法都需要日誌記錄功能。Join point(連線點)
:程式執行的某個特定位置,比如方法呼叫或異常處理的點。在 Spring AOP 中,一個連線點總是代表一個方法的執行。Pointcut(切點)
:匹配連線點的斷言。切點表示式決定了某個特定的方法是否被一個切面的通知所影響。簡單來說,它定義了 "我們在什麼地方應用切面"。Advice(通知)
:在切點匹配的連線點上所採取的動作。主要有以下型別:
Before advice(前置通知)
:在目標方法執行之前執行的通知。After returning advice(後置通知)
:在目標方法正常執行之後執行的通知。After throwing advice(異常通知)
:在方法丟擲異常退出時執行的通知。After advice(最終通知)
:無論目標方法如何結束,該通知都會執行。Around advice(環繞通知)
:包圍一個連線點的通知,可以在方法呼叫之前和之後自定義行為。
Target object(目標物件)
:被一個或多個切面通知的物件。在 Spring AOP 中,這些物件總是代理物件。AOP proxy(AOP 代理)
:AOP 框架建立的物件,用來實現切面契約(如方法攔截)。在 Spring AOP 中,這通常是 JDK 動態代理或 CGLIB 代理。Weaving(織入)
:把切面與其他應用程式型別或物件連線起來,以建立通知物件的過程。這可以在編譯時(例如使用 AspectJ 編譯器)、載入時或執行時完成。Spring AOP 如同其他純 Java AOP 框架一樣,在執行時完成織入。
Spring AOP 專注於提供執行時織入的 AOP 支援,並且它在概念上是面向代理的。這意味著 Spring AOP 不需要特殊的編譯過程或類載入器來實現 AOP 功能,而是動態地在執行時將通知應用到 Spring 管理的 Bean 上。Spring AOP 對於簡單的場景非常有用,但如果需要完整的 AOP 支援(例如在欄位訪問時進行通知),則可能需要使用更強大的 AOP 實現,如 AspectJ。
AspectJ
AspectJ
:是一個成熟的 AOP 框架,它支援完整的 AOP 概念,包括在編譯時和載入時織入。它比 Spring AOP 提供了更多的織入點,可以實現更復雜的 AOP 場景。與 Spring AOP 相比,AspectJ 是一個更全面的 AOP 解決方案,它提供了更多的控制和靈活性,但也帶來了更復雜的配置和使用門檻。
在 Spring 框架中,Spring AOP 和 AspectJ 都可以使用。對於大多數 Spring 應用程式,Spring AOP 提供的執行時代理機制已經足夠用於處理常見的切面邏輯。對於更高階的需求,Spring 還支援與 AspectJ 的整合,允許開發者利用 AspectJ 的強大功能,同時享受 Spring 框架的便利。
使用 Spring AOP 通常涉及以下步驟:
定義切面
:建立一個類並使用 @Aspect 註解來標識它作為切面。宣告切點
:使用 @Pointcut 註解來定義切點表示式,指定哪些方法將被通知。編寫通知
:使用 @Before、@AfterReturning、@AfterThrowing、@After 和 @Around 註解來編寫不同型別的通知邏輯。配置 AOP
:如果使用的是基於 Java 的配置,需要在配置類上新增 @EnableAspectJAutoProxy 註解來啟用 AOP 代理的自動建立。如果使用的是 XML 配置,需要新增 <aop:aspectj-autoproxy /> 元素。將切面註冊到 Spring 容器中
:作為 Bean 註冊切面,以便能夠由 Spring 容器管理。
Spring AOP 的依賴:
<!-- Spring AOP 依賴 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.x</version> <!-- 請將 x 替換為實際的版本號 --> </dependency> <!-- AspectJ 依賴 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.x</version> <!-- 請將 x 替換為實際的版本號 --> </dependency>
如果使用 Spring Boot,新增 spring-boot-starter-aop 依賴即可:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
此時,@EnableAspectJAutoProxy 是自動啟用的,不需要顯式地新增這個註解。Spring Boot 自動配置會負責設定合適的 AOP 配置,包括啟用 AspectJ 自動代理。
以下是一個簡單的 Spring AOP 示例,展示如何定義一個簡單的日誌記錄切面:
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
private void serviceLayer() {
}
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("Method returned value is : " + result);
}
// ... 其他通知 ...
}
在上面的例子中,我們定義了一個切面 LoggingAspect,它包含了兩個通知:logBefore(方法執行之前記錄日誌)和 logAfterReturning(方法正常返回後記錄返回值)。@Pointcut 註解定義了切點表示式,指定了通知將應用於 com.example.service 包下所有類的所有方法。透過在 Spring 容器中註冊這個切面,我們能夠實現 AOP 的日誌記錄功能。
切入點表示式
切點表示式
:Pointcut Expression,是 AspectJ 表示式語言的一部分,它指定了何時以及在哪裡應用通知(Advice)。切點表示式通常與 @Pointcut 註解一起使用,在 Spring AOP 中定義如下:
@Pointcut("expression")
在這裡,expression 是實際的切點表示式,它可以包含各種匹配模式和指示符。下面是切點表示式的語法結構和組成部分:
設計符(Designator)
:表示要匹配的連線點型別,比如 execution,within,this,target,args,@annotation 等。返回型別
:表示方法的返回型別,可以是具體的型別如 int,String 等,或者使用 * 代表所有型別。方法簽名
:包括全限定類名和方法名,例如 com.example.service.MyService.* 會匹配 MyService 類中的所有方法。可以使用 .. 表示任意個數的引數。引數列表
:表示方法引數,可以是具體型別列表或者萬用字元。例如 (..) 匹配任意數量和型別的引數,而 () 僅匹配無參方法。異常型別
:可以指定方法丟擲的異常型別。
一個典型的切點表示式的示例如下:
@Pointcut("execution(* com.example.service.*.*(..))")
這個表示式的組成部分解釋如下:
execution
:設計符,指明這個表示式是要匹配方法執行的連線點。*
:萬用字元,表示任意返回型別。com.example.service.*
:類路徑萬用字元,表示 com.example.service 包下的任意類。*
:方法名萬用字元,表示匹配所有方法。(..)
:引數列表萬用字元,表示匹配任意引數的方法。
總結來說,切點表示式定義了 "何處" 和 "何時" 執行 AOP 通知,它是透過匹配連線點(如方法執行、建構函式呼叫等)來實現的。設計良好的切點表示式可以精確地捕獲你想要增強的應用程式行為。
除了前面舉例使用的 execution 設計符,還可以使用 @annotation 設計符,這在想要基於自定義註解增強方法行為時非常有用。以 @Around 註解與 @annotation 設計符結合使用為例,來建立一個環繞通知,它針對帶有特定註解的方法執行。
假設有一個自定義註解 @LogExecutionTime,功能上希望記錄所有帶有這個註解的方法的執行時間。首先,需要定義這個註解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
// 可以新增註解屬性
}
接下來,建立一個切面,並在其中定義一個環繞通知,它會捕獲所有帶有 @LogExecutionTime 註解的方法:
@Aspect
@Order(1)
@Component
public class PerformanceAspect {
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
// 執行目標方法
Object result = joinPoint.proceed();
return result;
} finally {
// 計算執行時間
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
}
}
}
在上面的切面中,@Around 註解與 @annotation 設計符結合使用,指定了當一個方法被 @LogExecutionTime 註解標記時,環繞通知 logExecutionTime 應該被執行。
然後,在業務邏輯程式碼中,可以使用 @LogExecutionTime 註解來標註需要記錄執行時間的方法:
@Service
public class MyService {
@LogExecutionTime
public void someMethodToLog() {
// 方法的業務邏輯
}
}
現在,每當呼叫 someMethodToLog() 方法時,PerformanceAspect 中的 logExecutionTime 環繞通知會自動執行,並記錄該方法的執行時間。
工作原理
Spring AOP 的底層原理主要基於代理模式
,它使用了 JDK 動態代理或 CGLIB 代理來建立目標物件的代理
,從而在不改變原有業務邏輯的情況下,實現了橫切邏輯的注入。
以下是 Spring AOP 的底層的主要工作流程:
-
代理模式
:AOP 透過建立原始物件的 "代理" 物件來工作。代理物件會包含附加的行為(即切面程式碼),並將對原始物件的所有呼叫重定向至代理物件。當代理物件接收到呼叫時,它會根據配置執行相應的切面邏輯,然後再將呼叫傳遞給原始物件。 -
JDK 動態代理
:如果目標物件實現了介面,Spring AOP 預設會使用 JDK 動態代理。JDK 動態代理透過反射機制在執行時建立一個實現了目標物件介面的代理類。這個代理類會將所有方法呼叫轉發到 InvocationHandler 實現類的 invoke() 方法中。 -
CGLIB 代理
:如果目標物件沒有實現任何介面,Spring AOP 會使用 CGLIB(Code Generation Library)來建立代理。CGLIB 是一個強大的高效能程式碼生成庫,它在執行時擴充套件目標類並生成子類。這個子類被用作代理物件,可以攔截對父類方法的所有呼叫。 -
AOP 代理的建立
:在 Spring 容器啟動時,如果檢測到類被 AOP 相關注解(如 @Aspect)或配置檔案中的 AOP 配置標記,則 Spring 會自動為這些類建立代理物件。這個過程通常由 Spring 的 Bean 後處理器(BeanPostProcessors)實現,特別是 AnnotationAwareAspectJAutoProxyCreator。 -
切面(Aspect)和通知(Advice)
:切面是模組化橫切關注點的實現,包含了通知(Advice)和切點(Pointcut)。通知定義了切面的具體行為,比如何時執行(前置、後置、環繞等)。切點定義了通知應當應用的位置(即哪些方法呼叫)。 -
攔截器鏈(Interceptor Chain)
:當對代理物件進行方法呼叫時,Spring AOP 會構建一個攔截器鏈,這個鏈中包含了與方法呼叫匹配的所有通知。這些通知按照順序被執行,從而實現了橫切邏輯的注入。 -
通知的執行
:在呼叫鏈的執行過程中,環繞通知(Around Advice)可以決定是否繼續執行鏈中的下一個通知或目標方法,也可以在方法執行前後新增額外行為。最終,目標方法會被執行,然後按照相反的順序執行剩下的通知。 -
AOP 聯盟介面
:Spring AOP 遵循了 AOP 聯盟的 API 和標準,這是一套定義了 AOP 的核心概念和介面的規範。例如,MethodInterceptor 介面用於環繞通知,Joinpoint 提供了對當前方法、執行物件等的訪問。 -
AspectJ 支援
:雖然 Spring AOP 本身不提供完整的 AspectJ 的支援(如編譯時織入),但它可以與 AspectJ 的註解一起使用,如 @Aspect、@Before、@After 等,來定義切面和通知。Spring AOP 使用 AspectJ 的切點解析系統,這允許使用 AspectJ 風格的切點表示式來定義切點。 -
代理呼叫的效能影響
:代理機制在方法呼叫過程中引入了額外的層,這會導致一定程度的效能開銷。為了最小化效能影響,Spring AOP 通常僅應用於單例 Bean,並且一般只在有橫切需求的時候使用。 -
BeanPostProcessor 和 AOP
:Spring 透過 BeanPostProcessor 介面允許對 Bean 的例項化過程進行干預。AOP 正是利用這一點,在 Bean 例項化之後但在其被注入到其他 Bean 之前,將代理物件織入到 Spring 容器中。 -
AOP 上下文傳播
:AOP 上下文(如安全上下文、事務上下文等)通常是透過執行緒本地儲存(ThreadLocal)來保持的,確保即使是在多執行緒環境中,上下文也是安全的。 -
載入時織入(Load-Time Weaving,LTW)
:Spring 還支援載入時織入,這是一種使用特殊的類載入器在類載入到 JVM 時應用 AOP 通知的技術,這需要使用 AspectJ 的編織器和 Spring 的 LoadTimeWeaver 介面。
總結來說,Spring AOP 的工作原理主要是透過代理模式,在執行時為目標物件建立代理,並透過這些代理將切面邏輯織入到應用程式中。透過這種方式,Spring AOP 能夠將橫切關注點(如日誌、事務等)與業務邏輯分離,從而提高程式碼的模組化和可維護性。
事務管理
Spring 事務管理是 Spring 框架提供的一種機制,它允許開發人員以宣告式或程式設計式的方式管理事務。
以下是 Spring 事務管理的關鍵概念和元件:
事務抽象
:Spring 提供了一個抽象層來統一不同事務管理 API 的事務模型(如 JDBC、JPA、Hibernate 等)。這種抽象使得開發者可以不必關心具體的事務管理實現,而是可以專注於業務邏輯。事務管理器
:Spring 的事務管理器是實現事務管理的核心元件,它處理事務的建立、提交和回滾。根據不同的持久化技術,Spring 提供了不同的事務管理器實現,如 DataSourceTransactionManager(用於 JDBC)、HibernateTransactionManager 等。宣告式事務管理
:宣告式事務管理是透過配置來管理事務,通常使用 @Transactional 註解。只要在類或方法上新增了這個註解,Spring 就會自動為其方法呼叫建立和管理事務。宣告式事務管理是推薦的方式,因為它將事務管理程式碼與業務邏輯程式碼分離。程式設計式事務管理
:程式設計式事務管理是透過編寫程式碼來管理事務。Spring 提供了 TransactionTemplate 或 PlatformTransactionManager API,允許開發人員在程式碼中直接控制事務的邊界和行為。這種方式靈活但程式碼侵入性較強,通常只在宣告式事務管理無法滿足需求時使用。事務的傳播行為
:Spring 提供了多種事務傳播行為,定義了在方法呼叫時事務如何傳播。例如,REQUIRED 表示當前方法必須在一個事務中執行,如果呼叫者已經在事務中,則加入該事務,否則建立一個新的事務。事務的隔離級別
:事務的隔離級別定義了一個事務可能受其他併發事務影響的程度。Spring 支援多種隔離級別,如 READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE。事務超時和只讀標誌
:可以為事務指定超時,超過指定時間未完成則自動回滾。只讀標誌可以告知事務管理器當前事務不會進行任何修改,以便進行一些最佳化處理。
PlatformTransactionManager
PlatformTransactionManager
:是 Spring 框架中用於事務管理的核心介面,它為不同的事務管理策略提供了一致的程式設計模型。具體的實現類則針對不同的事務管理機制,例如 JDBC、Hibernate、JPA 或者 JTA 等。
配置事務管理器
配置步驟:
-
選擇實現類
:根據業務的資料訪問技術,選擇合適的 PlatformTransactionManager 實現。例如,如果使用的是 JDBC,那麼應該使用 DataSourceTransactionManager,對於 JPA,使用 JpaTransactionManager,Hibernate 則使用 HibernateTransactionManager,而對於全域性事務(如分散式事務)則使用 JtaTransactionManager。 -
宣告 Bean
:在 Spring 配置檔案或者使用 Java 配置類中宣告一個 PlatformTransactionManager 的 bean。-
XML 配置示例:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
-
Java 配置示例:
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
-
-
關聯資料來源
:確保事務管理器有一個資料來源(DataSource)或者實體管理器工廠(EntityManagerFactory)與之相關聯。
使用事務管理器
當在 Spring 配置中宣告瞭 PlatformTransactionManager 的 Bean 之後,就可以透過以下兩種方式使用事務管理器:
-
宣告式事務管理
:最常用的方法,使用 @Transactional 註解來宣告方法的事務行為。Spring 容器會自動檢測 @Transactional 註解,並使用配置的事務管理器來管理事務的邊界和行為。 -
程式設計式事務管理
:使用 TransactionTemplate 或者直接使用 PlatformTransactionManager 的方法來在程式碼中控制事務。這種方法靈活但編碼複雜,通常只在特殊情況下使用。下面是一個使用 TransactionTemplate 的示例:
@Autowired
private PlatformTransactionManager transactionManager;
public void executeTransactionally(MyBusinessLogic businessLogic) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 執行業務邏輯
businessLogic.execute();
} catch (SomeBusinessException ex) {
// 設定回滾標記
status.setRollbackOnly();
}
}
});
}
在大多數情況下,宣告式事務管理透過 @Transactional 註解提供了足夠的功能和便利性,並且是推薦的方法,程式設計式事務管理則在需要更細粒度控制事務時才會使用。
@Transactional
@Transactional 註解
:是 Spring 框架提供的一種宣告式事務管理機制。它可以應用於類定義或者方法上,當應用在類上時,表示該類中的所有公共方法都會被事務管理;當應用在方法上時,則只有標記了該註解的方法會被事務管理。
以下是 @Transactional 註解的主要作用:
建立和管理事務
:該註解告訴 Spring 容器,被註解的方法需要在一個資料庫事務中執行。根據配置,Spring 可以為每個方法呼叫建立一個新的事務或者使用現有的事務。事務傳播行為
:你可以指定方法的事務傳播行為,控制當前事務方法是應該加入已存在的事務中執行,還是應該開啟一個新的事務執行。這是透過 propagation 屬性來設定的。事務隔離級別
:可以指定事務的隔離級別,這樣就能夠控制事務內部和外部的互動,以避免問題如髒讀、不可重複讀、幻讀等。這是透過 isolation 屬性來設定的。事務超時
:可以設定事務的超時時間,超時時間定義了事務在被強制回滾之前可以執行多長時間。這是透過 timeout 屬性來設定的。只讀事務
:可以宣告事務是否為只讀。設定事務為只讀可以幫助資料庫最佳化事務。對於只讀操作,這可以提高效能。這是透過 readOnly 屬性來設定的。異常回滾規則
:可以指定哪些異常應該觸發事務回滾。預設情況下,執行時異常(RuntimeException 及其子類)和錯誤(Error)將導致事務回滾,而檢查型異常(非 RuntimeException 的異常)不會導致事務回滾。透過 rollbackFor 和 noRollbackFor 屬性可以細化控制回滾行為。
使用示例:
@Service
public class MyService {
@Transactional(readOnly = true)
public MyData readData(Long id) {
// 這個方法有一個只讀事務,通常用於查詢操作
}
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE, timeout = 60)
public void updateData(MyData data) {
// 這個方法將在一個新的事務中執行,並且擁有 SERIALIZABLE 的隔離級別和 60 秒的超時時間
}
}
在上面的例子中,readData 方法被標記為只讀事務,而 updateData 方法被標記為在新事務中執行,並使用最高的隔離級別 SERIALIZABLE。
事務傳播行為
@Transactional 註解使用 propagation 屬性設定事務的傳播行為,事務的傳播行為定義了一個事務性方法如何關聯到已存在的事務當中。
以下是 Spring 支援的幾種事務傳播型別:
REQUIRED
:如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。這是最常見的選擇,預設設定。SUPPORTS
:如果當前存在事務,則加入該事務;如果沒有,則以非事務的方式執行。適用於不需要事務控制的方法。MANDATORY
:如果當前存在事務,則加入該事務;如果沒有,則丟擲異常。適用於必須在事務環境下執行的操作。REQUIRES_NEW
:總是啟動一個新的事務,如果當前存在事務,則將當前事務掛起。適用於需要獨立事務控制的情況。NOT_SUPPORTED
:總是以非事務方式執行,如果當前存在事務,則將當前事務掛起。適用於不應在事務環境中執行的操作。NEVER
:總是以非事務方式執行,如果當前存在事務,則丟擲異常。這個選項表明方法不應在事務環境中執行。NESTED
:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則行為與REQUIRED一樣。巢狀事務可以單獨回滾而不影響外部事務。注意,並不是所有的事務管理器都支援巢狀事務。
使用示例:
@Transactional(propagation = Propagation.REQUIRED)
public void someTransactionalMethod() {
// ...
}
實際開發中,根據具體業務需求和操作,合理選擇傳播行為是非常重要的。例如,如果方法必須在事務的上下文中執行,但是不想中斷現有的事務流程,那麼 REQUIRES_NEW 或者 NESTED 可能是一個好的選擇。反之,如果方法不需要事務,可以選擇 NOT_SUPPORTED 或 NEVER。預設的 REQUIRED 適用於大多數場景,它能夠確保事務的存在,並在需要的時候建立新事務。
事務隔離級別
@Transactional 註解使用 isolation 屬性設定事務的隔離級別,事務的隔離級別定義了一個事務可能受其他併發事務影響的程度。
以下是定義在 org.springframework.transaction.annotation.Isolation 列舉中的隔離級別,這些隔離級別對應於 SQL 標準中定義的隔離級別:
DEFAULT
:使用底層資料來源的預設隔離級別。大多數情況下,預設值是 READ_COMMITTED。READ_UNCOMMITTED
:允許事務讀取未提交的資料變更,可能會導致髒讀、不可重複讀和幻讀。READ_COMMITTED
:允許事務讀取併發事務已經提交的資料。可以防止髒讀,但是不可重複讀和幻讀仍然可能發生。REPEATABLE_READ
:確保如果在事務期間多次讀取同一資料,則會看到相同的資料,即事務可以重複讀取之前讀取的資料。可以防止髒讀和不可重複讀,但幻讀仍然可能發生。SERIALIZABLE
:這是最嚴格的隔離級別,事務完全序列化順序執行,以此確保一個事務不會看到其他事務的中間狀態。這個級別可以防止髒讀、不可重複讀和幻讀,但是它可能導致大量的效能開銷,並且增加了死鎖的風險。
注意:以上是 SQL 標準中定義的隔離級別,對於不同的資料庫實現可能不同,比如在 MySQL 中,預設的 REPEATABLE READ 隔離級別就已經解決了幻讀的問題。因此,使用 @Transactional 設定事務隔離級別時,需要結合底層資料來源。
使用示例:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void someTransactionalMethod() {
// ...
}
合理選擇隔離級別對於保證資料庫操作的準確性和效率至關重要。較低的隔離級別(如 READ_UNCOMMITTED 和 READ_COMMITTED)通常具有更高的併發效能,但是可能會面臨髒讀、不可重複讀和幻讀等併發問題。較高的隔離級別(如 REPEATABLE_READ 和 SERIALIZABLE)可以提供更好的資料一致性保證,但可能會降低併發效能並增加死鎖的可能性。
因此,在選擇事務隔離級別時,需要根據應用程式的具體需求和資料庫的效能特性進行權衡。
工作原理
@Transactional 註解背後的原理主要基於 Spring AOP(面向切面程式設計)
。
以下是 @Transactional 註解的主要工作流程:
代理建立
:當 Spring 應用程式啟動時,如果配置了 <tx:annotation-driven/> 或者 @EnableTransactionManagement,Spring 容器會掃描所有的 Bean,查詢哪些類和方法上標有 @Transactional 註解。AOP 代理
:對於標有 @Transactional 註解的 Bean,Spring 會為它們建立一個 AOP 代理(這個代理可以是 JDK 動態代理或者 CGLIB 代理)。這個代理將攔截對被代理物件的所有方法呼叫。事務攔截
:當代理物件的方法被呼叫時,代理會先呼叫事務攔截器(TransactionInterceptor),該攔截器會根據 @Transactional 註解的屬性來建立或者加入一個現有的事務。事務邊界管理
:TransactionInterceptor 與 PlatformTransactionManager 一起協作,確保在方法開始執行前開啟事務,在方法執行結束後根據執行情況(是否出現異常)提交或回滾事務。事務同步
:如果事務成功開啟,所有的資料庫操作都會在這個事務的上下文中執行。Spring 還會負責事務的同步,比如確保在當前事務中使用相同的資料庫連線。
失效情形
在某些情況下,@Transactional 註解可能失效,導致事務管理不按預期進行。以下是一些常見的導致 @Transactional 註解失效的情形:
註解用於非 public 方法
:@Transactional 註解預設只能應用於 public 方法。如果將它用於 protected、private 或 package-private 方法上,事務是不會被 Spring 代理捕獲的,因此註解會失效。方法內部呼叫
:當事務方法在同一個類內部被直接呼叫時,如 this.someTransactionalMethod(),事務的代理機制並不會被觸發,因為代理是在方法外部的呼叫才能介入的。自呼叫問題
:類似於方法內部呼叫,當一個類自身的一個非事務方法內部呼叫另一個有 @Transactional 註解的方法時,由於事務代理沒有介入,所以事務不會生效。解決方法是使用方法注入或者從 Spring 容器中獲取代理物件。異常處理不當
:預設情況下,只有執行時異常會觸發事務回滾。如果方法丟擲的是檢查型異常,並且沒有在 @Transactional 註解中明確指定回滾條件,事務不會回滾。不支援的事務傳播行為
:如果選擇的傳播行為不支援當前事務上下文,則可能導致事務失效。如 Propagation.NEVER 或 Propagation.NOT_SUPPORTED。資料庫不支援事務
:使用的資料庫必須支援事務。如果你使用的是一個不支援事務的資料庫(比如某些 NoSQL 資料庫),@Transactional 註解將無效。事務管理器配置錯誤
:如果 Spring 配置中事務管理器沒有配置正確,或者有多個事務管理器而沒有指定使用哪一個,那麼 @Transactional 註解可能不會正確工作。Spring Bean 配置錯誤
:如果事務性的類沒有被 Spring 作為 Bean 管理,比如沒有在 Spring 容器中宣告,而是透過 new 關鍵字手動例項化的,事務註解也不會生效。代理方式
:如果類是透過實現介面建立的代理(JDK 動態代理),那麼只有介面中定義的方法可以應用事務。如果使用 CGLIB 代理而類是 final 的,同樣會導致事務失效。多執行緒環境
:當在多執行緒環境下執行事務方法時,事務上下文可能無法正確地傳播到新執行緒中,導致事務失效。
為了確保 @Transactional 註解能夠正確工作,應該:
- 確保相關的類和方法由 Spring 管理,即它們應該是 Spring 容器中的 Bean。
- 使用公共方法來應用 @Transactional 註解。
- 如果需要在非執行時異常時回滾,使用 rollbackFor 屬性明確指定。
- 確保正確配置了事務管理器,並且如果有多個,正確地指定了要使用的事務管理器。
- 理解並正確設定事務的傳播行為。
- 如果是自呼叫情況,考慮重構程式碼,使得事務方法能夠透過代理物件呼叫,或者使用 ApplicationContext 來獲取代理物件。
- 如果類實現了介面,確保事務方法在介面中被宣告過,或者顯式地使用基於類的代理(如 CGLIB)。
- 避免在類定義為 final 的情況下使用基於類的代理,因為這會導致 CGLIB 代理失敗。
透過注意這些細節,可以確保 @Transactional 註解能夠按照預期工作,正確地管理事務邊界。如果發現事務沒有如預期工作,檢查上述列表中的每一項,通常可以幫助快速定位問題。在除錯過程中,啟用 Spring 事務管理的詳細日誌也是一個好方法,它可以提供更多關於事務行為的資訊。
原文連結
https://github.com/ACatSmiling/zero-to-zero/blob/main/SpringEcosystem/spring.md