主頁 > 後端開發 > 原始碼框架-Spring-思維導圖

原始碼框架-Spring-思維導圖

2022-10-21 07:50:45 後端開發

目錄
  • SpringIOC原始碼
    • IOC容器加載程序及Bean生命周期
      • BeanFactory和ApplicationContext的區別
      • Spring IOC容器的具體加載程序
      • 簡述Bean的生命周期
      • 后置處理器的九次呼叫
      • BeanDefinition
    • 內置后置PostProcess處理器
      • BeanFactoryPostProcessor的呼叫程序/配置類的決議程序
      • 配置類@Configuration加與不加的區別
      • 重復beanName覆寫原則
    • 回圈依賴
      • 如何解決回圈依賴/為什么要有二級快取和三級快取
      • Spring三級快取解決setter方式的回圈依賴原理
      • BeanCurrentlyInCreationException
    • 監聽器Listener
      • Spring事件監聽器的原理
      • Spring是怎樣避免讀取到不完整的Bean
    • 推斷構造方法底層原理
    • SpringAOP底層原理
  • Spring事務
    • Spring事務的7種傳播行為
      • 1、PROPAGATION_REQUIRED
      • 2、PROPAGATION_SUPPORTS
      • 3、PROPAGATION_MANDATORY
      • 4、PROPAGATION_REQUIRES_NEW
      • 5、PROPAGATION_NOT_SUPPORTED
      • 6、PROPAGATION_NEVER
      • 7、PROPAGATION_NESTED
      • 8、總結
    • 用法
    • Spring事務不生效
    • Transaction rolled back because it has been marked as rollback-only
    • 切面類內的事務
    • 自定義AOP與宣告式事務執行順序問題

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 = https://www.cnblogs.com/sudokill/p/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會發現已經有一個事務,可選擇掛起或加入事務;

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/518701.html

標籤:Java

上一篇:Resilience4J通過yml設定circuitBreaker

下一篇:LRU演算法簡單實作

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more