Spring Boot 启动原理和自动配置原理探究

声明:本文大部分来源于网络,及本人通过debug对源码的追溯

springboot框架具有如下特性:

SpringApplication 应用类

SpringApplication 是 Spring Boot 应用启动类,在 main() 方法中调用 SpringApplication.run() 静态方法,即可运行一个 Spring Boot 应用。简单使用代码片段如下:

public static void main(String[] args) {
    SpringApplication.run(QuickStartApplication.class, args);
}

自动配置

Spring Boot 在不需要任何配置情况下,就直接可以运行一个应用。实际上,Spring Boot 框架的 spring-boot-autoconfigure 依赖做了很多默认的配置项,即应用默认值。这种模式叫做 “自动配置”。Spring Boot 自动配置会根据添加的依赖,自动加载依赖相关的配置属性并启动依赖。例如,默认用的内嵌式容器是 Tomcat ,端口默认设置为 8080。

外化配置

Spring Boot 简化了配置,在 application.properties 文件配置常用的应用属性。Spring Boot 可以将配置外部化,这种模式叫做 “外化配置”。将配置从代码中分离外置,最明显的作用是只要简单地修改下外化配置文件,就可以在不同环境中,可以运行相同的应用代码。

内嵌容器

Spring Boot 启动应用,默认情况下是自动启动了内嵌容器 Tomcat,并且自动设置了默认端口为 8080。另外还提供了对 Jetty、Undertow 等容器的支持。开发者自行在添加对应的容器 Starter 组件依赖,即可配置并使用对应内嵌容器实例。

Starter 组件

Spring Boot 提供了很多 “开箱即用” 的 Starter 组件。Starter 组件是可被加载在应用中的 Maven 依赖项。只需要在 Maven 配置中添加对应的依赖配置,即可使用对应的 Starter 组件。例如,添加 spring-boot-starter-web 依赖,就可用于构建 REST API 服务,其包含了 Spring MVC 和 Tomcat 内嵌容器等。

本文拟结合网络博客教程和手动debug看源码,对spring boot的启动原理和自动配置原理做初步探究。


正文

我们开发任何一个Spring Boot项目,都会用到如下的启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

我们跟踪注解@SpringBootApplication可以看到下面的代码:

@Target(ElementType.TYPE)            // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME)  // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented                          // 表明这个注解应该被javadoc记录
@Inherited                           // 子类可以继承该注解
@SpringBootConfiguration             // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration             // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan(excludeFilters = {    // 扫描路径设置(具体使用待确认)
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

上面众多注解中最重要的只有三个:

  • @Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration)
  • @EnableAutoConfiguration
  • @ComponentScan

@Configuration

SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。基于JavaConfig的配置形式是这样的:

@Configuration
public class MockConfiguration{
    @Bean
    public MockService mockService(){
        return new MockServiceImpl();
    }
}

任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

@ComponentScan

@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

@EnableAutoConfiguration

@EnableAutoConfiguration是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器

@EnableAutoConfiguration作为一个复合Annotation,其自身定义关键信息如下:

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

两个比较重要的注解:

  • @AutoConfigurationPackage:自动配置包
  • @Import: 导入自动配置的组件

AutoConfigurationPackage注解的定义@importAutoConfigurationPackages.Registrar.class类文件,该文件有如下定义:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImport(metadata).getPackageName());
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImport(metadata));
		}

	}

它其实是注册了一个Bean的定义。new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的同级以及子级的包组件。

以下图为例,DemoApplication是和demo包同级,但是demo2这个类是DemoApplication的父级,和example包同级。也就是说,DemoApplication启动加载的Bean中,并不会加载demo2,这也就是为什么,我们要把DemoApplication放在项目的最高级中。

通过追踪源码AutoConfigurationImportSelector → getAutoConfigurationEntry → getCandidateConfigurations → loadFactoryNames → loadSpringFactories,可以看到读取了 FACTORIES_RESOURCE_LOCATION 路径,而该路径被定义为"META-INF/spring.factories"。

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

 

我们在spring-boot-autoconfigure-2.1.6.RELEASE文件夹中找到这个外部文件,有很多自动配置的类,如下:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\

...

其中最重要的要属@Import(AutoConfigurationImportSelector.class),@EnableAutoConfiguration借助EnableAutoConfigurationImportSelector可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。

非web应用程序

在spring Boot中,有些代码是WEB功能,例如API等,但是有些逻辑是非WEB,启动时就要调用并持续运行的。要创建一个非Web应用程序,实现CommandLineRunner并覆盖run()方法,例如:

import org.springframework.boot.CommandLineRunner;

@SpringBootApplication
public class SpringBootConsoleApplication implements CommandLineRunner {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(SpringBootConsoleApplication.class, args);
    }

    //access command line arguments
    @Override
    public void run(String... args) throws Exception {
        //do something
    }
}

pom.xml的项目依赖只有spring-boot-starter库。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值