主頁 > 後端開發 > SpringBoot啟動流程原理+自動裝配原理

SpringBoot啟動流程原理+自動裝配原理

2021-02-18 13:43:48 後端開發

文章目錄

  • 前言
    • 1、@SpringBootApplication注解剖析
      • 1.1、@SpringBootConfiguration
      • 1.2、@EnableAutoConfiguration
        • 1.2.1@AutoConfigurationPackage
        • 1.2.2 @Import(AutoConfigurationImportSelector.class)
      • 1.3@ComponentScan
    • 2.SpringApplication.run(啟動類.class,args)方法剖析
      • 2.1實體化SpringApplication物件
      • 2.2 run(args):呼叫run方法
        • 2.2.1run(args)方法——第三步之創建Spring應用背景關系
        • 2.2.2run(args)方法——第四步之Spring應用背景關系前置處理
        • 2.2.3run(args)方法——第五步之重繪容器


前言

SpringBoot 設計的目的是為了讓你盡可能快的跑起來 Spring 應用程式并且盡可能減少你的組態檔,SpringBoot相對Spring的優點主要有兩個:
1.起步依賴-會將很多jar包按照功能合并成stater整體進行版本管理和參考,解決Spring集成其他框架時jar版本管理問題
2.自動裝配-引入相關的jar包后SpringBoot會自動注冊一些比較關鍵的bean,并進行默認配置,不用我們進行特殊配置,解決Spring重量級XML配置問題,比如整合Mybatis時的SqlSessionFactory

注:其中起步依賴主要是解決版本控制問題,主要設計在于POM檔案,這里主要探究第二優點自動裝配,

SpringBoot啟動依靠的是帶有main方法的啟動類,啟動類的內容可以分為兩個部分一個是啟動類上@SpringBootApplication這個注解;第二部分是main方法里的SpringApplication.run(啟動類.class,args)方法,下面主要就是分析一下這兩部分分別是什么作用?完成了什么功能?怎樣實作的自動裝配?以及SpringBoot的啟動流程分析,


1、@SpringBootApplication注解剖析

@SpringBootApplication是個組合注解包含四個元注解和@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan組成,下面逐個分析,
在這里插入圖片描述
在這里插入圖片描述

1.1、@SpringBootConfiguration

@SpringBootConfiguration也是一個組合注解由元注解和@Configuration構成,@Configuration是@Component的一個衍生注解主要作用是標記當前類是個配置類,
在這里插入圖片描述

1.2、@EnableAutoConfiguration

@EnableAutoConfiguration也是一個組合注解由元注解和@AutoConfigurationPackage、@Import注解構成,Spring中有很多Enable開頭的注解,其作用大都是借助@Import來收集并注冊特定場景相關的bean,@EnableAutoConfiguration的主要作用就是借助@Import來收集并注冊所有符合自動裝配條件的bean,
在這里插入圖片描述

1.2.1@AutoConfigurationPackage

注:很多人以為@SpringBootApplication可以掃描啟動類當前包及其子包下面的類是由此注解完成的,是錯誤的

@AutoConfigurationPackage由元注解和@Import注解組成
在這里插入圖片描述
@Import注解匯入了AutoConfigurationPackages.Registrar.class實作了ImportBeanDefinitionRegistrar介面會呼叫registerBeanDefinitions方法

在這里插入圖片描述

進入AutoConfigurationPackages#register,這里主要為Spring容器里注入了BasePackages的BeanDefinition目的是講啟動類的包路徑傳入容器,官網解釋在后面整合jpa時會用到,這里暫不做探究,

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	    // 如果已經存在該 BEAN ,則修改其包(package)屬性
		// BEAN 就是 AutoConfigurationPackages,用于存盤自動配置包以供稍后參考
		if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			// 將建構式的第一個引數設定為包名串列
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        // 如果不存在該 BEAN ,則創建一個 Bean ,并進行注冊
        } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			// 將beanClass設定為BasePackages
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			// 將建構式的第一個引數設定為包名串列,也就是BasePackages的建構式
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			// 注冊beanDefinition
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

1.2.2 @Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector類是SpringBoot實作自動裝配的關鍵,AutoConfigurationImportSelector實作了DeferredImportSelector介面會呼叫process和selectImports方法(在何處呼叫會在后面2.2.3講到),其中selectImports方法會回傳一個陣列,陣列中的類都會注冊到Spring容器中

AutoConfigurationImportSelector.AutoConfigurationGroup.class
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			// 斷言
		    Assert.state(
					deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
		    // 獲得 AutoConfigurationEntry 物件
		    // 核心方法:獲取并過濾全部自動裝配的類
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			// 添加到 autoConfigurationEntries 中
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			// 添加到 entries 中
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}
AutoConfigurationImportSelector.AutoConfigurationGroup.class
public Iterable<Entry> selectImports() {
		    // 如果為空,則回傳空陣列
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			// 獲得 allExclusions
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions)
					.flatMap(Collection::stream).collect(Collectors.toSet());
			// 獲得 processedConfigurations
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations)
					.flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			// 從 processedConfigurations 中,移除排除的
			processedConfigurations.removeAll(allExclusions);
			// 處理,回傳結果
			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()) // 排序
                        .stream()
                        .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) // 創建 Entry 物件
                        .collect(Collectors.toList()); // 轉換成 List
		}

由原始碼可以看到selectImports只是對process中封裝到autoConfigurationEntries的結果進行分組排序等處理后回傳,下面主要看到process中的getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);方法

AutoConfigurationImportSelector.class
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
	    // 1. 判斷是否開啟注解,如未開啟,回傳空串
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// 2. 獲得注解的屬性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);

		// 3. getCandidateConfigurations()用來獲取默認支持的自動配置類名串列
		// spring Boot在啟動的時候,使用內部工具類SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
		// 找出其中key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的屬性定義的工廠類名稱,
		// 將這些值作為自動配置類匯入到容器中,自動配置類就生效了
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);


		// 3.1 //去除重復的配置類,若我們自己寫的starter 可能存在重復的
		configurations = removeDuplicates(configurations);
		// 4. 如果專案中某些自動配置類,我們不希望其自動配置,我們可以通過EnableAutoConfiguration的exclude或excludeName屬性進行配置,
		// 或者也可以在組態檔里通過配置項“spring.autoconfigure.exclude”進行配置,
		//找到不希望自動配置的配置類(根據EnableAutoConfiguration注解的一個exclusions屬性)
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 4.1 校驗排除類(exclusions指定的類必須是自動配置類,否則拋出例外)
		checkExcludedClasses(configurations, exclusions);
		// 4.2 從 configurations 中,移除所有不希望自動配置的配置類
		configurations.removeAll(exclusions);

		// 5. 對所有候選的自動配置類進行篩選,根據專案pom.xml檔案中加入的依賴檔案篩選出最終符合當前專案運行環境對應的自動配置類

		//@ConditionalOnClass : 某個class位于類路徑上,才會實體化這個Bean,
		//@ConditionalOnMissingClass : classpath中不存在該類時起效
		//@ConditionalOnBean : DI容器中存在該型別Bean時起效
		//@ConditionalOnMissingBean : DI容器中不存在該型別Bean時起效
		//@ConditionalOnSingleCandidate : DI容器中該型別Bean只有一個或@Primary的只有一個時起效
		//@ConditionalOnExpression : SpEL運算式結果為true時
		//@ConditionalOnProperty : 引數設定或者值一致時起效
		//@ConditionalOnResource : 指定的檔案存在時起效
		//@ConditionalOnJndi : 指定的JNDI存在時起效
		//@ConditionalOnJava : 指定的Java版本存在時起效
		//@ConditionalOnWebApplication : Web應用環境下起效
		//@ConditionalOnNotWebApplication : 非Web應用環境下起效
		
		//要判斷@Conditional是否滿足
		// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在類路徑中存在SqlSessionFactory.class、SqlSessionFactoryBean.class這兩個類才能完成自動注冊,
		configurations = filter(configurations, autoConfigurationMetadata);


		// 6. 將自動配置匯入事件通知監聽器
		//當AutoConfigurationImportSelector過濾完成后會自動加載類路徑下Jar包中META-INF/spring.factories檔案中 AutoConfigurationImportListener的實作類,
		// 并觸發fireAutoConfigurationImportEvents事件,
		fireAutoConfigurationImportEvents(configurations, exclusions);
		// 7. 創建 AutoConfigurationEntry 物件
		return new AutoConfigurationEntry(configurations, exclusions);
	}

1.3@ComponentScan

這個注解才是@SpringBootApplication會默認掃描啟動類所在包以及子包路徑下全部類的原因

2.SpringApplication.run(啟動類.class,args)方法剖析

SpringApplication#run主要完成的事件可以分成兩部分1.實體化SpringApplication物件2. run(args):呼叫run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	   //SpringApplication的啟動由兩部分組成:
		//1. 實體化SpringApplication物件
		//2. run(args):呼叫run方法
		return new SpringApplication(primarySources).run(args);
	}

在這里插入圖片描述

2.1實體化SpringApplication物件

在實體化SpringApplication中設定的初始化器和監聽器都是在/META-INF/spring.factories 中獲取的

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

		this.sources = new LinkedHashSet();
		this.bannerMode = Mode.CONSOLE;
		this.logStartupInfo = true;
		this.addCommandLineProperties = true;
		this.addConversionService = true;
		this.headless = true;
		this.registerShutdownHook = true;
		this.additionalProfiles = new HashSet();
		this.isCustomEnvironment = false;
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");

		//專案啟動類 SpringbootDemoApplication.class設定為屬性存盤起來
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

		//設定應用型別是SERVLET應用(Spring 5之前的傳統MVC應用)還是REACTIVE應用(Spring 5開始出現的WebFlux互動式應用)
		this.webApplicationType = WebApplicationType.deduceFromClasspath();

		// 設定初始化器(Initializer),最后會呼叫這些初始化器
		//所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實作類,在Spring背景關系被重繪之前進行初始化的操作
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

		// 設定監聽器(Listener)
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

		// 初始化 mainApplicationClass 屬性:用于推斷并設定專案main()方法啟動的主程式啟動類
		this.mainApplicationClass = deduceMainApplicationClass();
	}

2.2 run(args):呼叫run方法

這里一個分為九步,最核心的是3、4、5下面會逐一介紹:

  1. 獲取并啟動監聽器,監聽器也是在spring.factories中獲取的,
  2. 專案運行環境Environment的預配置
  3. 創建Spring容器
  4. Spring容器前置處理,這一步主要是在容器重繪之前的準備動作,包含一個非常關鍵的操作:將啟動類注入容器,為后續開啟自動化配置奠定基礎,
  5. 重繪容器
  6. Spring容器后置處理,擴展介面,設計模式中的模板方法,默認為空實作,
  7. 向監聽器發出結束執行的事件通知
  8. 執行Runners
  9. 向監聽器發布應用背景關系就緒事件
public ConfigurableApplicationContext run(String... args) {
	    // 創建 StopWatch 物件,并啟動,StopWatch 主要用于簡單統計 run 啟動程序的時長,
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		// 初始化應用背景關系和例外報告集合
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 配置 headless 屬性
		configureHeadlessProperty();


		//   (1)獲取并啟動監聽器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
		    // 創建  ApplicationArguments 物件 初始化默認應用引數類
			// args是啟動Spring應用的命令列引數,該引數可以在Spring應用中被訪問,如:--server.port=9000
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

			//(2)專案運行環境Environment的預配置
			// 創建并配置當前SpringBoot應用將要使用的Environment
			// 并遍歷呼叫所有的SpringApplicationRunListener的environmentPrepared()方法
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

			configureIgnoreBeanInfo(environment);
			// 準備Banner列印器 - 就是啟動Spring Boot的時候列印在console上的ASCII藝術字體
			Banner printedBanner = printBanner(environment);

			// (3)創建Spring容器
			context = createApplicationContext();
			// 獲得例外報告器 SpringBootExceptionReporter 陣列
			//這一步的邏輯和實體化初始化器和監聽器的一樣,
			// 都是通過呼叫 getSpringFactoriesInstances 方法來獲取配置的例外類名稱并實體化所有的例外處理類,
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);


			// (4)Spring容器前置處理
			//這一步主要是在容器重繪之前的準備動作,包含一個非常關鍵的操作:將啟動類注入容器,為后續開啟自動化配置奠定基礎,
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);

			// (5):重繪容器
			refreshContext(context);

			// (6):Spring容器后置處理
			//擴展介面,設計模式中的模板方法,默認為空實作,
			// 如果有自定義需求,可以重寫該方法,比如列印一些啟動結束log,或者一些其它后置處理
			afterRefresh(context, applicationArguments);
			// 停止 StopWatch 統計時長
			stopWatch.stop();
			// 列印 Spring Boot 啟動的時長日志,
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			// (7)發出結束執行的事件通知
			listeners.started(context);

			// (8):執行Runners
			//用于呼叫專案中自定義的執行器XxxRunner類,使得在專案啟動完成后立即執行一些特定程式
			//Runner 運行器用于在服務啟動時進行一些業務初始化操作,這些操作只在服務啟動后執行一次,
			//Spring Boot提供了ApplicationRunner和CommandLineRunner兩種服務介面
			callRunners(context, applicationArguments);
		} catch (Throwable ex) {
		    // 如果發生例外,則進行處理,并拋出 IllegalStateException 例外
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

        //   (9)發布應用背景關系就緒事件
		//表示在前面一切初始化啟動都沒有問題的情況下,使用運行監聽器SpringApplicationRunListener持續運行配置好的應用背景關系ApplicationContext,
		// 這樣整個Spring Boot專案就正式啟動完成了,
		try {
			listeners.running(context);
		} catch (Throwable ex) {
            // 如果發生例外,則進行處理,并拋出 IllegalStateException 例外
            handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		 //回傳容器
		return context;
	}

2.2.1run(args)方法——第三步之創建Spring應用背景關系

這里根據實體SpringApplication時獲取的應用型別來創建不同的應用背景關系物件

SpringApplication.class
protected ConfigurableApplicationContext createApplicationContext() {
	    // 根據 webApplicationType 型別,獲得 ApplicationContext 型別
		// 這里創建容器的型別 還是根據webApplicationType進行判斷的,
		// 該型別為SERVLET型別,所以會通過反射裝載對應的位元組碼,
		// 也就是AnnotationConfigServletWebServerApplicationContext

		// 先判斷有沒有指定的實作類
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {

				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			} catch (ClassNotFoundException ex) {
				throw new IllegalStateException("Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex);
			}
		}
		// 創建 ApplicationContext 物件
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

2.2.2run(args)方法——第四步之Spring應用背景關系前置處理

這塊會對整個背景關系進行一個預處理,比如觸發監聽器的回應事件、加載資源、設定背景關系環境等等

SpringApplication.class
private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		//設定容器環境,包括各種變數
	    context.setEnvironment(environment);

		//設定背景關系的 bean 生成器和資源加載器
		postProcessApplicationContext(context);

		//執行容器中的ApplicationContextInitializer(包括 spring.factories和自定義的實體)
		applyInitializers(context);

		//觸發所有 SpringApplicationRunListener 監聽器的 contextPrepared 事件方法
		listeners.contextPrepared(context);

		//記錄啟動日志
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		//注冊啟動引數bean,這里將容器指定的引數封裝成bean,注入容器
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		// Load the sources
		// 加載所有資源
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		//加載我們的啟動類,將啟動類注入容器,為后續開啟自動化配置奠定基礎
		load(context, sources.toArray(new Object[0]));

		//觸發所有 SpringApplicationRunListener 監聽器的 contextLoaded 事件方法
        listeners.contextLoaded(context);

		
	}

在前置處理中最核心的一步是加載我們的啟動類,將啟動類注入容器,為后續開啟自動化配置奠定基礎load(context, sources.toArray(new Object[0]));

BeanDefinitionLoader.class
private int load(Object source) {
		Assert.notNull(source, "Source must not be null");
		// 如果是 Class 型別,則使用 AnnotatedBeanDefinitionReader 執行加載
		if (source instanceof Class<?>) {
			return load((Class<?>) source);
		}
        // 如果是 Resource 型別,則使用 XmlBeanDefinitionReader 執行加載
        if (source instanceof Resource) {
			return load((Resource) source);
		}
        // 如果是 Package 型別,則使用 ClassPathBeanDefinitionScanner 執行加載
        if (source instanceof Package) {
			return load((Package) source);
		}
        // 如果是 CharSequence 型別,則各種嘗試去加載
		if (source instanceof CharSequence) {
			return load((CharSequence) source);
		}
		// 無法處理的型別,拋出 IllegalArgumentException 例外
		throw new IllegalArgumentException("Invalid source type " + source.getClass());
	}

2.2.3run(args)方法——第五步之重繪容器

這里重繪容器最終呼叫的是AbstractApplication#refresh方法

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			// 第一步 重繪前的預處理
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			// 第二步 1.創建BeanFactory實體,默認實作是DefaultListableBeanFactory
			//       2.決議XML中的<bean>為BeanDefition 并注冊到 BeanDefitionRegistry
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			// 第三步 BeanFactory的預準備?作(BeanFactory進??些設定,?如context的類加載器等)
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				// 第四步 BeanFactory準備作業完成后的后置處理作業,鉤子方法,等子類重寫
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				// 第五步 實體化并調?實作了BeanFactoryPostProcessor接?的Bean
				// 提前初始化工廠后置處理器bean,并呼叫postProcessBeanFactory方法
				//其中BeanFactoryPostProcessor比較重要的一個ConfigurationClassPostProcessor在這里呼叫,
				//用來遍歷BeanDefinitionRegistry中現有的BeanDefinition決議@Import、@Configuration
				// 、@ComponentScan等注解將注解覆寫到的類也注冊到BeanDefinitionRegistry中
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// 第六步 注冊BeanPostProcessor(Bean的后置處理器),在創建bean的前后等執
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				// 第七步 初始化MessageSource組件(做國際化功能;訊息系結,訊息決議);
				initMessageSource();

				// Initialize event multicaster for this context.
				// 第八步 初始化事件派發器
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				// 第九步 ?類重寫這個?法,在容器重繪的時候可以?定義邏輯,鉤子方法
				onRefresh();

				// Check for listener beans and register them.
				// 第十步 注冊應?的監聽器,就是注冊實作了ApplicationListener接?的監聽器bean
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 第十一步 初始化所有剩下的?懶加載的單例bean
				//1).初始化創建?懶加載?式的單例Bean實體(未設定屬性)
				//2).填充屬性
				//3) .如果bean實作了Aware相關介面,則呼叫Aware介面的實作方法
				//4) .呼叫BeanPostProcessor處理器的前置方法
				//5).初始化?法調?(?如調?afterPropertiesSet?法、init-method?法)
				//6).調?BeanPostProcessor(后置處理器)對實體bean進?后置處
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				// 第十二步 完成context的重繪,主要是調?LifecycleProcessor的onRefresh()?法,并且發布事件 (ContextRefreshedEvent)
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

這也是Spring容器啟動的經典方法這里就不每個步驟逐一過了,只重點關注和SpringBoot自動裝配相關的步驟——第五步 實體化并調?實作了BeanFactoryPostProcessor接?的Bean,就是在這一步決議的@SpringBootApplication這個組合注解,BeanFactoryPostProcessor比較重要的一個ConfigurationClassPostProcessor在這里呼叫,用來遍歷BeanDefinitionRegistry中現有的BeanDefinition決議@Import、@Configuration 、@ComponentScan等注解將注解覆寫到的類也注冊到BeanDefinitionRegistry中,

a.進入ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

ConfigurationClassPostProcessor.class
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);
		// 核心方法
		processConfigBeanDefinitions(registry);
	}

b.進入ConfigurationClassPostProcessor#processConfigBeanDefinitions,這里遍歷BeanDefinitionRegistry現有的全部類不包含@Configuration的類不會進行決議,這也是為什么配置類需要加@Configuration的原因

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		//如果不存在@Configuration直接return
		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// 核心決議方法
			parser.parse(candidates);
			parser.validate();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}

		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
			// Clear cache in externally provided MetadataReaderFactory; this is a no-op
			// for a shared cache since it'll be cleared by the ApplicationContext.
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
	}

c.進入ConfigurationClassParser#parse

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					// 注解決議BeanDefinition核心方法
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}

		this.deferredImportSelectorHandler.process();
	}

d.進入ConfigurationClassParser#processConfigurationClass

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}

		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do {
			// 決議核心方法
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	}

e.進入ConfigurationClassParser#doProcessConfigurationClass,在這里決議@PropertySource、@ComponentScan、@Import、@Bean、@ImportResource等注解,并將其覆寫的資源或類加載到容器背景關系中,每個注解的具體決議細節這里就不深探討了,主要梳理流程

ConfigurationClassParser.class
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass);
		}
		//決議@PropertySource注解
		// Process any @PropertySource annotations
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}
		// 決議@ComponentScan注解
		// Process any @ComponentScan annotations
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}
		// 決議@Import注解
		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), true);
		// 決議@ImportResource注解
		// Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}
		// 決議@Bean注解
		// Process individual @Bean methods
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

f.此處簡單過一下@Import注解的決議程序,驗證一下1.2.2 @Import(AutoConfigurationImportSelector.class)中process和selectImports方法的呼叫進入ConfigurationClassParser#processImports

在這里插入圖片描述

在進入ConfigurationClassParser#handle

public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
			DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
					configClass, importSelector);
			if (this.deferredImportSelectors == null) {
				DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
				handler.register(holder);
				// 核心方法
				handler.processGroupImports();
			}
			else {
				this.deferredImportSelectors.add(holder);
			}
			
		}

進入ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports

public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				//getImports()中呼叫了AutoConfigurationImportSelector.AutoConfigurationGroup.class中的process和selectImports
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(
							entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}

進入ConfigurationClassParser.DeferredImportSelectorGrouping#getImports方法此處呼叫了AutoConfigurationImportSelector.AutoConfigurationGroup的process和selectImports

在這里插入圖片描述

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

標籤:java

上一篇:JAVA-基礎-Map和Set

下一篇:網路爬蟲爬取b站勵志彈幕并生成詞云(精心筆記總結)

標籤雲
其他(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