- 类加载器
我们在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容器关联获取到三大组件便已完成。
本文暂时没有评论,来添加一个吧(●'◡'●)