计算机系统应用教程网站

网站首页 > 技术文章 正文

阅读代码深入原理6——Spring Boot启动解密

btikc 2024-09-16 13:03:31 技术文章 20 ℃ 0 评论
  • 类加载器

我们在IDE运行和jar/war运行方式是有区别的。

以jar包来说,我们的spring-boot应用由于引入了spring-boot-maven-plugin,在maven生命周期的package阶段,会执行插件的repackage,将原先的不含依赖的jar包重命名成".jar.original"形式,生成一个新的jar包,大致目录如下:

application.jar

├───BOOT-INF

│├───classes# application.jar.original内容

│├───lib# 依赖的jar包

│├───classpath.idx# lib下jar包名称

│└───layers.idx# 整个jar顶层目录描述

├───META-INF

│├───maven# 常规maven描述信息

│└───MANIFEST.MF# jar包元信息描述文件

└───org# main类及启动我们应用代码所必须的类加载器


通过打完的jar包MANIFEST.MF文件,我们通过Main-Class属性可以得知默认的main类是“org.springframework.boot.loader.JarLauncher”,而我们也能找到一个特殊的“Start-Class”属性,它就是我们应用main方法所在的类名。

JarLauncher创建ClassLoader来加载jar包下的“BOOT-INF/lib”和“BOOT-INF/classes”文件,通过MANIFEST.MF文件的“Start-Class”属性,找到类,然后运行main方法。

如果我们在自己的启动类里打印类加载器,我们可以看到是“org.springframework.boot.loader.LaunchedURLClassLoader”。

而如果是IDE运行,打印的类加载器则是常见的“sun.misc.Launcher$AppClassLoader”。

  • 启动概览

通常,我们代码使用SpringApplication.run形式来“启动”spring boot应用。跟踪代码“栈”如下:

org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String[])
	org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])
		org.springframework.boot.SpringApplication#run(java.lang.String...)##此步前创建SpringApplication,会读取META-INF/spring.factories文件,获取ApplicationContextInitializer和ApplicationListener
			org.springframework.boot.SpringApplication#getRunListeners##读取META-INF/spring.factories,初始化SpringApplicationRunListener
			org.springframework.boot.SpringApplicationRunListener#starting##分发ApplicationStartingEvent事件
			org.springframework.boot.SpringApplication#prepareEnvironment##创建并配置Environment,分发ApplicationEnvironmentPreparedEvent。比如ConfigFileApplicationListener,会读取META-INF/spring.factories,获取EnvironmentPostProcessor,加载配置;比如LoggingApplicationListener会初始化日志。
			org.springframework.boot.SpringApplication#createApplicationContext##创建ConfigurableApplicationContext,视servlet、reactive存在情况而类型不同
			org.springframework.boot.SpringApplication#prepareContext##使用ApplicationContextInitializer初始化,将SpringApplication.run时的第一个参数所使用的类注册,分发ApplicationPreparedEvent
			org.springframework.boot.SpringApplication#refreshContext##参考spring AbstractApplicationContext.refresh
			org.springframework.boot.SpringApplicationRunListeners#started##分发ApplicationStartedEvent事件
			org.springframework.boot.SpringApplication#callRunners##获取注册到beanFactory的ApplicationRunner、CommandLineRunner,调用他们的run方法
			org.springframework.boot.SpringApplicationRunListeners#running##分发ApplicationReadyEvent事件

在prepareContext里有创建BeanDefinitionReader,将我们的启动类注册到BeanFactory。

假设使用的传统形式的servlet形式,则ConfigurableApplicationContext对应的是AnnotationConfigServletWebServerApplicationContext。

之前Spring的代码已经分析过AbstracApplicationContext的刷新过程,AnnotationConfigServletWebServerApplicationContext覆写了postProcessBeanFactory,代码如下:


# spring-boot-2.1.0.jar!/org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
	public AnnotationConfigServletWebServerApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
	
	@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		super.postProcessBeanFactory(beanFactory);
		if (this.basePackages != null && this.basePackages.length > 0) {// 1. 默认未设置
			this.scanner.scan(this.basePackages);
		}
		if (!this.annotatedClasses.isEmpty()) { // 2. 默认为空
			this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
		}
	}

看来bean的注册并没有依靠AnnotationConfigServletWebServerApplicationContext的postProcessBeanFactory来做的。

但根据我在spring源码分析里说过的Import注解如何处理,我们可以在bean创建过程,条件断点,看下ConfigurationClassPostProcessor何时被注册到BeanFactory。

熟练的话,会发现这个过程是在创建AnnotationConfigServletWebServerApplicationContext对象时,在构造函数内创建了AnnotatedBeanDefinitionReader,而在AnnotatedBeanDefinitionReader的构造函数内将ConfigurationClassPostProcessor注册到BeanFactory!

# spring-context-*.jar!/org.springframework.context.annotation.AnnotatedBeanDefinitionReader
    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this(registry, getOrCreateEnvironment(registry));
    }
    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        this.beanNameGenerator = new AnnotationBeanNameGenerator();
        this.scopeMetadataResolver = new AnnotationScopeMetadataResolver();
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        Assert.notNull(environment, "Environment must not be null");
        this.registry = registry;
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, (ResourceLoader)null);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

在spring源代码分析中,我已经贴过AnnotationConfigUtils.registerAnnotationConfigProcessors处理逻辑,此处会注册ConfigurationClassPostProcessor。

由于ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor,在AbstractApplicationContext的refresh过程,具体点是invokeBeanFactoryPostProcessors时会触发BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry。

由于启动类上的SpringBootApplication注解,实质有@SpringBootConfiguration注解,而SpringBootConfiguration注解上有Configuration注解,会被当作配置类,被ConfigurationClassParser进行解析。

需要注意被当作配置类前,会往BeanDefintion里面添加一个属性“org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass”,值为full或者lite,避免后续被再次解析。

由于SpringBootConfiguration有@ComponentScan注解,ConfigurationClassParser会将读取类/包下的类,将符合田间的类/方法注册。

由于SpringBootConfiguration有@EnableAutoConfiguration,ConfigurationClassParser在处理@Import注解时,会获取SpringBootApplication上的注解,接着获取到了EnableAutoConfiguration类上注解。

EnableAutoConfiguration注解上有AutoConfigurationPackage,此注解上有@Import注解,值为org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar。此Registrar主要是获取注解属性basePackages或属性basePackageClasses的值,注册为包名对应的bean定义。

EnableAutoConfiguration本身还有@Import注解,值是AutoConfigurationImportSelector,它会将META-INF/spring.factories当作properties文件,将key为EnableAutoConfiguration的进行处理,代码如下:

# spring-boot-autoconfigure-2.1.0.jar!/org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) { // 1. 没有配置“spring.autoconfigure.exclude=false”时,默认开启
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader); // 2. 加载“META-INF/spring-autoconfigure-metadata.properties”
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
				autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	
	protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata); // 3. 获取注解上的所有方法值
		List<String> configurations = getCandidateConfigurations(annotationMetadata, // 4. 加载“META-INF/spring.factories”作为properties文件,取key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值
				attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 5. 收集EnableAutoConfiguration.exclude、EnableAutoConfiguration.excludeName以及环境中“spring.autoconfigure.exclude”的值
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions); // 6. 根据exclude信息进行移除指定自动配置类
		configurations = filter(configurations, autoConfigurationMetadata); // 7. 将“META-INF/spring.factories”文件,key为“org.springframework.boot.autoconfigure.AutoConfigurationImportFilter”初始化,并排除不匹配的自动配置类
		fireAutoConfigurationImportEvents(configurations, exclusions); // 8. 将“META-INF/spring.factories”文件,key为“org.springframework.boot.autoconfigure.AutoConfigurationImportListener”初始化,触发AutoConfigurationImportEvent事件
		return new AutoConfigurationEntry(configurations, exclusions);
	}	

此时,spring-boot-autoconfigure里的“*AutoConfiguration”就作为配置类来解析,然后注册各种Bean了。

比如我们的@ConfigurationProperties一般配合@Configuration存在,由于spring-boot本身有ConfigurationPropertiesAutoConfiguration,它有@EnableConfigurationProperties注解,此处通过Import形式引入ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar。

ConfigurationPropertiesBeanRegistrar负责收集@EnableConfigurationProperties注解的value,然后将类注册成bean。(类上必须有@ConfigurationProperties注解)

ConfigurationPropertiesBindingPostProcessorRegistrar在BeanFactory不存在ConfigurationPropertiesBindingPostProcessor时,注册ConfigurationPropertiesBindingPostProcessor和ConfigurationBeanFactoryMetadata。

ConfigurationPropertiesBindingPostProcessor作为BeanPostProcessor,在bean初始化前以类型生成一个Bindable对象,然后用定义bean复合成新的Bindable,在这基础上再填充注解生成新的Bindable。最后使用ConfigurationPropertiesBinder进行配置绑定。

  • WebServer启动分析

如前所述,在createApplicationContext时注册了spring-boot处理@EnableAutoConfiguration的关键类——ConfigurationClassPostProcessor。在prepareContext时,将启动类注册,以供后续扫描应用的配置。

而AbstractApplicationContext的refresh有几个步骤,在invokeBeanFactoryPostProcessors阶段,基本所有的bean都已经注册了,然后在onRefresh阶段,ServletWebServerApplicationContext覆写了此方法。

ServletWebServerApplicationContext会从applicationContext获取ServletWebServerFactory,然后调用ServletWebServerFactory来得到WebServer。代码如下:

# spring-boot-2.1.0.jar!/org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}
	private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) { // 1. 无参构造的AnnotationConfigServletWebServerApplicationContext,webServer、servletContext都为null
			ServletWebServerFactory factory = getWebServerFactory(); // 2. 从BeanFactory中获取类型为ServletWebServerFactory的bean,有且只能有1个
			// 3. 从BeanFactory中获取ServletContextInitializer加入到initializers,获取Servlet、Filter、EventListener类型的bean适配成RegistrationBean加入到initializers;
			// lambda形式构建ServletContextInitializer,方法内容主要为触发ServletContextInitializer.onStartup
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context",
						ex);
			}
		}
		initPropertySources();
	}
	
	private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return this::selfInitialize;
	}
	private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareWebApplicationContext(servletContext);
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
				beanFactory);
		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
				getServletContext());
		existingScopes.restore();
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
				getServletContext());
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

ServletWebServerFactory以tomcat为例,在getWebServer时会处理tomcat的Context,在配置context时将initializers放到StandardContext。

之后是AbstractApplicationContext冻结配置,并初始化单例bean。在AbstractApplicationContext.finishRefresh阶段,ServletWebServerApplicationContext覆写此方法,启动WebServer。代码如下:

# spring-boot-2.1.0.jar!/org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
	@Override
	protected void finishRefresh() {
		super.finishRefresh();
		WebServer webServer = startWebServer();
		if (webServer != null) {
			publishEvent(new ServletWebServerInitializedEvent(webServer, this));
		}
	}
	
	private WebServer startWebServer() {
		WebServer webServer = this.webServer;
		if (webServer != null) {
			webServer.start();
		}
		return webServer;
	}

而实际tomcat的启动早在getWebServer时就进行了初始化,会触发tomcat.start,此时会触发StandardContext的start,然后触发ServletContextInitializer.onStartup。

web容器所需要的listener、filter、servlet,spring支持多种使用形式。

第一种是直接定义此种类型的Bean,spring会使用对应的RegistrationBeanAdapter将其适配成合适的RegistrationBean;

第二种是使用标准的@WebFilter、@WebServlet、@WebListener,然后使用@ServletComponentScan来扫描,再使用ServletComponentRegisteringPostProcessor注册成适当的RegistrationBean子类型;

第三种是定义spring对应的RegistrationBean子类型,RegistrationBean实现了ServletContextInitializer接口,在onStartup时进行注册。代码如下:

# spring-boot-2.1.0.jar!/org.springframework.boot.web.servlet.RegistrationBean
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
	@Override
	public final void onStartup(ServletContext servletContext) throws ServletException {
		String description = getDescription();
		if (!isEnabled()) {
			logger.info(StringUtils.capitalize(description)
					+ " was not registered (disabled)");
			return;
		}
		register(description, servletContext);
	}
	
}
# spring-boot-2.1.0.jar!/org.springframework.boot.web.servlet.DynamicRegistrationBean
public abstract class DynamicRegistrationBean<D extends Registration.Dynamic>
		extends RegistrationBean {
	
	@Override
	protected final void register(String description, ServletContext servletContext) {
		D registration = addRegistration(description, servletContext);
		if (registration == null) {
			logger.info(StringUtils.capitalize(description) + " was not registered "
					+ "(possibly already registered?)");
			return;
		}
		configure(registration);
	}
	
}
# spring-boot-2.1.0.jar!/org.springframework.boot.web.servlet.ServletRegistrationBean
public class ServletRegistrationBean<T extends Servlet>
		extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
		
	@Override
	protected ServletRegistration.Dynamic addRegistration(String description,
			ServletContext servletContext) {
		String name = getServletName();
		logger.info("Servlet " + name + " mapped to " + this.urlMappings);
		return servletContext.addServlet(name, this.servlet);
	}
	
}
# spring-boot-2.1.0.jar!/org.springframework.boot.web.servlet.AbstractFilterRegistrationBean
public abstract class AbstractFilterRegistrationBean<T extends Filter>
		extends DynamicRegistrationBean<Dynamic> {
		
	@Override
	protected Dynamic addRegistration(String description, ServletContext servletContext) {
		Filter filter = getFilter();
		return servletContext.addFilter(getOrDeduceName(filter), filter);
	}
	
}
# spring-boot-2.1.0.jar!/org.springframework.boot.web.servlet.ServletListenerRegistrationBean
public class ServletListenerRegistrationBean<T extends EventListener>
		extends RegistrationBean {
		
	@Override
	protected void register(String description, ServletContext servletContext) {
		try {
			servletContext.addListener(this.listener);
		}
		catch (RuntimeException ex) {
			throw new IllegalStateException(
					"Failed to add listener '" + this.listener + "' to servlet context",
					ex);
		}
	}
	
}

至此,spring-boot容器中bean的扫描,以及WebServer如何启动并与spring容器关联获取到三大组件便已完成。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表