SpringBoot启动流程源码解析
共 22651字,需浏览 46分钟
·
2021-05-14 12:27
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | Lucky帅小武
来源 | urlify.cn/A7rErq
前言
SpringBoot本质上没有技术革新,而是在Spring框架的基础之上简化了系统的配置,核心功能是自动装配和starter组件
一、SpringBoot核心功能
1、独立运行程序(SpringBoot程序可以不依赖其他容器独立运行)
2、内嵌Servlet容器(内嵌Servlet容器,不依赖其他Web容器)
3、提供starter组件简化maven配置(只需要依赖一个starter组件就可以自动依赖相关jar包,简化maven配置)
4、自动装配配置(为大多数应用场景提供了默认配置,大幅度减少了应用配置)
二、SpringBoot常用注解
注解 | 修饰 | 用途 | 案例 |
@ComponentScan | 类 | 自动扫描,修饰某个类则Spring会自动扫描当前类所在包路径下所以被@Component修饰的类,也可以通过basePackages自定义扫描包的根目录 | @ComponentScan(basePackages="com.test.lucky") |
@Component | 类 | 声明当前类表示当前类为一个组件,Spring容器会很自动扫描被@Component修饰的类加载bean到Spring容器中 | @Component(value="beanName") |
@Controller | 类 | 本质是一个Component,声明当前类表示当前类是一个控制器类,分发处理器会扫描被@Controller注解修饰的类的方法 | @Controller(value="testController") |
@RequestMapping | 方法 | 声明方法表示请求URL映射的方法 | @RequestMapping(value="/test") |
@ResponseBody | 方法/类 | 将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据 | @ResponseBody |
@RestController | 类 | 等同于@Controller + @ResponseBody组合,表示当前类的所有方法都自动被@ResponseBody注解修饰 | @RestController(value="testController") |
@Service | 类 | 本质是一个Component, 声明当前类是Service层的bean | @Service(value="testService") |
@Repository | 类 | 本质是一个Component, 声明当前类是Dao层的bean | @Repository(value="testDao") |
@Configuration | 类 | 本质是一个Component, 表面当前类是一个配置类,会生成一个代理对象,表示当前类是@Bean定义的源类 | @Configuration |
@Bean | 方法 | 相当于<bean>标签,被@Bean修饰的方法返回对象会被加载到Spring容器中 | @Bean(name="beanName") |
@Import | 类 | 用于导入第三方包中的bean,导入的bean自动加载到Spring容器中 | @Import(value=Test.class) |
@Value | 方法/属性 | 将常量、配置中的值、其他bean注入到变量中 | @Value("${properteis.test}") |
三、SpringBoot启动流程解析
SpringBoot的功能是基于Spring框架,所以启动时的核心步骤自然少不了Spring容器的初始化以及Spring容器的启动过程,另外SpringBoot还提供了自动装配配置功能。
1、SpringBootApplication.run()方法
SpringBoot程序启动类案例如下:
@SpringBootApplication
public class BootStrap {
public static void main(String[] args){
ApplicationContext applicationContext = SpringApplication.run(BootStrap.class);
}
}
SpringBoot程序启动入口,代码比较简洁,除了被@SpringBootApplication注解修饰之外,main丰富直接调用SpringBootApplication类的静态方法run方法即可,最终会是调用了SpringBootApplication的实例方法run方法,核心逻辑如下:
public ConfigurableApplicationContext run(String... args) {
ConfigurableApplicationContext context = null;
/** 1.通过反射创建ApplicationContext对象 */
context = createApplicationContext();
/** 2.准备ApplicationContext上下文环境 */
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
/** 3.刷新ApplicationContext */
refreshContext(context);
/** 4.刷新ApplicationContext后置处理*/
afterRefresh(context, applicationArguments);
return context;
}
可以看出SpringBoot程序启动的核心逻辑实际就两步,1、创建IOC容器ApplicationContext实例;2、启动IOC容器。
而SpringBoot的其他核心功能很显然就是通过@SpringBootApplication注解来实现,这个注解才是SpringBoot的核心实现
2、@SpringBootApplication注解
@SpringBootApplication注解定义如下:
@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
从定义可以看出@SpringBootApplication是一个组合注解,内部包含了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解
2.1、@SpringBootConfiguration
@SpringBootConfiguration注解定义如下:
1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Configuration
5 public @interface SpringBootConfiguration
可以看出@SpringBootConfiguration本质上就是一个@Configuration注解,标注当前类是一个配置类
2.2、@ComponentScan
@ComponentScan注解定义如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan
@ComponentScan注解表示自动扫描,会扫描当前类包路径下所有被@Component注解修饰的bean
2.3、@EnableAutoConfiguration
@EnableAutoConfiguration注解定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration
可以看出@EnableAutoConfiguration注解也是一个组合注解,包含了@AutoConfigurationPackage和@Import注解,再看@AutoConfigurationPackage注解的定义:
1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Inherited
5 @Import(AutoConfigurationPackages.Registrar.class)
6 public @interface AutoConfigurationPackage
该注解内部包含了一个@Import注解,所以可以得出结论@EnableAutoConfiguration实际上就是由两个@Import注解组成,分别导入了AutoConfiguartionImportSelector和AutoConfigurationPackages.Registrar类的实例。
所以总结可以得出这两个类是实现SpringBoot自动装载配置的核心实现。
关于@Import注解的详细用法这里不多介绍,先简单介绍下,@Import注解有三种用法,分别如下:
1.显示引入需要加载的bean,如:@Import(value=TestService.class)
2.引入实现ImportSelector接口的类,如自定义MyImportSelector类实现ImportSelector并重写selectImports方法,返回需要导入的bean的数组,案例如下:
1 public class MyImportSelector implements ImportSelector {
2 @Override
3 public String[] selectImports(AnnotationMetadata importingClassMetadata) {
4 return new String[]{UserService.class.getName(), GoodsService.class.getName()};
5 }
6 }
3.引入实现ImportBeanDefinitionRegistrar接口的类,如自定义MyImportBeanDefinitionRegistrar并重写registBeanDefinitions方法,手动注册需要加载的bean,案例如下:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
/** 手动注册需要加载的bean*/
registerBeanDefinitions(importingClassMetadata, registry);
}
}
回过头再看@EnableAutoConfiguration注解,分别导入了ImportSelector接口的实现类AutoConfigurationImportSelector和ImportBeanDefinitionRegistrar的实现类AutoConfigurationPackages.Registrar,接下来依次分析。
2.4、AutoConfigurationImportSelector
AutoConfigurationImportSelector实现了ImportSelector接口,所以会加载selectImports方法返回的String数组中的beanName,所以重点是selectImports方法,源码如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
/** 加载元数据*/
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
核心步骤就两步,第一步是执行loadMetadata方法,源码如下:
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
/** 加载指定路径下的元数据*/
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
/** 遍历Spring-autoconfigure-metadata.properties配置文件,读取数据封装为AutoConfigurationMetadata对象*/
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
逻辑是从加载路径下找到spring-autoconfigure-metadata.properties配置文件,然后遍历读取配置文件内容加入到Properties对象中,并封装成AutoConfigurationMetadata对象
spring-autoconfigure-metadata.properties配置文件位于spring-boot-autoconfigure包中的META-INF目录下,暂时可以不关心作用是什么,目前可以得出的结论是会先把这个配置中的内容全部加载出来。
再看第二步getAutoConfigurationEntry方法的实现,源码如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
/** 封装加载的配置元数据为AnnotationAttributes对象*/
AnnotationAttributes attributes = getAttributes(annotationMetadata);
/** 获取所有候选的配置信息集合 (IOC容器需要加载的bean)
* 实际是加载META-INF/spring.factories配置文件中的bean
* */
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
/** 去除重复的*/
configurations = removeDuplicates(configurations);
/** 去除排除的*/
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
/** 去除过滤的*/
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
/** 封装成AutoConfigurationEntry对象*/
return new AutoConfigurationEntry(configurations, exclusions);
}
目的是加载META-INF/spring.factories配置文件,得到所有需要加载的类,然后通过去除、排除的方式筛选需要加载的类
而spring.factories中有一个key为EnableAutoConfiguration,value是一个集合,包含了几乎常用的所有各种中间件的自动配置类,这些类都在spring-boot-autoconfigure包中,包括常用的有:
MongoDataAutoConfiguration、RedisAutoConfiguration、DataSourceAutoConfiguration、RabbitAutoConfiguration、ElasticsearchAutoConfiguration等等,每个类都封装了各自的自动配置类
tips:那么问题来了,SpringBoot默认提供了各种各有的配置类,如果我们的程序中并没有用到相关的组件,会不会讲这些无用的配置类也加载到容器中呢?
答案很显然是否定的,spring-autoconfigure-metadata.properties中定义了各种组件的ConditionalOnClass的key,值为对应的Class全路径,比如:
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplate
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate
ConditionalOnClass代表如果要加载对应的属性,必要要加载对应的类,比如程序中用到了RabbitMQ,那么肯定依赖了RabbitMQ对应的jar包,所以肯定会加载RabbitTemplate类,那么此时才会加载RabbitAutoConfiguration类,同理适用RedisAutoConfiguration类的前提是需要先加载RedisOperations类,只有先加载了对应的类才会加载对应的AutoConfiiguration类
总结
1、SpringBoot启动时会通过@EnableAutoConfiguration注解导入AutoConfigurationImportSelector对象,会从扫描包路径下META-INF/spring.factories中的配置,该配置文件中包含了各种组件的自动配置类对应的AutoConfiguration类,然后通过加载各种组件的AutoConfiguration类从而可以加载对应的自动配置类,并且通过ConditionalOnClass来过滤需要的自动配置类,去除掉程序中没有使用的自动配置类
2、@SpringBootApplication是个组合注解,分别是@SpringBootConfiguration、@ComponentScan和@EnableAutoConfiguration三个组件,其中@SpringBootConfiguration和@ComponentScan两个组件保证可以扫描用户自定义的bean,@EnableAutoConfiguration用于扫描加载默认的第三方的bean
3、SpringBoot程序启动的流程实际就是IOC容器创建和启动的过程,然后通过扫描用户自定义的bean和第三方的bean完成bean的加载。
四、SpringBoot内置Tomcat源码解析
SpringBoot既然内置了web容器,那么就不会只内置Tomcat一个容器,还有Jetty、Undertow,所以从设计者的角度就会在这三个具体容器之上进行抽象,定义了一个WebServer接口,WebServer接口定义如下:
public interface WebServer {
/** 启动web容器*/
void start() throws WebServerException;
/** 关闭web容器*/
void stop() throws WebServerException;
/** 获取web容器监听的端口 */
int getPort();
}
而Tomcat、Jetty、Undertow等容器就会有各自的实现类实现了WebServer接口,分别是TomcatWebServer、JettyWebServer、UndertowWebServer,而SpringBoot采用了工厂模式来决定使用哪一个容器模式,所以定义了工厂类ServertWebServerFactory。在spring.factories配置文件中就定义了ServletWebServerFactoryAutoConfiguration,该类的定义如下:
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration
可以看出该类通过@Import注解分别导入了EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow的bean,以EmbeddedTomcat为例,定义如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
该类内部通过@Bean注解定义了一个TomcatServletWebServerFactory对象,该对象就是TomcatWebServer的工厂类,TomcatServletWebServerFactory有一个getWebServer方法,定义如下:
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
1 protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
2 return new TomcatWebServer(tomcat, getPort() >= 0);
3 }
源码不复杂,创建了一个Tomcat对象,然后初始化参数,再传给TomcatWebServer的构造函数创建TomcatWebServer对象,TomcatWebServer构造函数中会调用Tomcat的start方法启动Tomcat服务器
tips:那么问题来了,getWebServer方法是何时调用的呢?
在Spring容器创建之后会执行刷新操作,ApplicationContext的子类ServletWebServerApplicationContext实现了ApplicationContext的onRefresh方法,源码如下:
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
首先是调用父类的onRefresh方法,然后执行createWebServer方法创建WebServer对象,逻辑就是从IOC容器中找到ServletWebServerFactory对象,执行getWebServer方法获取WebServer对象。
apache的Tomcat实现原理解析可参考:
五、如何实现自定义starter
1、新建项目spring-boot-mytest-starter(官方提供的starter命名为spring-boot-starter-xxx,自定义的通常命名为spring-boot-xxx-starter,用于和官方提供的区分开)
粉丝福利:Java从入门到入土学习路线图
👇👇👇
👆长按上方微信二维码 2 秒
感谢点赞支持下哈