框架——Spring Boot

1,072 阅读11分钟

Spring Boot特性与模块 

特性

Spring Boot 是构建基于Spring的应用程序的起点。Spring Boot 旨在让你尽可能快地启动和运行,并以最小的预先配置的Spring配置。

 Spring Boot的核心特性如下: 

  • 创建一键运行的Spring应用。
  • 能够使用内嵌的Tomcat、Jetty或Undertow,不需要部署war。 
  • 提供定制化的启动器starters简化第三方依赖配置。 
  • 追求极致的自动配置Spring。 
  • 提供一些生产环境的特性,比如特征指标、健康检查和外部配置。 
  • 零代码生成和零XML配置

Spring Boot框架中还有两个非常重要的策略:开箱即用和约定优于配置。

  • 开箱即用,Outofbox,是指在开发过程中,通过在MAVEN项目的pom文件中添加相关依赖包,然后使用对应注解来代替繁琐的XML配置文件以管理对象的生命周期。这个特点使得开发人员摆脱了复杂的配置工作以及依赖的管理工作,更加专注于业务逻辑。
  • 约定优于配置,Convention over configuration,是一种由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。

核心模块

     Spring Boot是基于Spring Framework体系来构建的,它的主要核心: 

  • Starter组件,提供开箱即用的组件。
  • 自动装配,自动根据上下文完成Bean的装配。 
  • Actuator,Spring Boot应用的监控。
  • Spring Boot CLI,基于命令行工具快速构建Spring Boot应用。

  • spring-boot:Spring Boot核心工程。 
  • starters:Spring Boot的启动服务工程。spring-boot中内置提供的starter列表可以在SpringBoot 项目源代码工程spring-boot/spring-boot-starters中看到。
  • autoconfigure:Spring Boot实现自动配置的核心工程。 
  • actuator:提供Spring Boot应用的外围支撑性功能。比如:应用状态监控管理、应用健康指示表、远程shell支持。 
  • tools:提供了Spring Boot开发者的常用工具集。
  • cli:Spring Boot命令行交互工具,可用于使用Spring进行快速原型搭建。可以用它直接运行Groovy脚本。如果你不喜欢Maven或Gradle,可用CLI(Command Line Interface)来开发运行Spring应用程序。可以使用它来运行Groovy脚本,甚至编写自定义命令。

Spring Boot自动装配的原理

自动装配的实现

       自动装配在Spring Boot中是通过@EnableAutoConfiguration注解来开启的,这个注解的声明在启动类注解@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 {}

        进入@EnableAutoConfiguration注解里,可以看到除@Import注解之外,还多了一个@AutoConfigurationPackage注解(它的作用是把使用了该注解的类所在的包及子包下所有组件扫描到SpringIoC容器中)。并且,@Import注解中导入的并不是一个Configuration的配置类,而是一个AutoConfigurationImportSelector类。

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

AutoConfigurationImportSelector实现了ImportSelector,它只有一个selectImports抽象方法,并且返回一个String数组,在这个数组中可以指定需要装配到IoC容器的类,当在@Import中导入一个ImportSelector的实现类之后,会把该实现类中返回的Class名称都装载到IoC容器中。和@Configuration不同的是,ImportSelector可以实现批量装配,并且还可以通过逻辑处理来实现Bean的选择性装配,也就是可以根据上下文来决定哪些类能够被IoC容器初始化。

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);

    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }
}

例子

//首先创建两个类,我们需要把这两个类装配到IoC容器中。
public class FirstClass {
}
public class SecondClass {
}
//创建一个ImportSelector的实现类,在实现类中把定义的两个Bean加入String数组,
//这意味着这两个Bean会装配到IoC容器中。
public class GpImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{FirstClass.class.getName(),SecondClass.class.getName()};
    }
}
//创建一个启动类,在启动类上使用@Import与@AutoConfigurationPackage注解后,
//即可通过ca.getBean从IoC容器中得到FirstClass对象实例。
@SpringBootApplication
@AutoConfigurationPackage
@Import({GpImportSelector.class})
public class DemoApplication {
    public static void main(String[] args) throws IOException {
        ConfigurableApplicationContext ca =SpringApplication.run(DemoApplication.class, args);
        FirstClass bean = ca.getBean(FirstClass.class);
    }

这种实现方式相比@Import(*Configuration.class)的好处在于装配的灵活性,还可以实现批量。

自动装配原理分析

    定位到AutoConfigurationImportSelector中的selectlmports方法,它是ImportSelector接口的实现,这个方法中主要有两个功能: 

  • AutoConfigurationMetadata.loadMetadata方法从META-INF/spring-autoconfigure-metadata.properties中加载自动装配的条件元数据,简单来说就是只有满足条件的Bean才能够进行装配。
  • 收集所有符合条件的配置类autoConfigurationEntry.getConfigurations(),完成自动装配。

selectlmports()方法与loadMetadata()方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!this.isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	} else {
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
		AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
}
//META-INF/spring-autoconfigure-metadata.properties中加载
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
	return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
}

收集所有符合条件的配置类autoConfigurationEntry.getConfigurations()方法

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
	if (!this.isEnabled(annotationMetadata)) {
	   return EMPTY_ENTRY;
	} else {
	//获得@EnableAutoConfiguration注解中的属性exclude、excludeName等。    
	AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
	//获得所有自动装配的配置类
	List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
	//去除重复的配置项
	configurations = this.removeDuplicates(configurations);
	//根据@EnableAutoConfiguration注解中配置的exclude等属性,把不需要自动装配的配置类移除
	Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
	this.checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	configurations = this.filter(configurations, autoConfigurationMetadata);
	//广播事件
	this.fireAutoConfigurationImportEvents(configurations, exclusions);
	//返回经过多层判断和过滤之后的配置类集合
	return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
	} 

       总的来说,它先获得所有的配置类,通过去重、exclude排除等操作,得到最终需要实现自动装配的配置类。 其中getCandidateConfigurations,它是获得配置类最核心的方法。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

        这里用到的SpringFactoriesLoader,它是Spring内部提供的一种约定俗成的加载方式,类似于Java中的SPI。它会扫描classpath下的META-INF/spring.factories文件,spring.factories文件中的数据以Key=Value形式存储, 而SpringFactoriesLoader loadFactoryNames会根据Key得到对应的value值。 因此,这个场景中,Key对应为EnableAutoConfiguration,Value是多个配置类,也就是getCandidateConfigurations方法所返回的值。

# AutoConfigureCache auto-configuration imports
org.springframework.boot.test.autoconfigure.core.AutoConfigureCache=\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration

# AutoConfigureDataJpa auto-configuration imports
org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa=\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
.......

        每个XXXAutoConfiguration除了基本的@Configuration注解,还有一个@ConditionalOnClass注解,这个条件控制机制在这里的用途是,判断classpath下是否存在RabbitTemplate和Channel这两个类,如果是,则把当前配置类注册到IoC容器。另外,@EnableConfigurationProperties是属性配置,也就是说我们可以按照约定在applicationproperties中配置RabbitMQ的参数,而这些配置会加载到RabbitProperties中。实际上,这些东西都是Spring本身就有的功能。

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@EnableConfigurationProperties({RabbitProperties.class})
@Import({RabbitAnnotationDrivenConfiguration.class})
public class RabbitAutoConfiguration {

小结

       至此,自动装配的原理基本上就分析完了,简单来总结一下核心过程: 

  • 通过@Import(AutoConfigurationImportSelector)实现配置类的导入,但是这里并不是传统意义上的单个配置类装配。
  • AutoConfigurationImportSelector类实现了ImportSelector接口,重写了方法selectImports,它用于实现选择性批量配置类的装配。
  • 通过Spring提供的SpringFactoriesLoader机制,扫描classpath路径下的META-INF/springfactories,读取需要实现自动装配的配置类。 
  • 通过条件筛选的方式,把不符合条件的配置类移除,最终完成自动装配。

自动配置

      Spring Boot中还提供了spring-autoconfigure-metadata.properties文件来实现批量自动装配条件配置。 它的作用和@Conditional是一样的,只是将这些条件配置放在了配置文件中。下面这段配置来自spring -boot-autoconfigure jar包中的/META-INF/spring-autoconfigure-metadata.properties文件。

org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,reactor.core.publisher.Flux,org.springframework.data.cassandra.core.ReactiveCassandraTemplate
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration.ConditionalOnWebApplication=SERVLET
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration=

         这种形式也是“约定优于配置”的体现,通过这种配置化的方式来实现条件过滤必须要遵循两个条件:

  • 配置文件的路径和名称必须是/META-INF/spring-autoconfigure-metadata.properties。
  • 配置文件中key的配置格式:自动配置类的类全路径名条件=值。 

这种配置方法的好处在于,它可以有效地降低Spring Boot的启动时间,通过这种过滤方式可以减少配置类的加载数量,因为这个过滤发生在配置类的装载之前,所以它可以降低Spring Boot启动时装载Bean的耗时。

深入Actuator

       Spring Boot的Actuator。它提供了很多生产级的特性,比如监控和度量Spring Boot应用程序。Actuator的这些特性可以通过众多REST端点、远程shell和JMX获得。

Spring Boot Actuator的关键特性是在应用程序里提供众多Web端点,通过它们了解应用程序 运行时的内部状况。有了Actuator,你可以知道Bean在Spring应用程序上下文里是如何组装在一 起的,掌握应用程序可以获取的环境属性信息,获取运行时度量信息的快照……

Actuator端点

       要启用Actuator的端点,只需在项目中引入Actuator的起步依赖即可。Actuator提供了13个端点

  • GET /autoconfig 提供了一份自动配置报告,记录哪些自动配置条件通过了,哪些没通过 
  • GET /configprops 描述配置属性(包含默认值)如何注入Bean 
  • GET /beans 描述应用程序上下文里全部的Bean,以及它们的关系 
  • GET /dump 获取线程活动的快照 
  • GET /env 获取全部环境属性 
  • GET /env/{name} 根据名称获取特定的环境属性值 
  • GET /health 报告应用程序的健康指标,这些值由 HealthIndicator 的实现类提供 
  • GET /info 获取应用程序的定制信息,这些信息由 info 打头的属性提供 
  • GET /mappings 描述全部的URI路径,以及它们和控制器(包含Actuator端点)的映射关系 
  • GET /metrics 报告各种应用程序度量信息,比如内存用量和HTTP请求计数 GET /metrics/{name} 报告指定名称的应用程序度量值 
  • POST /shutdown 关闭应用程序,要求 endpoints.shutdown.enabled 设置为 true 
  • GET /trace 提供基本的HTTP请求跟踪信息(时间戳、HTTP头等)

保护 Actuator 端点

        很多Actuator端点发布的信息都可能涉及敏感数据,还有一些端点,(比如/shutdown)非常危险,可以用来关闭应用程序。因此,保护这些端点尤为重要,能访问它们的只能是那些经过授权 的客户端。 实际上,Actuator的端点保护可以用和其他URL路径一样的方式——使用Spring Security。在 Spring Boot应用程序中,这意味着将Security起步依赖作为构建依赖加入,然后让安全相关的自动配置来保护应用程序,其中当然也包括了Actuator端点。

Spring Boot开发者工具

       Spring Boot引入了一组新的开发者工具,可以让你在开发时更方便地使用Spring Boot, 包括如下功能。

自动重启:当Classpath里的文件发生变化时,自动重启运行中的应用程序。 

LiveReload支持:对资源的修改自动触发浏览器刷新。 

远程开发:远程部署时支持自动重启和LiveReload。 

默认的开发时属性值:为一些属性提供有意义的默认开发时属性值。 

Spring Boot的开发者工具采取了库的形式,可以作为依赖加入项目。在Maven POM里添加 

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
</dependency>

当应用程序以完整打包好的JAR或WAR文件形式运行时,开发者工具会被禁用,所以没有必要在构建生产部署包前移除这个依赖。

自动重启

         Classpath里对文件做任何修改都会触发应用程序重启。为了让重启速度够快,不会修改的类(比如第三方JAR文件里的类)都加载到了基础类加载器里,而应用程序的代码则会加载到一个单独的重启类加载器里。检测到变更时,只有重启类加载器重启。 有些Classpath里的资源变更后不需要重启应用程序。像Thymeleaf这样的视图模板可以直接编辑,不用重启应用程序。在/static或/public里的静态资源也不用重启应用程序,所以Spring Boot 开发者工具会在重启时排除掉如下目录:/META-INF/resources、/resources、/static、/public和 /templates。 可以设置 spring.devtools.restart.exclude 属性来覆盖默认的重启排除目录。

彻底关闭自动重启,可以将 spring.devtools.restart.enabled设置为false

LiveReload

      Spring Boot的开发者工具集成了LiveReload(livereload.com),可以消除刷新的步骤。 Spring Boot会启动一个内嵌的LiveReload服务器,在资源文件变化时会触发浏览器刷新。你要做的就是在浏览器里安装LiveReload插件。 禁用内嵌的LiveReload服务器 , 可以将 spring.devtools.livereload.enabled设置为false 。

远程开发

       设置一个远程安全码来开启远程开发功能: 

spring:devtools:remote:secret:myappsecret 

       有了这个属性后,运行中的应用程序就会启动一个服务器组件以支持远程开发。它会监听接受变更的请求,可以重启应用程序或者触发浏览器刷新。 为了使用这个远程服务器,你需要在本地运行远程开发工具的客户端。这个远程客户端是一个类,全限定类名是 org.springframework.boot.devtools.RemoteSpringApplication 。 它会运行在IDE里,要求提供一个参数,告知远程应用程序部署在哪里。

参考

SpringBoot实战(第4版)
Spring Cloud Alibaba 微服务原理与实战