Spring Boot微服务项目实战(第2版)学习笔记-第20章Spring Boot原理解析
Spring Boot原理解析
本章主要回顾了MySpringApplication入口类上注解和run方法的原理,梳理了Spring Boot启动执行的流程和简单分析spring-boot-starter起步依赖原理,同时介绍真实项目中的跨域问题、Spring Boot优雅关闭以及如何将普通Web项目改造成Spring Boot项目等内容。
1.回顾入口类
1.1 DemoApplication入口类
首先,我们先来回顾一下项目spring-boot-book-v2的入口类DemoApplication,具体代码如下:
@SpringBootApplication
@ServletComponentScan
@ImportResource(locations={"classpath:spring-mvc.xml"})
@EnableAsync
@EnableRetry
public class DemoApplication{
public static void main(String[] args){
SpringApplication.run(MySpringBootApplication.class, args);
}
}
在入口类DemoApplication中,@SpringBootApplication和main方法是Spring Boot为我们自动生成的,其他注解都是我们在学习SpringBoot整合其他技术添加上去的。接下来就和大家一起看看@SpringBootApplication和SpringApplication.run方法到底为我们做了些什么。
1.2 @SpringBootApplication的原理
@SpringBootApplication开启了Spring的组件扫描和Spring Boot自动配置功能。实际上它是一个复合注解,包含3个重要的注解**@SpringBootConfiguration**、@EnableAutoConfiguration、@ComponentScan,其源代码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
//省略代码
}
- @SpringBootConfiguration注解:标明该类使用Spring基于Java的注解,Spring Boot推荐使用基于Java而不是XML的配置,所以本书的实战例子都是基于Java而不是XML的配置。查看@SpringBootConfiguration源代码,可以看到它就是对@Configuration进行简单的“包装”,然后取名为SpringBootConfiguration。@SpringBootConfiguration源代码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration{
//省略代码
}
我们对@Configuration注解并不陌生,它就是JavaConfig形式的Spring IoC容器的配置类使用的那个@Configuration。
- @EnableAutoConfiguration注解:该注解可以开启自动配置的功能。@EnableAutoConfiguration的源代码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration{
//省略代码
}
从@EnableAutoConfiguration源代码可以看出,其包含@Import注解。我们知道,@Import注解的主要作用就是借助EnableAutoConfigurationImportSelector将Spring Boot应用所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中,IoC容器就是我们所说的Spring应用程序上下文ApplicationContext。学习过Spring框架就知道,Spring框架提供了很多@Enable开头的注解定义,比如@EnableScheduling、@EnableCaching等。这些@Enable开头的注解都有一个共同的功能,就是借助@Import的支持,收集和注册特定场景相关的bean定义。
- @ComponentScan注解:启动组件扫描,开发的组件或bean定义能被自动发现并注入到Spring应用程序上下文。比如我们在控制层添加@Controller注解、服务层添加的@Service注解和@Component注解等,这些注解都可以被@ComponentScan注解扫描到。
Spring Boot早期的版本中,需要在入口类同时添加这3个注解,但从Spring Boot 1.2.0开始,只要在入口类添加@SpringBootApplication注解即可。
1.3 SpringApplication的run方法
除了@SpringBootApplication注解外,我们发现入口类的一个显眼的地方,就是SpringApplication.run方法。在run方法中,首先创建一个SpringApplication对象实例,然后调用SpringApplication的run方法。SpringApplication.run方法的源代码如下:
从源代码可以看出,Spring Boot首先开启了一个SpringApplicationRunListeners监听器,然后通过createApplicationContext、prepareContext和refreshContext方法创建、准备、刷新应用上下文ConfigurableApplicationContext,通过上下文加载应用所需的类和各种环境配置等,最后启动一个应用实例。
1.4 SpringApplicationRunListeners监听器
SpringApplicationRunListener接口规定了Spring Boot的生命周期,在各个生命周期广播相应的事件(ApplicationEvent),调用实际的是ApplicationListener类。SpringApplicationRunListener源代码如下:
public interface SpringApplication {
//执行run方法时触发
void starting();
//环境建立好时候触发
void environmentPrepared(ConfigurableEnvironment environment);
//上下文建立好的时候触发
void contextPrepared(ConfigurableApplicationContext context);
//上下文载入配置时候触发
void contextLoaded(ConfigurableApplicationContext context);
//上下文刷新完成后,run方法执行完之前触发
void finished(ConfigurableApplicationContext context, Throwable exception);
}
ApplicationListener是Spring框架对Java中实现的监听器模式的一种框架实现。具体源代码如下:
public interface ApplicationListerner<E extends ApplicationEvent> extends EventListener{
void onApplicationEvent(E var1);
}
ApplicationListener接口只有一个方法onApplicationEvent,所以自己的类在实现该接口的时候要实现该方法。如果在上下文ApplicationContext中部署一个实现了ApplicationListener接口的监听器,每当ApplicationEvent事件发布到 ApplicationContext时,该监听器会得到通知。如果要为Spring Boot应用添加自定义的ApplicationListener,可以通过SpringApplication.addListeners()或者SpringApplication.setListeners()方法添加一个或者多个自定义的ApplicationListener。
1.5 ApplicationContextInitializer接口
在Spring Boot准备上下文prepareContext的时候,会对ConfigurableApplicationContext实例做进一步的设置或者处理。prepareContext的源代码如下:
在准备上下文prepareContext方法中,通过applyInitializers方法对context上下文进行设置和处理。applyInitializers的源代码如下:
在applyInitializers方法中,主要是调用ApplicationContextInitializer类的initialize方法对应用上下文进行设置和处理。ApplicationContextInitializer本质上是一个回调接口,用于在ConfigurableApplicationContext执行refresh操作之前对它进行一些初始化操作。一般情况下,我们基本不需要自定义一个ApplicationContextInitializer,如果真需要自定义一个ApplicationContextInitializer,那么可以通过SpringApplication.addInitializers()设置即可。
1.6 ApplicationRunner与CommandLineRunner
ApplicationRunner与CommandLineRunner接口执行点是在容器启动成功后的最后一步回调,我们可以在回调方法run中执行相关逻辑。ApplicationRunner的源代码如下:
public interface ApplicationRunner{
void run(ApplicationArguments arg) throws Exception;
}
CommandLineRunner的源代码如下:
public interface CommandLineRunner{
void run(String... arg) throws Exception;
}
在ApplicationRunner或CommandLineRunner类中,只有一个run方法,但是它们的入参不一样,分别是ApplicationArguments和可变String数组。
如果有多个ApplicationRunner或CommandLineRunner实现类,而我们需要按一定顺序执行它们的话,可以在实现类上加上@Order(value=整数值)注解,Spring Boot会按照@Order中的value值从小到大依次执行。
如果想在Spring Boot启动的时候运行一些特定的代码,你可以实现接口ApplicationRunner或者 CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个run方法。
例如:
public class MyCommandRunner implements CommandLineRunner{
@Override
public void run(String... args) throws Exception {
//do something
}
}
或者:
@Bean
public CommandLineRunner init(){
return (String... strings) ->{
};
}
2.SpringApplication执行流程
上一节,我们对SpringApplication的run方法进行了简单的学习,本节再简单地总结一下Spring Boot启动的完整流程,具体流程如图所示。
- 项目启动时,调用入口类MySpringBootApplication的main方法。
- 入口类MySpringBootApplication的main方法会调用SpringApplication的静态方法run。
- 在run方法中首先创建一个SpringApplication对象实例,然后调用SpringApplication对象实例的run方法。
- 查询和加载所有的SpringApplicationListener监听器。
- SpringApplicationListener监听器调用其starting方法,SpringBoot通知这些SpringApplicationListener监听器,我马上要开始执行了。
- 创建和准备Spring Boot应用将要使用的Environment环境,包括配置要使用的PropertySource以及Profile。
- 创建和初始化应用上下文ApplicationContext。这一步只是准备工作,并未开始正式创建。
- 这一步是最重要的,Spring Boot会通过@EnableAutoConfiguration获取所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。
- 主要是调用ApplicationContextInitializer类的initialize方法对应用上下文进行设置和处理。
- 调用ApplicationContext上下文的refresh方法,使Ioc容器达到可用状态。
- 查找当前ApplicationContext上下文是否注册有ApplicationRunner与CommandLineRunner,如果有,循环遍历执行ApplicationRunner和CommandLineRunner的run方法。
- 执行SpringApplicationListener的finished方法,Spring Boot应用启动完毕。
3.spring-boot-starter原理
3.1 自动配置条件依赖
在之前的章节中,我们在项目的pom文件中引入了很多spring-boot-starter依赖,比如spring-boot-starter-jdbc、spring-boot-starter-jdbc-logging、spring-boot-starter-web等。这些带有spring-boot-starter前缀的依赖都叫作Spring Boot起步依赖,它们有助于SpringBoot应用程序的构建。
如果没有起步依赖,假如要使用Spring MVC的,我们根本记不住SpringMVC到底要引入那些依赖包、到底要使用哪个版本的Spring MVC、Spring MVC的Group和Artifact ID又是多少?
Spring Boot通过提供众多起步依赖降低项目依赖的复杂度。起步依赖本质上是一个Maven项目对象模型,定义了对其他库的传递依赖,这些依赖的合集可以对外提供某项功能。起步依赖的命名表明它们提供某种或某类功能。例如,spring-boot-starter-jdbc表示提供JDBC相关的功能,spring-boot-starter-jpa表示提供JPA相关的功能,等等。表20-1中简单地列举了工作中经常使用的起步依赖。
事实上,起步依赖和项目里的其他依赖没什么区别。引入起步依赖的同时会引入相关的传递依赖。比如spring-boot-starter-web起步依赖会引入spring-webmvc、jackson-databind、spring-boot-starter-tomcat等传递依赖。如果不想用spring-boot-starter-web引入的spring-webmvc传递依赖,可以使用标签来排除传递依赖。具体代码如下:
假如spring-boot-starter-web引入的传递依赖版本过于低,可以在pom文件中直接引入所需的版本,告诉Maven现在需要这个版本的依赖。
传统的Spring应用需要在application.xml中配置很多bean,比如dataSource的配置,transactionManager的配置等。Spring Boot是如何帮我们完成这些bean的配置的呢?下面我们来分析这个过程。我们以第10章引入的mybatis-spring-boot-starter依赖为例:
首先,查看mybatis-spring-boot-starter包下的内容,具体如图
所示。
可以看出在mybatis-spring-boot-starter包中并没有任何源代码,只有一些配置文件,例如,pom.xml文件,它的作用就是帮我们引入了相关的jar包。在pom.xml中可以看出,mybatis-spring-boot-starter包帮我们引入了mybatis-spring-boot-autoconfigure这个包,具体代码如下:
查看mybatis-spring-boot-autoconfigure包下的内容,如图所示。
再查看MybatisAutoConfiguration源代码:
- @Configuration、@Bean:这两个注解一起使用就可以创建一个基于Java代码的配置类。可以把MybatisAutoConfiguration类想象成一份XML配置文件。@Configuration注解的类可以看作Bean实例的工厂,能生产让Spring IoC容器管理的Bean。@Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册到Spring容器中。MybatisAutoConfiguration类能自动生成SqlSessionFactory、SqlSessionTemplate等MyBatis的重要实例并交给Spring容器管理,从而完成Bean的自动注册。
- @ConditionalOnClass:某个class位于类路径上,才会实例化这个Bean。
- @ConditionalOnBean:仅在当前上下文中存在某个bean时,才会实例化这个Bean。
- @ConditionalOnSingleCandidate:类似于@ConditionalOnBean。
- @ConditionalOnExpression:当表达式为true的时候,才会实例化这个Bean。
- @ConditionalOnMissingBean:仅在当前上下文中不存在某个Bean时,才会实例化这个Bean。
- @ConditionalOnMissingClass:某个class在类路径上不存在的时候,才会实例化这个Bean。
- @ConditionalOnNotWebApplication:不是Web应用时才会实例化这个Bean。
- @AutoConfigureAfter:在某个Bean完成自动配置后实例化这个Bean。
- @AutoConfigureBefore:在某个Bean完成自动配置前实例化这个Bean。
可见,要完成MyBatis的自动配置,需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类,需要存在DataSource这个Bean且这个Bean完成自动注册。
进入DataSourceAutoConfiguration类,可以看到该类属于spring-boot-autoconfigure自动配置包,自动配置这个包帮我们引入了jdbc、kafka、logging、mail、mongo等包。很多包需要我们引入相应的jar后自动配置才生效。
3.2 Bean参数获取
我们已经知道了Bean的配置过程,但是还没有看到Spring Boot是如何读取yml或者properites配置文件的属性来创建数据源,在DataSourceAutoConfiguration类里面,使用了EnableConfigurationProperties注解,参见如下代码:
@EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。
DataSourceProperties中封装了数据源的各个属性,且使用了注解@ConfigurationProperties指定了配置文件的前缀,参见如下代码:
@ConfigurationProperties注解的作用是把yml或者properties配置文件转化为Bean,通过这种方式,把yml或者properties配置参数转化为Bean。
3.3 Bean的发现与加载
Spring Boot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包中的类,那么依赖包中的Bean是如何被发现和加载的呢?
我们通常在启动类中加@SpringBootApplication注解,查看如下源代码:
@SpringBootConfiguration是进入@ SpringBootConfiguration注解的源代码,你会发现它其实和@Configuration注解的功能是一样的,只是换了一个名字而已。@ SpringBootConfiguration的源代码如下:
- @EnableAutoConfiguration:这个注解的功能非常重要,它用于借助@Import的支持,收集和注册依赖包中相关的Bean定义。
- @ComponentScan:该注解的作用是自动扫描并加载符合条件的组件,比如@Component和@Repository等,最终将这些Bean定义加载到Spring容器中。
@EnableAutoConfiguration的源代码如下:
@EnableAutoConfiguration注解引入了@AutoConfigurationPackage和@Import这两个注解。@AutoConfigurationPackage的作用是自动配置包,@Import则是导入需要自动配置的组件。
进入@AutoConfigurationPackage,发现其也是引入了@Import注解,参见代码如下:
查看@Import注解中的Registrar类源代码:
new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()和new AutoConfigurationPackages.PackageImport(metadata)的作用就是加载启动类所在的包下的主类与子类的所有组件注册到Spring容器,Spring Boot默认扫描启动类所在的包下的主类与子类的所有组件。
继续查看AutoConfigurationImportSelector类的源代码:
SpringFactoriesLoader.loadFactoryNames方法调用loadSpringFactories方法从所有的jar包中读取META-INF/spring.factories文件信息。loadSpringFactories的源代码如下:
下面是spring-boot-autoconfigure的jar中spring.factories文件的部分内容,其中有一个key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定义了需要自动配置的Bean,通过读取这个配置获取一组@Configuration类。
每个xxxAutoConfiguration都是一个基于Java的Bean配置类。实际上,这些xxxAutoConfiguration不是所有都会被加载,会根据xxxAutoConfiguration上的@ConditionalOnClass等条件判断是否加载。通过反射机制将spring.factories中的@Configuration类实例化为对应的Java实例。
至此,我们已经知道了怎么发现自动配置的Bean,最后一步就是怎样将这些Bean加载到Spring容器中。
将普通类交给Spring容器管理,通常有以下方法:
- 使用 @Configuration与@Bean注解。
- 使用@Controller、@Service、@Repository、@Component注解标注该类,然后启用@ComponentScan自动扫描。
- 使用@Import方法。
Spring Boot中采取第3种方法,@EnableAutoConfiguration注解中使用@Import({AutoConfigurationImportSelector.class})注解,AutoConfigurationImportSelector实现了DeferredImportSelector接口,DeferredImportSelector接口继承了ImportSelector接口,ImportSelector接口只有一个selectImports方法。
AutoConfigurationImportSelector的源代码如下:
上一篇: Spring学习笔记(一)bean的配置
下一篇: pageher +mybaits的yml