源碼框架-Spring-思維導圖

来源:https://www.cnblogs.com/sudokill/archive/2022/10/20/16808162.html
-Advertisement-
Play Games

SpringIOC源碼 Spring源碼大綱 https://www.processon.com/view/link/5f5075c763768959e2d109df IOC載入流程圖 https://www.processon.com/view/link/5f15341b07912906d9ae8 ...


目錄

SpringIOC源碼

Spring源碼大綱   https://www.processon.com/view/link/5f5075c763768959e2d109df

IOC載入流程圖  https://www.processon.com/view/link/5f15341b07912906d9ae8642

Spring迴圈依賴圖  https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c

Spring Xmind 小結
Spring Xmind 小結

IOC容器載入過程及Bean生命周期

BeanFactory和ApplicationContext的區別

Spring Framework 中文文檔

BeanFactory和ApplicationContext的區別就是工廠和4S店的區別

BeanFactory是Bean的工廠,spring的頂層核心介面,沒有BeanFactory就沒有Bean的存在,工廠只負責按照要求生產Bean,Bean的定義信息,要生產成什麼樣由下家(ApplicationContext)說了算。

ApplicationContext面向的是用戶,所以需要更好的服務用戶,不僅要提供Bean和調用工廠去生產Bean還要提供一系列人性化的服務如國際化、載入Bean定義、監聽器等等,怎麼生成Bean的事交給工廠去做。

但是ApplicationContext也依賴工廠,沒有工廠他沒有辦法提供Bean,沒有辦法更好的服務用戶,所以它需要繼承工廠;ApplicationContext 繼承自 BeanFactory,但是它不應該被理解為 BeanFactory 的實現類,而是說其內部持有一個實例化的 BeanFactory(DefaultListableBeanFactory)。以後所有的 BeanFactory 相關的操作其實是給這個實例來處理的。DefaultListableBeanFactory也有註冊bean定義的能力。

BeanDefinition是bean在spring中的描述,有了BeanDefinition我們就可以創建Bean。

BeanDefinition介面: 頂級基礎介面,用來描述Bean,裡面存放Bean元數據,比如Bean類名、scope、屬性、構造函數參數列表、依賴的bean、是否是單例類、是否是懶載入等一些列信息。

Spring IOC容器的具體載入過程

// spring的配置方式一般有三種:註解配置/xml配置/JavaConfig配置 
// 創建spring 容器: ClassPathXmlApplicationContext構造器 / AnnotationConfigApplicationContext構造器 
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
   this();                      // 1.準備工作
   register(componentClasses);  // 2.註冊配置類
   refresh();                   // 3.IOC容器刷新
}
// 1. 這是一個有參的構造方法,可以接收多個配置類,不過一般情況下,只會傳入一個配置類。 
// 2. 這個配置類有兩種情況,一種是傳統意義上的帶上@Configuration註解的配置類,還有一種是沒有帶上@Configuration,但是帶有@Component,@Import,@ImportResouce,@Service, @ComponentScan等註解的配置類,在Spring內部把前者稱為Full配置類,把後者稱之為Lite配置類。
  • 1.準備工作,過程中主要實例化的對象

GenericApplicationContext#beanFactory = new DefaultListableBeanFactory()

父類構造函數為spring上下文,實例化了beanFactory:DefaultListableBeanFactory  
DefaultListableBeanFactory 是最底層,實現功能最全的BeanFactory

AnnotationConfigApplicationContext#reader = new AnnotatedBeanDefinitionReader(this);

初始化註解模式下bean定義掃描器, 註冊了一些創世紀後置處理器,比如:
解析我們配置類的後置處理器ConfigurationClassPostProcessor(它是BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor的實現,用來處理配置類解析@Configuration、@ComponentScan、@Import等);
AutowiredAnnotationBeanPostProcessor(BeanPostProcessor的實現,解析@Autowired);
AnnotationAwareOrderComparator(Order註解相關)

AnnotationConfigApplicationContext#scanner = new ClassPathBeanDefinitionScanner(this);

初始化classPath類型bean定義掃描器,可以用來掃描指定包下所有類,並將符合過濾條件的類(設置this.includeFilters =AnnotationTypeFilter(Component.class))封裝成 beanDefinition 註冊到容器,適用於沒有指定配置類時手動調用scan,不是預設的掃描包對象,可忽略

BeanDefinitionReader 讀取
BeanDefinitionScanner 掃描
BeanDefinitionRegistry 註冊

拓展點:

BeanFactoryPostProcessor 修改BeanDefinition
BeanDefinitionRegistryPostProcessor 註冊BeanDefinition  eg:集成Mybatis

  • 2.註冊配置類

將配置類註冊到beanDefinitionMap中,此時beanDefinitionMap中只有配置類、創世紀後置處理器

  • 3.IOC容器刷新過程-源碼debug

AbstractApplicationContext#refresh()

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()

Spring解析xml配置文件,將要創建的所有bean配置信息保存起來;javaconfig只刷新該beanFactory包括 beanDefinitionMap和beanDefinitionNames等

invokeBeanFactoryPostProcessors(beanFactory)

執行BeanFactoryPostProcessor 調用BeanFactory的後置處理器,真正的掃描包對象scanner,掃描class,解析成beanDefinition並註冊到beanDefinitionMap

registerBeanPostProcessors(beanFactory)

註冊Bean後置處理器

finishBeanFactoryInitialization(beanFactory)

實例化所有剩餘的(非延遲初始化)單例

beanFactory.preInstantiateSingletons()

獲取容器中所有bean定義的名稱;
合併BeanDefinition生成RootBeanDefinition;
判斷beanDefinition是不是抽象的&&不是單例的&&不是懶載入的;
判斷是FactoryBean則創建,SmartFactoryBean可調用工廠方法getobject返回內部對象,不是FactoryBean調用getBean();getBean(&beanName)返回的是FactoryBean,beanDefinitionMap中只有FactoryBean,但單例池最終會生成2個bean

// 核心!!!getBean方法

AbstractBeanFactory#getBean調用#doGetBean
  DefaultSingletonBeanRegistry#getSingleton(beanName) 為空,往下調用
  DefaultSingletonBeanRegistry#getSingleton(beanName,singletonFactory) 鉤子函數調用
AbstractAutowireCapableBeanFactory#createBean調用#doCreateBean,再依次調用 
  AbstractAutowireCapableBeanFactory#resolveBeanClass 載入類 先載入當前BeanDefinition所對應的class
  AbstractAutowireCapableBeanFactory#createBeanInstance 實例化(addSingletonFactory放入緩存)
  AbstractAutowireCapableBeanFactory#populateBean 屬性註入,填充屬性/註入依賴
  AbstractAutowireCapableBeanFactory#initializeBean 初始化,執行aware介面中的方法,完成AOP代理,最後把最終生成的代理對象放入單例池,下次getBean時就直接從單例池拿即可
 
// AbstractAutowireCapableBeanFactory#createBeanInstance,bean的實例化過程:
// 使用合適的實例化策略來創建新的實例:工廠方法、構造函數自動註入、簡單初始化 
1.首先判斷BeanDefinition中是否設置了Supplier,如果設置了則調用Supplier的get()得到對象。
2.如果沒有設置Supplier,檢查BeanDefinition中是否設置了factoryMethod,然後調用工廠方法得到對象。@Bean所註解的方法就是factoryMethod,配置類為factoryBean
3.推斷構造方法:根據class推斷構造方法,根據推斷出來的構造方法,反射得到一個對象

// DefaultSingletonBeanRegistry#getSingleton(beanName)
一級緩存二級緩存beanName不存在且標記為正在創建,加鎖,取出三級緩存的Bean工廠調用getObject方法拿到單例對象或代理對象,將單例對象添加到二級緩存中,移除三級緩存單例工廠中對應的singletonFactory

// AbstractAutowireCapableBeanFactory#addSingletonFactory放入三級緩存
InstantiationAwareBeanPostProcessor#postProcessAfterInitialization是後置處理器的擴展點,允許在對象返回之前修改甚至替換bean;
如果存在AOP,返回的不是原始的Bean實例,而是實現AOP方法的代理類;
只用二級緩存會將AOP中創建代理對象的時機提前,設計之初就是讓Bean在生命周期的最後一步完成代理而不是在實例化後就立馬完成代理;
迴圈依賴發生時提前代理,沒有迴圈依賴代理方式不變,依然是初始化以後代理;
有ab對象,getBean(a)在載入b的流程中如果發生了迴圈依賴,就是說b又依賴了a,我們就要對a執行AOP,
提前獲取增強以後的a對象,這樣b對象依賴的a對象就是增強以後的a了。
見https://segmentfault.com/a/1190000023712597

簡述Bean的生命周期


1.利用該類的構造方法來實例化得到一個對象(但是如何一個類中有多個構造方法,Spring則會進行選擇,這個叫做推斷構造方法)

2.得到一個對象後,Spring會判斷該對象中是否存在被@Autowired註解了的屬性,把這些屬性找出來並由Spring進行賦值(依賴註入)

3.依賴註入後,Spring會判斷該對象是否實現了BeanNameAware介面、BeanClassLoaderAware介面、BeanFactoryAware介面,如果實現了,就表示當前對象必須實現該介面中所定義的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就會調用這些方法並傳入相應的參數(Aware回調)

4.Aware回調後,Spring會判斷該對象中是否存在某個方法被@PostConstruct註解了,如果存在,Spring會調用當前對象的此方法(初始化前)

5.緊接著,Spring會判斷該對象是否實現了InitializingBean介面,如果實現了,就表示當前對象必須實現該介面中的afterPropertiesSet()方法,那Spring就會調用當前對象中的afterPropertiesSet()方法(初始化)

6.最後,Spring會判斷當前對象需不需要進行AOP,如果不需要那麼Bean就創建完了,如果需要進行AOP,則會進行動態代理並生成一個代理對象做為Bean(初始化後)

後置處理器的九次調用

時機 方法入口 實現的介面 instanceof eg
實例化前 #resolveBeforeInstantiation Instantiation AwareBeanPostProcessor AnnotationAwareAspectJAutoProxyCreator解析aop切麵信息進行緩存
實例化-推斷構造器 #createBeanInstance SmartInstantiation AwareBeanPostProcessor 通過bean的後置處理器進行選舉出合適的構造函數對象
實例化後 #applyMergedBean DefinitionPostProcessors MergedBeanDefinition PostProcessor @AutoWired的註解的預解析 可以修改BeanDefinition
實例化後 #getEarlyBeanReference SmartInstantiation AwareBeanPostProcessor 解決迴圈依賴
填充屬性前 #populateBean InstantiationAware BeanPostProcessor 用戶可以自定義屬性註入
填充屬性前 #populateBean InstantiationAware BeanPostProcessor 可以修改填充屬性的值 處理@AutoWired
初始化 #initializeBean BeanPostProcessor 例如@PostConstruct
初始化 #initializeBean BeanPostProcessor aop和事務都會在這裡生成代理對象
銷毀bean容器 InitDestroyAnnotationBeanPostProcessor

BeanDefinition

BeanDefinition是Spring頂層核心介面封裝了生產Bean的一切原料,BeanDefinition中存在很多屬性用來描述一個Bean的特點。比如:

  • class,表示Bean類型
  • scope,表示Bean作用域,單例或原型等
  • lazyInit:表示Bean是否是懶載入
  • initMethodName:表示Bean初始化時要執行的方法
  • destroyMethodName:表示Bean銷毀時要執行的方法

內置後置PostProcess處理器

BeanFactoryPostProcessor的調用過程/配置類的解析過程

https://www.processon.com/view/link/5f18298a7d9c0835d38a57c0

調用bean工廠的後置處理器 
1)BeanDefinitionRegistryPostProcessor(先被執行)              它是能註冊BeanDefinition                        的子介面   
所有的bean定義信息將要被載入到容器中,Bean實例還沒有被初始化
2)BeanFactoryPostProcessor(後執行)                           它是修改BeanDefinition 但不能註冊BeanDefinition  的父介面
所有的Bean定義信息已經載入到容器中,但是Bean實例還沒有被初始化
修改BeanDefinition 即通過設置bean對象的類型 setBeanClassName 偷天換日

1.去容器中獲取BeanDefinitionRegistryPostProcessor的bean的處理器名稱
// 判斷是否實現了PriorityOrdered介面的,getBean,調用他的後置處理方法
2.去容器中獲取BeanDefinitionRegistryPostProcessor的bean的處理器名稱
// 判斷是否實現了Ordered介面的,getBean,調用他的後置處理方法
3.去容器中獲取BeanDefinitionRegistryPostProcessor的bean的處理器名稱
// 剩下的沒有被處理過的,getBean,調用他的後置處理方法
4.去容器中獲取BeanDefinitionRegistryPostProcessor,同時實現了BeanFactoryPostProcessor的bean的處理器名稱
// getBean 調用他的後置處理方法
123後置處理方法   #postProcessBeanDefinitionRegistry
4後置處理方法     #postProcessBeanFactory
// ConfigurationAnnotationProcessor 會走第一步、第四步

5.獲取容器中所有的 BeanFactoryPostProcessor
6.先調用BeanFactoryPostProcessor實現了 PriorityOrdered介面的
7.再調用BeanFactoryPostProcessor實現了 Ordered的
8.調用沒有實現任何方法介面的 後置處理方法 BeanFactoryPostProcessor#postProcessBeanFactory

  • BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 做了什麼?
  1. 迴圈bean定義名稱names找到配置類,判斷其是完全的配置類還是一個非正式的配置類;
  2. 創建一個配置類解析器對象真正地解析配置類,parser.parse(),把我們掃描出來的類添加到beanDefinition的集合即beanDefinitionMap(@ComponentScan);
  3. 新建一個ConfigurationClassBeanDefinitionReader,把我們解析出來的配置類configClasses(解析出來的配置類)註冊到容器中(@Import、@Bean、@ImportResources、ImportBeanDefinition註解)
  • BeanDefinitionRegistryPostProcessor#postProcessBeanFactory 做了什麼?

enhanceConfigurationClasses 配置類增強

  • 提前生成配置類單例bean引發的問題

自定義beanFactory後置處理器會在第一步被ConfigurationClassPostProcessor掃描添加到 beanFactory 的BeanDefinitionMap,在第三步時被getBean,再調用自定義beanFactory後置處理器的後置處理方法;如果是在配置類里@Bean註冊的自定義beanFactory後置處理器,會getBean(配置類),提前生成配置類,沒有通過配置類增強、配置類增強失敗。

解決:static關鍵字修飾@Bean方法返回為BeanPostProcessor、BeanFactoryPostProcessor等類型的方法

// 方式1:    
@Configuration
class AppConfig {
	AppConfig() { System.out.println("AppConfig init...");}
    @Bean
	BeanDefinitionRegistryPostProcessor postProcessor() {
		return new MyBeanDefinitionRegistryPostProcessor();
	}
}
class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
	MyBeanDefinitionRegistryPostProcessor() {System.out.println("MyBeanDefinitionRegistryPostProcessor init...");}
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
	}
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	}
}
// 控制台輸出
AppConfig init...
MyBeanDefinitionRegistryPostProcessor init...

// 警告
org.springframework.context.annotation.ConfigurationClassPostProcessor enhanceConfigurationClasses
Cannot enhance @Configuration bean definition 'appConfig' since 
its singleton instance has been created too early. 
The typical cause is a non-static @Bean method 
with a BeanDefinitionRegistryPostProcessor return type: 
Consider declaring such methods as 'static'.
// 方式2:    
@Configuration
class AppConfig {
	AppConfig() { System.out.println("AppConfig init...");}
}

@Component
class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
	MyBeanDefinitionRegistryPostProcessor() {System.out.println("MyBeanDefinitionRegistryPostProcessor init...");}
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
	}
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	}
}
// 控制台輸出
MyBeanDefinitionRegistryPostProcessor init...
AppConfig init...

配置類@Configuration加與不加的區別

配置類加@Configuration的話,@Bean里方法名獲取對象,對象只實例化一次

BeanDefinitionRegistryPostProcessor中第4步調用postProcessBeanFactory方法時給配置類創建cglib動態代理,指定配置時不加Configuration也行,但加了@Configuration會根據方法名從單例池拿getBean,這樣就有bean和bean之間的引用,而不是重覆載入bean
@Configuration為Full配置類,經過enhance增強,所有的@Bean方法都被BeanMethodInterceptor攔截

重覆beanName覆蓋原則

loadBeanDefinitionsForBeanMethod
1、配置類的名字相同,則報錯(同名@Component)
2、同一個配置類中的@Bean名字相同,則返回true,意思是以先載入的@Bean方法為準
3、不同的配置類中的@Bean名字相同,則返回false,意思是可以被覆蓋,已後被載入的@Bean方法為準

Snipaste_2022-03-15_22-20-24.png

迴圈依賴

如何解決迴圈依賴/為什麼要有二級緩存和三級緩存

https://note.youdao.com/ynoteshare/index.html?id=01ec86d7955e2c9cd45c1c0e22f07535&type=note&_time=1635692387674

三級緩存結構

Map<String,Object> singletonObjects                          // 一級緩存

Map<String,Object> earlySingletonObjects                 // 二級緩存

Map<String,ObjectFactory> singletonFactories          // 三級緩存

一級緩存的作用:存放可用的成品bean;

二級緩存的作用:為了將成熟Bean和純凈Bean分離(未註入屬性),避免多線程下讀取到不完整的Bean;存放半成品bean,半成品bean即已經調用完構造但是還沒有註入屬性和初始化;

三級緩存的作用:用來生產半成品的bean,與getbean方法解耦,能解決aop增強下的迴圈依賴;存放函數介面/鉤子函數,函數介面實現創建動態代理調用BeanPostProcessor,即其要加強的aop處理(為了避免重覆創建,調用會返回動態代理對象或者原實例,再存儲在二級緩存);

真正的解決迴圈依賴是靠二級緩存,不用三級緩存也可以解決迴圈依賴,但這樣就造成了在實例化後就立馬完成代理,違背了最後一步完成代理的原則;

在創建bean的時候,在哪裡通過什麼方式創建了動態代理:通過BeanPostProcessor創建動態代理,在初始化之後或在出現迴圈依賴時實例化之後(實例化 -> 屬性註入 -> 初始化)

發生迴圈依賴會用到二級緩存,普通依賴過程只用到一三級緩存

Spring三級緩存解決setter方式的迴圈依賴原理



為什麼Spring不能解決構造器的迴圈依賴?

從流程圖應該不難看出來,在Bean調用構造器實例化之前,一二三級緩存並沒有Bean的任何相關信息,在實例化之後才放入三級緩存中,因此當getBean的時候緩存並沒有命中,這樣就拋出了迴圈依賴的異常了。

為什麼多例Bean不能解決迴圈依賴?

我們的bean是單例的,而且是欄位註入(setter註入)的,單例意味著只需要創建一次對象,後面就可以從緩存中取出來,欄位註入,意味著我們無需調用構造方法進行註入。

  • 如果是原型bean,那麼就意味著每次都要去創建對象,無法利用緩存;
  • 如果是構造方法註入,那麼就意味著需要調用構造方法註入,也無法利用緩存。

如何進行拓展?

bean可以通過實現SmartInstantiationAwareBeanPostProcessor介面getEarlyBeanReference方法進行拓展

BeanCurrentlyInCreationException

spring的aop代理(包括@Aysnc,@Transactional),一般都是在屬性賦值時中調用#postProcessAfterInitialization方法創建的代理對象,這個代理過程是不涉及到迴圈引用的情況下執行;在迴圈引用下會提前創建代理對象#getEarlyBeanReference(ab迴圈依賴,a通過ObjectFactory提前曝光自己,b通過getObject獲取到這個提前曝光的a對象填充屬性,該earlySingletonReference放進二級緩存,只有迴圈依賴下才會放入二級緩存),

如果迴圈引用下提前創建了代理對象,經過initializeBean初始化又產生代理對象(exposedObject與earlySingletonReference兩者不等拋BeanCurrentlyInCreationException異常,@Aysnc會發生,@Transactional不會發生);解決方式:加上@lazy

spring迴圈依賴在 構造器註入下會拋異常BeanCurrentlyInCreationException 可以用基於屬性註入

監聽器Listener

  • Spring事件體系包括三個組件:事件,事件監聽器,事件廣播器。基於觀察者模式。

事件(ApplicationEvent)負責對應相應監聽器,事件源發生某事件是特定事件監聽器被觸發的原因。事件分為 Spring內置事件自定義事件(繼承ApplicationEvent)

事件監聽器(ApplicationListener)對應於觀察者模式中的觀察者。監聽器監聽特定事件,併在內部定義了事件發生後的響應邏輯。 分為 基於介面(繼承ApplicationListener)、基於註解 (@EventListener)

事件廣播器(ApplicationEventMulticaster)對應於觀察者模式中的被觀察者/主題, 負責通知觀察者對外提供發佈事件和增刪事件監聽器的介面,維護事件和事件監聽器之間的映射關係,併在事件發生時負責通知相關監聽器。

發佈者調用applicationContext.publishEvent(msg),將事件發送給了EventMultiCaster,而後由 EventMultiCaster註冊著所有的Listener,然後根據事件類型決定轉發給那個Listener。

Spring事件監聽器的原理

IOC容器刷新介面refresh方法

initApplicationEventMulticaster 創建事件多播器(預設的事件廣播器SimpleApplicationEventMulticaster)

registerListeners 把我們的事件監聽器名字註冊到多播器上,通過多播器進行播發早期事件

finishRefresh 容器刷新,發佈刷新事件(ContextRefreshedEvent)

Spring提供的事件機制預設是同步的(SimpleApplicationEventMulticaster#multicastEvent),如果想用非同步的可以自己實現ApplicationEventMulticaster介面,併在Spring容器中註冊id為applicationEventMulticaster的Bean

Spring是怎樣避免讀取到不完整的Bean

防止多線程下Spring讀取到不完整Bean加了兩把鎖

一把鎖放在getSingleton()方法三級緩存,第二個線程阻塞直到第一個線程把二三級緩存刪除完;

一把鎖放在getSingleton(,)方法,先從單例池再拿一遍單例對象(double check防重覆創建單例bean)

怎麼樣可以在所有Bean創建完後做擴展代碼?

ContextRefreshedEvent/SmartInitializingSingleton

推斷構造方法底層原理

Spring的判斷邏輯如下:

1.如果一個類只存在一個構造方法,不管該構造方法是無參構造方法,還是有參構造方法,Spring都會用這個構造方法

2.如果一個類存在多個構造方法

a.這些構造方法中,存在一個無參的構造方法,那麼Spring就會用這個無參的構造方法

b.這些構造方法中,不存在一個無參的構造方法,那麼Spring就會報錯

c.如果某個構造方法上加了@Autowired註解,Spring就會用這個加了@Autowired註解構造方法了

SpringAOP底層原理

https://www.processon.com/view/link/5faa4ccce0b34d7a1aa2a9a5

Bean的生命周期  :   UserService.class -> 無參構造方法(推斷構造方法)-> 普通對象 -> 依賴註入(屬性賦值) -> 初始化前 -> 初始化 -> 初始化後 -> 代理對象(UserServiceProxy) -> Bean

如何判斷當前Bean對象需不需要進行AOP:

1.找出所有的切麵Bean

2.遍歷切麵中的每個方法,看是否寫了@Before、@After等註解

3.如果寫了,則判斷所對應的Pointcut是否和當前Bean對象的類是否匹配

4.如果匹配則表示當前Bean對象有匹配的的Pointcut,表示需要進行AOP

利用cglib進行AOP的大致流程:

1.生成代理類UserServiceProxy,代理類繼承UserService

2.代理類中重寫了父類的方法,比如UserService中的test()方法

3.代理對象持有普通對象的引用,UserServiceProxy.target = 普通對象

4.調用代理類的test方法 -> 先執行切麵邏輯@Before,再執行target.test方法

Spring常見代理創建方式:

1.FactoryBean方式創建單個動態代理:

  • proxyInterfaces指定需增強的介面;
  • target指定需增強的實現類
  • interceptorNames 指定Advice(MethodBeforeAdvice, AfterReturningAdvice)、Interceptor(MethodInterceptor)、Advisor(結合Advice或Interceptor)都行;
  • Advice、Interceptor攔截器的粒度只控制到了類級別,類中所有的方法都進行攔截;Advisor攔截器的粒度達到方法級別,通知者切點分為正則匹配/方法名;需要獲取這個代理類

2.autoProxy方式: 根據advisor批量創建自動代理(當Spring發現一個bean需要被切麵織入的時候,Spring會自動生成這個bean的一個代理來攔截方法的執行,確保定義的切麵能被執行);不需要獲取這個代理類

  • BeanPostProcessor手動指定Advice方式,BeanNameAutoProxyCreator指定Advisor,可以使用正則來匹配要創建代理的那些Bean的名字
  • BeanPostProcessor自動掃描Advisor方式,DefaultAdvisorAutoProxyCreator

開啟aop:

1.配置類,加入@EnableAspectJAutoProxy註解

2.切麵類,加入@Aspect註解,定義一個Pointcut方法(切點:指定哪些類需要被代理),最後定義一系列的增強方法(Advice通知:代理邏輯)

切麵類的解析: AspectJAutoProxyRegistrar實現ImportBeanDefinitionRegistrar,在解析配置類到容器時,如果開啟@EnableAspectJAutoProxy註解下,通過registerBeanDefinitions方法為我們容器導入beanDefinition;該beanDefinition 是 AnnotationAwareAspectJAutoProxyCreator,繼承自AbstractAutoProxyCreator,#findCandidateAdvisors在bean實例化前解析aop切麵信息,生成對應的Advisor對象進行緩存

AbstractAdvisorAutoProxyCreator非常強大以及重要,只要Spring容器中存在這個類型的Bean,就相當於開啟了AOP,AbstractAdvisorAutoProxyCreator實際上就是一個BeanPostProcessor,所以在創建某個Bean時,就會進入到它對應的生命周期方法中,比如:在某個Bean初始化之後,會調用wrapIfNecessary()方法進行AOP,底層邏輯是AbstractAdvisorAutoProxyCreator #getAdvicesAndAdvisorsForBean 會找到所有的通知器Advisor,然後判斷當前這個Bean是否存在某個Advisor與之匹配(根據Pointcut)AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply ,如果匹配就表示當前這個Bean有對應的切麵邏輯,需要進行AOP,需要產生一個代理對象

// ProxyFactory產生代理對象
{
    UserService target = new UserService();
    ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.setTarget(target);
		proxyFactory.addAdvisor(new PointcutAdvisor() {
        @Override
        public Pointcut getPointcut() {
            return new StaticMethodMatcherPointcut() {
                @Override
                public boolean matches(Method method, Class<?> targetClass) {
                    return method.getName().equals("test");
                }
            };
        }
        @Override
        public Advice getAdvice() {
            return new MethodInterceptor() {
                @Override
                public Object invoke(MethodInvocation invocation) throws Throwable {
                     System.out.println("before...");
                     Object result = invocation.proceed();
                     System.out.println("after...");
                     return result;
                 }
            };
        }
        @Override
        public boolean isPerInstance() {
            return false;
        }
    });
    UserInterface userService = (UserInterface) proxyFactory.getProxy();
		userService.test();  
}

代理對象執行過程: CglibAopProxy.DynamicAdvisedInterceptor#intercept

1.在使用ProxyFactory創建代理對象之前,需要往ProxyFactory先添加Advisor

2.代理對象在執行某個方法時,會把ProxyFactory中的Advisor拿出來和當前正在執行的方法進行匹配篩選

3.把和方法所匹配的Advisor適配成MethodInterceptor

4.把和當前方法匹配的MethodInterceptor鏈,以及被代理對象、代理對象、代理類、當前Method對象、方法參數封裝為MethodInvocation對象

5.調用MethodInvocation的proceed()方法,開始執行各個MethodInterceptor以及被代理對象的對應方法

6.按順序調用每個MethodInterceptor的invoke()方法,並且會把MethodInvocation對象傳入invoke()方法

7.直到執行完最後一個MethodInterceptor了,就會調用invokeJoinpoint()方法,從而執行被代理對象的當前方法

Spring事務

當我們在某個方法上加了@Transactional註解後,就表示該方法在調用時會開啟Spring事務,而這個方法所在的類所對應的Bean對象會是該類的代理對象。

事務註解@EnableTransactionManagement 為我們的容器導入了添加了兩個Bean:

  1. AutoProxyRegistrar 導入的 InfrastructureAdvisorAutoProxyCreator :開啟自動代理
  2. ProxyTransactionManagementConfiguration 導入的 BeanFactoryTransactionAttributeSourceAdvisor(Advisor)、AnnotationTransactionAttributeSource(pointcut)、TransactionInterceptor(advice)

事務是基於AOP完成的,判斷bean生命周期是否開啟aop:找到所有的通知器對象Advisor,判斷這個bean是否與Advisor匹配:通過pointcut對象(解析@Transactional註解,匹配邏輯為判斷該Bean的類上是否存在@Transactional註解,或者類中的某個方法上是否存在@Transactional註解)、Advice對象(TransactionalInterceptor 代理的邏輯)

該代理對象在執行某個方法時,會再次判斷當前執行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,如果匹配則執行該Advisor中的TransactionInterceptor的invoke()方法,執行基本流程為:

Spring事務的代理對象執行某個方法時的步驟:

1.判斷當前執行的方法是否存在@Transactional註解

2.如果存在,則利用事務管理器(TransactionMananger)新建一個資料庫連接

3.修改資料庫連接的autocommit為false,資料庫連接放入threadlocal

4.執行target.test(),執行程式員所寫的業務邏輯代碼,也就是執行sql

5.執行完了之後如果沒有出現異常,則提交,否則回滾

Spring事務的7種傳播行為

https://blog.csdn.net/weixin_39625809/article/details/80707695

事務傳播行為(propagation behavior)指的就是當一個事務方法被另一個事務方法調用時,這個事務方法應該如何進行。例如:methodA事務方法調用methodB事務方法時,methodB是繼續在調用者methodA的事務中運行呢,還是為自己開啟一個新事務運行,這就是由methodB的事務傳播行為決定的。

1、PROPAGATION_REQUIRED

如果存在一個事務,則支持當前事務。如果沒有事務則開啟一個新的事務。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB(){}
單獨調用A、B方法都會開啟一個新的事務
A調用B方法、B調用A方法都會加入到同一個事務

2、PROPAGATION_SUPPORTS

如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB(){}
單獨調用B方法不會開啟事務
A調用B方法,B會加入這個事務

3、PROPAGATION_MANDATORY

如果存在一個事務,支持當前事務。如果沒有事務,則拋出異常。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB(){}
單獨調用B方法會拋IllegalTransactionStateException異常
A調用B方法,B會加入這個事務

4、PROPAGATION_REQUIRES_NEW

如果存在一個事務,先將這個存在的事務掛起,再開啟一個新的事務。如果沒有事務則開啟一個新的事務。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(){}
單獨調用B方法開啟事務
A方法(外層事務)調用B方法(內層事務),B會開啟一個新的事務
外層事務回滾,內層事務仍然提交

5、PROPAGATION_NOT_SUPPORTED

總是非事務地執行,並掛起任何存在的事務。

6、PROPAGATION_NEVER

總是非事務地執行,如果存在一個活動事務,則拋出異常。

7、PROPAGATION_NESTED

如果存在一個事務,依賴該事務,作為該事務的子事務。如果沒有事務則開啟一個新的事務。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){}
@Transactional(propagation = Propagation.NESTED)
public void methodB(){}
單獨調用B方法開啟事務
A方法(外層事務)調用B方法(內層事務),nested屬於子事務,依賴於外層,有單獨的保存節點
外層事務的回滾可以引起內層事務的回滾,內層事務異常的回滾可導致外層事務的回滾(如果沒有吞掉異常)
也可不導致外層事務的回滾(吞掉異常),外層事務自行決定是commit還是rollback

8、總結

case1:如果A捕獲B的異常,並且未向上拋異常

case2:如果A未捕獲B的異常,則預設將B的異常向上拋

REQUIRES_NEW和NESTED:內層事務拋異常一定會回滾,外層事務可以通過控制吞不吞內層事務拋的異常來決定是否回滾

異常狀態 REQUIRED REQUIRES_NEW NESTED
methodA拋異常 methodB正常 均回滾 A回滾,B正常提交 均回滾
methodA正常 methodB拋異常 case1:均回滾拋異常   case2:均回滾 case1:A正常提交B回滾     case2:均回滾 case1:A正常提交B回滾                case2:均回滾
methodA拋異常 methodB拋異常 均回滾 均回滾 均回滾
methodA正常 methodB正常 均提交 均提交 均提交

用法

@Transactional 可以作用於介面、介面方法、類以及類方法上。當作用於類上時,該類的所有 public方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標註來覆蓋類級別的定義。

雖然 @Transactional 註解可以作用於介面、介面方法、類以及類方法上,但是 Spring 建議不要在介面或者介面方法上使用該註解,因為這隻有在使用基於介面的代理時它才會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP的本質決定的。如果你在 protected、private或者預設可見性的方法上使用 @Transactional 註解,這將被忽略,也不會拋出任何異常。
預設情況下,只有來自外部的方法調用才會被AOP代理捕獲,也就是,類內部方法調用本類內部的其他方法並不會引起事務行為,即使被調用方法使用@Transactional註解進行修飾。

@Transactional不做任何配置,預設是對拋出的unchecked異常、Error回滾,checked異常不會回滾,為了讓所有異常都會讓事務啟動可以將 @Transactional配置為 @Transactional(rollbackFor = Exception.class)
Snipaste_2022-03-23_14-30-26.png

Spring事務不生效

  1. 框架不支持:入口的方法必須是public、事務是否在同一個線程里、資料庫引擎設置不對資料庫不支持事務
  2. 錯誤使用:只對出現運行期異常(java.lang.RuntimeException及其子類)/Error進行回滾、rollbackFor屬性設置錯誤、異常被catch、錯誤地傳播機制
  3. 代理失效:被final、static關鍵字修飾的類或方法、將註解標註在介面方法上將無法用CGLIB代理、是否通過代理對象只有代理對象調用方法才能被攔截
@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){}
public void methodB(){}
A方法(事務)調用B方法(沒有事務),B方法的異常也會導致AB方法事務的回滾
B方法(沒有事務)調用A方法(事務),事務失效

Transaction rolled back because it has been marked as rollback-only

Spring的@Transactional 可以註解到方法上或者類上從而開啟事務,而正確調用類事務方法是通過容器調用,即@autowird 被註入到其他類中使用,因為此時調用方法會被spring容器的 TransactionInterceptor 攔截器攔截

@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB(){}
Propagation.REQUIRED實例,預設事務實例不管是否捕獲異常,全部一起回滾
A方法(外層事務)調用B方法(內層事務),B方法發現異常了會標記整個事務為roll-back
但如果外層方法捕獲異常正常退出後執行commit事務,此時發現已經標記異常會出錯拋UnexpectedRollbackException
部分失敗。全局回滾屬性為true

解決辦法:在catch塊中添加TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 手動回滾
或者內層事務使用propagation = Propagation.NESTED,從而保證內層異常不會影響外層提交

切麵類內的事務

含有@Aspect的類在生命周期的第一個bean後置處理器會被標記不處理,在最後一個bean後置處理器它就不會被代理,故aop切麵類本身使用不了聲明式事務@Transactional;可以在切麵類新寫子方法新建事務,或用publisher.publishEvent發佈非同步事件新建事務

// 判斷當前事務是否是新事務
TransactionAspectSupport.currentTransactionStatus().isNewTransaction()
// @Order
預設為@Order(value = Ordered.LOWEST_PRECEDENCE)優先度最低;可以自定義修改切麵類的優先順序別@Order(value = Ordered.HIGHEST_PRECEDENCE + 1)

自定義AOP與聲明式事務執行順序問題

@SysLog: 自定義AOP,產生系統日誌
@Transactional:聲明式事務

//某個service方法
@SysLog("/testService")
@Transactional(propagation = Propagation.REQUIRED)
public Result testService(Info info) {}

//自定義aop
@Aspect
@Component
public class SysLogAspect{
	@Around("@annotation(sysLog)")  
	public Object around(ProceedingJoinPoint point, SysLog sysLog) {
            obj = point.proceed();
            innerService.do();
        }
}

@Transactional(propagation = Propagation.REQUIRED)
public Result innerService(Info info) {}

在controller同時標記@SysLog和@Transactional時候,由於事務註解預設 @EnableTransactionManagement(order=Ordered.LOWEST_PRECEDENCE) 優先順序最高,
故先執行事務aop再執行自定義註解aop(先進後出的⚪)

此時自定義註解代碼SysLogAspect#around與外層方法testService是同一個事務,around子事務方法#innerService會發現已經有一個事務,可選擇掛起或加入事務;


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 近期,數據中心系統負荷大,mysql伺服器的CPU動輒達到90%以上。代碼和數據表存在很大優化空間。 這裡分享一個定時任務批量處理數據的優化過程。 先介紹定時任務 先介紹下麵2張數據表 欄位 數據量 platform_order 平臺交易訂單表 有超過50多個欄位。 包括 主鍵自增id、客戶id、客 ...
  • 前言 嗨嘍~大家好呀,這裡是魔王吶 ! 國企文員和游戲陪玩兩個職業間,你會選擇哪個? 00後李明的答案是後者。 今年3月,某二本院校應屆畢業生李明,兜兜轉轉,沒有找到特別合心的工作 卻憑著還不錯的游戲技術,成為了全職的游戲陪玩。 “按單收費,大概一單大概兩三百元,按時長收費,一小時50到100元”, ...
  • 滿漢樓02 4.功能實現04 4.6顯示所有菜品 4.6.1思路分析 創建一個菜單表menu,在Domain層創建與菜單表對應的Javabean-Menu類,在DAO層創建MenuDAO,完成對menu表的增刪改查,在Service層創建一個和menu表相關的service類,service類提供給 ...
  • 在筆者之前的文章`《驅動開發:內核特征碼搜索函數封裝》`中我們封裝實現了特征碼定位功能,本章將繼續使用該功能,本次我們需要枚舉內核`LoadImage`映像回調,在Win64環境下我們可以設置一個`LoadImage`映像載入通告回調,當有新驅動或者DLL被載入時,回調函數就會被調用從而執行我們自己... ...
  • 集合 Scala的集合有三大類: 序列Seq、集Set、映射Map 所有的集合都擴展自Iterable特質。對於幾乎所有的集合類 Scala都同時提供了可變和不可變的版本 可變集合 可以在適當的地方被更新或擴展。這意味著你可以修改,添加,移除一個集合的元素。 不可變集合 永遠不會改變。不過,你仍然可 ...
  • LRU:最近最少使用緩存 LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換演算法,選擇最近最久未使用的頁面予以淘汰。該演算法賦予每個頁面一個訪問欄位,用來記錄一個頁面自上次被訪問以來所經歷的時間 t,當須淘汰一個頁面時,選擇現有頁面中其 t 值最大的,即最近最少 ...
  • 過濾組件 查詢所有才涉及到過濾,其他介面都不需要 restful規範中有一條,請求地址中帶過濾條件:分頁、排序、過濾統稱為過濾 內置過濾類 使用內置過濾類的步驟 必須是繼承GenericAPIView+ListModelMixin的之類視圖上 1.配置過濾類 filter_backends=[Sea ...
  • 兄弟們,為了幫助大家更加高效的摸魚,今天分享一個騷操作,Python自動安裝第三方庫! 為了體現小編在懶上的造就,今天再分享一個騷操作:Python自動安裝第三方庫,全自動不需要你動! pip手動安裝 一說Python要安裝哪個模塊,我們第一反應,win+r輸入cmd,pip instll 安裝~ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...