springboot--springboot的启动整体过程|自动配置类解析注册过程|spring5源码解析(代码片段)

做猪呢,最重要的是开森啦 做猪呢,最重要的是开森啦     2022-12-21     584

关键词:

基础环境:  spring-boot :2.3.3.RELEASE、jdk1.8

1. SpringBoot启动类注解@SpringBootApplication:

 标注@SpringBootApplication的类就是SpringBoot的主配置类,启动该类来启动应用,这是一个组合注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters =  @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) )
public @interface SpringBootApplication 

 主要关注自动配置类注解@EnableAutoConfiguration,这也是个组合注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 

 这里主要关注@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)
`
对于@AutoConfigurationPackage,也是一个组合注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)

 至此,可以看见整个@SpringBootApplication会引入两个@Import,要留意下AutoConfigurationImportSelector.class

2. SpringBoot的启动:

启动主配置类即可启动SpringBoot项目,启动过程是怎样的,下面来看看,先进入启动类的run方法,会调用到此处:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) 
	return new SpringApplication(primarySources).run(args);

 在构造函数中会进行一些参数赋值,值得注意的是,此时开始预解析META-INF/spring.factories文件的配置类名

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) 
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	// web应用类型,通常默认SERVLET
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	// 设置用于容器初始化的ApplicationContextInitializer
	// 初次调用时,getSpringFactoriesInstances会进行META-INF/spring.factories文件的配置类名的解析
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	// 同理是设置用于容器初始化的ApplicationListener
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();

 2.1. getSpringFactoriesInstances:

 该方法会调用SpringFactoriesLoader.loadFactoryNames方法进行预解析配置类名

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) 
	// 会获取默认类加载器,Launcher类型
	ClassLoader classLoader = getClassLoader();
	// 获取类型为type的配置类名,本次为获取ApplicationContextInitializer类型的配置类名
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	// 通过默认构造方法创建对应配置类名的实例
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	// 对实例进行调用排序
	AnnotationAwareOrderComparator.sort(instances);
	return instances;

 2.1.1. SpringFactoriesLoader.loadFactoryNames:

 该方法会调用loadSpringFactories来解析META-INF/spring.factories文件的配置类名

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) 
	// 参数传递过来的类的完全限定名,即ApplicationContextInitializer类的完全限定名
	String factoryTypeName = factoryType.getName();
	// loadSpringFactories方法来解析配置类名;getOrDefault获取key为factoryTypeName的配置类名
	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());


private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) 
	// 从缓存cache(key:类加载器;value: result)中获取默认类加载器对应的value
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) 
		return result;
	
	// 初次启动调用,缓存获取不到值,开始解析META-INF/spring.factories文件的配置类名
	try 
		// 类加载器不为null,取所有类路径下的META-INF/spring.factories
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		result = new LinkedMultiValueMap<>();
		// 循环遍历有META-INF/spring.factories文件的jar包,将里面的键值对封装到result,结构相当于 Map<Stirng,LinkedList>
		while (urls.hasMoreElements()) 
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) 
				String factoryTypeName = ((String) entry.getKey()).trim();
				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) 
					result.add(factoryTypeName, factoryImplementationName.trim());
				
			
		
		// 写入缓存,方便后续调用
		cache.put(classLoader, result);
		return result;
	
	catch (IOException ex) 
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	

 像自动配置类名就是从spring-boot-autoconfigure这个jar包META-INF/spring.factories文件解析出来

 2.2. SpringApplication#run:

public ConfigurableApplicationContext run(String... args) 
	... ... ...
	try 
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		// 上面设置一些环境吧应该,没怎么关注
		configureIgnoreBeanInfo(environment);
		// 这里控制台会打印输出那个Spring启动图
		Banner printedBanner = printBanner(environment);
		// 创建ApplicationContext即ioc容器
		context = createApplicationContext();
		// 像上面2.1那样,会从缓存中获取SpringBootExceptionReporter类型实例
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[]  ConfigurableApplicationContext.class , context);
		// 对context进行一些预处理,主要是一些赋值
		// 这里会将主配置类封装成BeanDefinition再注册到ApplicationContext中,详情可参考文末参考链接1
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		// 刷新context容器,重点方法
		refreshContext(context);
	... ...

 2.3. AbstractApplicationContext#refresh:

 上述refreshContext方法会刷新上下文,即context容器,方法最后会调用refresh方法:

public void refresh() throws BeansException, IllegalStateException 
    synchronized (this.startupShutdownMonitor) 
        // 刷新前准备,初始化properties等
        prepareRefresh();

        // 获取ApplicationContext中组合的BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 主要给beanFactory赋值,比如类加载器,ApplicationContextAwareProcessor后置处理器等等
        prepareBeanFactory(beanFactory);

        try 
            // 允许在上下文的子类中对bean factory进行后处理
            postProcessBeanFactory(beanFactory);

            // 调用Bean工厂的后置处理器,重点方法
            invokeBeanFactoryPostProcessors(beanFactory);

            // 注册Bean的后置处理器
            registerBeanPostProcessors(beanFactory);

            // 初始化消息源
            initMessageSource();

            // 初始化事件广播
            initApplicationEventMulticaster();

            // 初始化特殊的Bean,比如tomcat
            onRefresh();

            // 注册监听器
            registerListeners();

            // 实例化所有的(non-lazy-init)单例Bean
            finishBeanFactoryInitialization(beanFactory);

            // 发布刷新完毕事件
            finishRefresh();
        
        ... ... ... 

 2.4. invokeBeanFactoryPostProcessors:

 通过委托模式调用PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors方法

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) 
	PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
	... ... ...

 2.4.1. invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

  • 这个方法会实例化和调用所有postProcessBeanDefinitionRegistry
  • 还有其父类BeanFactoryPostProcessor的postProcessBeanFactory方法
  • 注意:BeanDefinitionRegistryPostProcessor优先于BeanFactoryPostProcessor执行,BeanFactoryPostProcessor优先于bean实例化执行;
public static void invokeBeanFactoryPostProcessors(
		ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) 

	Set<String> processedBeans = new HashSet<>();
	// ioc容器创建时beanFactory为DefaultListableBeanFactory
	// DefaultListableBeanFactory实现了BeanDefinitionRegistry接口,所以true
	if (beanFactory instanceof BeanDefinitionRegistry) 
		BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
		List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
		List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

		// 循环已经注册过的beanFactoryPostProcessors
		for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) 
			// 如果是子类BeanDefinitionRegistryPostProcessor,就执行postProcessBeanDefinitionRegistry方法
			if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) 
				BeanDefinitionRegistryPostProcessor registryProcessor =
						(BeanDefinitionRegistryPostProcessor) postProcessor;
				registryProcessor.postProcessBeanDefinitionRegistry(registry);
				registryProcessors.add(registryProcessor);
			
			else 
				regularPostProcessors.add(postProcessor);
			
		

		// 用于保存本初要执行的BeanDefinitionRegistryPostProcessor
		List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

		// 找出所有实现BeanDefinitionRegistryPostProcessor接口的Bean的beanName
		String[] postProcessorNames =
				beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
		for (String ppName : postProcessorNames) 
			// 判断这个ppName的bean是否实现了PriorityOrdered
			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) 
				// 满足条件会通过getBean获取对应ppName的实例,添加到currentRegistryProcessors
				currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
				// 将ppName添加到processedBeans,表名这个ppName的bean调用过postProcessBeanDefinitionRegistry方法
				processedBeans.add(ppName);
			
		
		sortPostProcessors(currentRegistryProcessors, beanFactory);
		registryProcessors.addAll(currentRegistryProcessors);
		// 调用postProcessBeanDefinitionRegistry方法,重点方法		
		invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
		currentRegistryProcessors.clear();
		... ... ...
		// 下面省略了两次查找postProcessorNames及调用postProcessBeanDefinitionRegistry方法的逻辑,与上面类似
		// 也省略对父类BeanFactoryPostProcessor查找和调用的逻辑,也类似
		// 省略的内容可以见文末参考链接2

  • postProcessorNames查找其实是从beanDefinitionNames中查找符合类型的beanName
  • 容器context创建后,默认的BeanFactory有五个内部托管的beanName,在prepareContext会将主配置类的beanName添加进去
  • 这里实现BeanDefinitionRegistryPostProcessor的只有internalConfigurationAnnotationProcessor

 这个beanName在getBean时会获取ConfigurationClassPostProcessor对象

 2.4.2. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

 本次会调用ConfigurationClassPostProcessor的currentRegistryProcessors方法

private static void invokeBeanDefinitionRegistryPostProcessors(
		Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) 
	// 循环遍历传过来的postProcessors,调用postProcessBeanDefinitionRegistry方法
	for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) 
		postProcessor.postProcessBeanDefinitionRegistry(registry);
	

 会通过registryId去重,重复则抛异常,反则调用processConfigBeanDefinitions

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);

 2.4.3. processConfigBeanDefinitions(registry);

 方法入参registry就是BeanFactory,就是默认的DefaultListableBeanFactory

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

	// 循环遍历candidateNames,也就是beanDefinitionNames,检查出是配置类的beanName
	for (String beanName : candidateNames) 
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) 
			if (logger.isDebugEnabled()) 
				logger.de

springboot:springboot启动原理分析(代码片段)

文章目录SpringBoot启动原理分析一、依赖导入原理二、SpringBoot包扫描原理 三、SpringBoot自动配置原理SpringBoot启动原理分析一、依赖导入原理父项目版本控制ctrl+点击spring-boot-starter-parent进入继续点击,进入spring-boot-dependencie... 查看详情

springboot--springboot的启动整体过程|自动配置类解析注册过程|spring5源码解析(代码片段)

1.SpringBoot启动类注解@SpringBootApplication:2.SpringBoot的启动: 2.1.getSpringFactoriesInstances: 2.1.1.SpringFactoriesLoader.loadFactoryNames: 2.2.SpringApplication#run: 查看详情

springboot--springboot的启动整体过程|自动配置类解析注册过程|spring5源码解析(代码片段)

1.SpringBoot启动类注解@SpringBootApplication:2.SpringBoot的启动: 2.1.getSpringFactoriesInstances: 2.1.1.SpringFactoriesLoader.loadFactoryNames: 2.2.SpringApplication#run: 查看详情

springboot|springboot启动错误

ErrorstartingApplicationContext.Todisplaytheconditionsreportre-runyourapplicationwith‘debug‘enabled.   原因:  springboot启动时会自动注入数据源和配置jpa 解决:  在@SpringBootApplication中排除其注入  exclude= 查看详情

学习springboot

什么是springboot?springboot是一款快速开发的框架,能够快速整合第三方框架,全部采用注解的方式,内置tomcat容器,springboot的web组件默认集成的是springMVC框架。优点:1.省去了各种繁琐的配置,开箱即用。2.可以打jar包直接使用ja... 查看详情

springboot#springboot项目启动时,打印端口号项目名访问地址(代码片段)

...应的信息打印在控制台解决方案:使用Environment类@SpringBootApplicationpublicclassApplicationpublicstaticvoi 查看详情

springboot

什么是SpringBoot?  SpringBoot是一个框架,一种全新的编程规范,他的产生简化了框架的使用,所谓简化是指简化了Spring众多框架中所需的大量且繁琐的配置文件,所以SpringBoot是一个服务于框架的框架,服务范围是简化配置文件... 查看详情

java面试题springboot启动原理(代码片段)

SpringBoot启动原理1、什么是SpringBoot2、SpringBoot启动原理3、三个关键注解解析3.1、@SpringBootConfiguration注解解析3.2、@EnableAutoConfiguration注解解析3.3、SpringFactoriesLoader(幕后英雄)3.4、@ComponentScan注解解析1、 查看详情

Spring Boot + Spring Boot 安全启动错误

】SpringBoot+SpringBoot安全启动错误【英文标题】:Springboot+springbootsecuritystarterror【发布时间】:2020-08-2504:49:00【问题描述】:我正在使用springboot和springsecurity和jsp做一个MVC项目。我只是在训练我的弹簧,我有同样的项目在没有弹簧... 查看详情

springboot:springboot的底层运行原理解析(代码片段)

...器spring-boot-starter2.主启动类的注解1.默认的主启动类2.@SpringBootApplication3.@ComponentScan4.@SpringBootConfiguration5.SpringBootApplication注解6.spring.factories7.结论8.简单图解3.主启动类的方法1.SpringApplication2.run方法流程分析1.pom.xml1.父依赖... 查看详情

springboot入门基础

SpringBoot入门(一)HelloWorld一什么是springboot  springboot是一个全新的框架,它设计的目的简化spring项目的初始环境的搭建和开发,主要有以下几个特点:  1、简化初始配置,可与主流框架集成;  2、内置Servlet容器,无需在打... 查看详情

springboot#springboot项目启动时,打印端口号项目名访问地址(代码片段)

...应的信息打印在控制台解决方案:使用Environment类@SpringBootApplicationpublicclassApplicationpublicstaticvoidmain(String[]args)ConfigurableApplicationContextconfigurableApplicationContext=SpringApplication.run(Application.class,args);Environmentenvironment=configurab... 查看详情

springboot返回xml数据,一分钟搞定!

SpringBoot返回XML数据,前提必须已经搭建了SpringBoot项目,所以这一块代码就不贴了,可以点击查看之前分享的SpringBoot返回JSON数据,一分钟搞定!。你所需具备的基础什么是SpringBoot?SpringBoot核心配置文件详解SpringBoot开启的2种方式... 查看详情

springboot实现一个监听用户请求的拦截器

...个拦截器来监听,并继续相应的日志记录 项目构建与SpringBoot,SpringBoot实现一个拦截器很容易。SpringBoot的核心启动类继承WebMvcConfigurerAdapter//增加拦截器@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){ 查看详情

springboot——springboot集成thymeleaf(代码片段)

文章目录:1.认识Thymeleaf2.详细步骤2.1创建一个SpringBoot项目2.2在pom.xml文件中会自动添加SpringBoot集成Thymeleaf的起步依赖2.3在核心配置文件中添加以下内容2.4写一个Controller控制层2.5写一个html页面2.6启动测试1.认识Thymeleaf Thymeleaf... 查看详情

微服务之springboot面试题(代码片段)

1,什么是SpringBoot?SpringBoot是Spring开源组织下的子项目,是Spring组件一站式解决方案,主要是简化了使用Spring的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。2,为什么要用SpringBoot?SpringBoot优点非常多,如... 查看详情

微服务之springboot面试题(代码片段)

1,什么是SpringBoot?SpringBoot是Spring开源组织下的子项目,是Spring组件一站式解决方案,主要是简化了使用Spring的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。2,为什么要用SpringBoot?SpringBoot优点非常多,如... 查看详情

springboot入门基础

1.什么是springboot? springboot是一个快速开发框架,快速整合第三方依赖 (原理:maven父子工程的方式),简化xml的配置,采用注解形式, 内置HTTP服务器(Jetty、Tomcat)最终以java应用程序执行。2.核心原理: <1>基于springMVC无... 查看详情