Spring Boot 2.1.8 发布了,更新内容如下:

Spring
Boot为开发人员提供了大多数流行的开源项目的启动器,但我们不仅限于此。

  • Spring
    Boot非常适合开发web应用程序。你可以使用内嵌的Tomcat,Jetty或Undertow轻轻松松地创建一个HTTP服务器。大多数的web应用都使用spring-boot-starter-web模块进行快速搭建和运行。

 New Features

  • 添加了 Issuer Validation
    的其他资源服务器配置 #17952
  • 在依赖关系管理中公开依赖管理插件版本 #17842

我们也可以编写自己的自定义启动器。
如果想为我们的组织提供一个内部库,并且将它在Spring
Boot上下文中使用,那么为它编写一个启动器也许是一个好的习惯。

Spring Web MVC框架

  • Spring Web MVC框架(通常简称为”Spring
    MVC”)是一个富”模型,视图,控制器”的web框架。 Spring
    MVC允许你创建特定的@Controller或@RestController
    beans来处理传入的HTTP请求。
    使用@RequestMapping注解可以将控制器中的方法映射到相应的HTTP请求。

示例:

@RestController
@RequestMapping(value="/users")
public class MyRestController {
    @RequestMapping(value="/{user}", method=RequestMethod.GET)
    public User getUser(@PathVariable Long user) {
// ...
    }
    @RequestMapping(value="/{user}/customers", method=RequestMethod.GET)
    List<Customer> getUserCustomers(@PathVariable Long user) {
// ...
    }
    @RequestMapping(value="/{user}", method=RequestMethod.DELETE)
    public User deleteUser(@PathVariable Long user) {
// ...
    }
}

Bug Fixes

  • 使用未命名为 flywayInitializer 的自定义 FlywayMigrationInitializer
    bean 的
    NoSuchBeanDefinitionException #18105
  • 使用未命名为 flyway 的自定义 Flyway bean 的
    NoSuchBeanDefinitionException #18102
  • 使用 Netty
    时,无效的内容类型标头值会导致请求在启用压缩时卡住 #18018
  • ApplicationContextRequestMatcher 可以使用错误的
    ApplicationContext #18012
  • Spring Boot CLI 无法始终捕获
    ctrl-c #17976
  • TypeUtils.process(TypeDescriptor,
    TypeMirror) 吞下异常消息和堆栈跟踪 #17974
  • 非 MVC Web 应用程序始终返回 404
    以查找错误 #17938
  • 使用没有 JpaVendorAdapter 的自定义
    LocalContainerEntityManagerFactoryBean
    时,DataSourceSchemaCreatedPublisher.getPersistenceProviderRootPackage
    失败并显示
    NullPointerException #17935
  • 尝试预编译 JSP 文件时,TomcatEmbeddedContext 上的
    OutOfMemoryError #17927
  • ……

其他大量 bug
修复及文档和依赖更新请查看发行说明。

(文/开源中国)    

这些启动器使开发人员能够避免冗长的配置并快速启动他们的开发。但是,由于隐藏了在后台发生的很多事情,有时候很难理解注释或只是在pom.xml中包含些依赖项就能够实现这么多功能。

Spring MVC自动配置
  • Spring Boot为Spring
    MVC提供适用于多数应用的自动配置功能。在Spring默认基础上,自动配置添加了以下特性:
  1. 引入ContentNegotiatingViewResolver和BeanNameViewResolver beans。
  2. 对静态资源的支持,包括对WebJars的支持。
  3. 自动注册Converter,GenericConverter,Formatter beans。
  4. 对HttpMessageConverters的支持。
  5. 自动注册MessageCodeResolver。
  6. 对静态index.html的支持。
  7. 对自定义Favicon的支持。
  • 如果想全面控制Spring
    MVC,你可以添加自己的@Configuration,并使用@EnableWebMvc对其注解。如果想保留Spring
    Boot
    MVC的特性,并只是添加其他的MVC配置(拦截器,formatters,视图控制器等),你可以添加自己的WebMvcConfigurerAdapter类型的@Bean(不使用@EnableWebMvc注解)。

在本文中,我们将揭开Spring
Boot的神秘面纱,看看幕后发生了什么。然后我们将使用这些概念为我们自己的自定义库创建一个启动器。

HttpMessageConverters

  • Spring
    MVC使用HttpMessageConverter接口转换HTTP请求和响应。合理的缺省值被包含的恰到好处(out
    of the
    box),例如对象可以自动转换为JSON(使用Jackson库)或XML(如果Jackson
    XML扩展可用则使用它,否则使用JAXB)。字符串默认使用UTF-8编码。

  • 如果需要添加或自定义转换器,你可以使用Spring
    Boot的HttpMessageConverters类:

import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;
@Configuration
public class MyConfiguration {
    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = ...
        HttpMessageConverter<?> another = ...
        return new HttpMessageConverters(additional, another);
    }
}
  • 任何在上下文中出现的HttpMessageConverter
    bean将会添加到converters列表,你可以通过这种方式覆盖默认的转换器(converters)。

2.1 自动配置类

当Spring
Boot启动时,它会在类路径中查找名为spring.factories的文件。该文件位于META-INF目录中。让我们看一下spring-boot-autoconfigure项目中这个文件的片段:

# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

此文件将名称映射到Spring
Boot将尝试运行的不同配置类。因此,根据这个片段,Spring
Boot将尝试运行RabbitMQ,Cassandra,MongoDB和Hibernate的所有配置类。

这些类是否实际运行将取决于类路径上是否存在依赖类。例如,如果在类路径中找到MongoDB的类,则将运行MongoAutoConfiguration,并初始化所有与mongo相关的bean。

此条件初始化由@ConditionalOnClass注释启用。让我们看一下MongoAutoConfiguration类的代码片段,看看它的用法:

@Configuration@ConditionalOnClass(MongoClient.class)@EnableConfigurationProperties(MongoProperties.class)@ConditionalOnMissingBean(type = "org.springframework.data.mongodb.MongoDbFactory")public class MongoAutoConfiguration { // configuration code}

现在如果MongoClient在类路径中可用 –
这个配置类将运行,使用默认配置设置初始化的MongoClient来填充Spring
bean工厂。

MessageCodesResolver
  • Spring
    MVC有一个策略,用于从绑定的errors产生用来渲染错误信息的错误码:MessageCodesResolver。如果设置
    spring.mvc.message-codes-resolver.format 属性为 PREFIX_ERROR_CODE
    或 POSTFIX_ERROR_CODE (具体查看
    DefaultMessageCodesResolver.Format 枚举值),Spring
    Boot会为你创建一个MessageCodesResolver。

2.2 application.properties文件中的自定义属性

Spring
Boot使用一些预先配置的默认值初始化bean。要覆盖这些默认值,我们通常会在application.properties文件中使用某个特定名称声明它们。Spring
Boot容器会自动获取这些属性。

让我们看看它是如何工作的。

在MongoAutoConfiguration的代码片段中,使用MongoProperties类声明@EnableConfigurationProperties注释,该类充当自定义属性的容器:

@ConfigurationProperties(prefix = "spring.data.mongodb")public class MongoProperties { private String host; // other fields with standard getters and setters}

前缀加上字段名称可以在application.properties文件中创建属性的名称。因此,要设置MongoDB
的主机,我们只需要在属性文件中编写以下内容:

spring.data.mongodb.host = localhost

同样,可以使用属性文件设置类中其他字段的值。

根据第2节中的概念,要创建自定义启动器,我们需要编写以下组件:

  • 我们库的自动配置类以及自定义配置的属性类。
  • 一个启动程序pom,用于引入库和autoconfigure项目的依赖项。

为了演示,我们创建了一个简单的greeter库,它将作为配置参数接收一天中不同时间的问候消息并输出问候应答消息。我们还将创建一个示例Spring
Boot应用程序来演示我们的autoconfigure和starter模块的用法。

静态内容
  • 默认情况下,Spring
    Boot从classpath下一个叫/static(/public,/resources或/META-INF/resources)的文件夹或从ServletContext根目录提供静态内容。这使用了Spring
    MVC的ResourceHttpRequestHandler,所以你可以通过添加自己的WebMvcConfigurerAdapter并覆写addResourceHandlers方法来改变这个行为(加载静态文件)。
  • 在一个单独的web应用中,容器默认的servlet是开启的,如果Spring决定不处理某些请求,默认的servlet作为一个回退(降级)将从ServletContext根目录加载内容。大多数时候,这不会发生(除非你修改默认的MVC配置),因为Spring总能够通过DispatcherServlet处理请求。
  • 此外,上述标准的静态资源位置有个例外情况是Webjars内容。任何在/webjars/**路径下的资源都将从jar文件中提供,只要它们以Webjars的格式打包。

注:如果你的应用将被打包成jar,那就不要使用src/main/webapp文件夹。尽管该文件夹是一个共同的标准,但它仅在打包成war的情况下起作用,并且如果产生一个jar,多数构建工具都会静悄悄的忽略它。

3.1 自动配置模块

模板引擎

  • 正如REST web服务,你也可以使用Spring MVC提供动态HTML内容。Spring
    MVC支持各种各样的模板技术,包括Velocity,FreeMarker和JSPs。很多其他的模板引擎也提供它们自己的Spring
    MVC集成。

Spring Boot为以下的模板引擎提供自动配置支持:

  1. FreeMarker
  2. Groovy
  3. Thymeleaf
  4. Velocity

注:如果可能的话,应该忽略JSPs,因为在内嵌的servlet容器使用它们时存在一些已知的限制。

  • 当你使用这些引擎的任何一种,并采用默认的配置,你的模板将会从src/main/resources/templates目录下自动加载。

注:IntelliJ
IDEA根据你运行应用的方式会对classpath进行不同的整理。在IDE里通过main方法运行你的应用跟从Maven或Gradle或打包好的jar中运行相比会导致不同的顺序。这可能导致Spring
Boot不能从classpath下成功地找到模板。如果遇到这个问题,你可以在IDE里重新对classpath进行排序,将模块的类和资源放到第一位。或者,你可以配置模块的前缀为classpath*:/templates/,这样会查找classpath下的所有模板目录。

我们将自动配置模块称为greeter-spring-boot-autoconfigure。该模块将有两个主要类,即GreeterProperties

它将通过application.properties文件和GreeterAutoConfiguartion设置自定义属性,这将为greeter库创建bean

让我们看看这两个类的代码:

@ConfigurationProperties(prefix = "peterwanghao.samples.greeter")public class GreeterProperties { private String userName; private String morningMessage; private String afternoonMessage; private String eveningMessage; private String nightMessage; // standard getters and setters }

@Configuration@ConditionalOnClass(Greeter.class)@EnableConfigurationProperties(GreeterProperties.class)public class GreeterAutoConfiguration { @Autowired private GreeterProperties greeterProperties; @Bean @ConditionalOnMissingBean public GreetingConfig greeterConfig() { String userName = greeterProperties.getUserName() == null ? System.getProperty("user.name") : greeterProperties.getUserName(); // .. GreetingConfig greetingConfig = new GreetingConfig(); greetingConfig.put(USER_NAME, userName); // ... return greetingConfig; } @Bean @ConditionalOnMissingBean public Greeter greeter(GreetingConfig greetingConfig) { return new Greeter(greetingConfig); }}

我们还需要在src/main/resources/META-INF目录中添加一个spring.factories文件,其中包含以下内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.peterwanghao.samples.greeter.autoconfigure.GreeterAutoConfiguration

在应用程序启动时,如果类路径中存在类Greeter,则将运行GreeterAutoConfiguration类。如果成功运行,它将通过GreeterProperties类读取属性,使用GreeterConfig和Greeter
bean 填充Spring应用程序上下文。

@ConditionalOnMissingBean注释将确保,如果他们不存在,这些bean才会创建。这使开发人员可以通过在其中一个@Configuration类中定义自己配置的bean来完全覆盖自动配置的bean

错误处理

  • Spring
    Boot默认提供一个/error映射用来以合适的方式处理所有的错误,并且它在servlet容器中注册了一个全局的错误页面。对于机器客户端(相对于浏览器而言,浏览器偏重于人的行为),它会产生一个具有详细错误,HTTP状态,异常信息的JSON响应。对于浏览器客户端,它会产生一个白色标签样式(whitelabel)的错误视图,该视图将以HTML格式显示同样的数据(可以添加一个解析为erro的View来自定义它)。为了完全替换默认的行为,你可以实现ErrorController,并注册一个该类型的bean定义,或简单地添加一个ErrorAttributes类型的bean以使用现存的机制,只是替换显示的内容。

  • 如果在某些条件下需要比较多的错误页面,内嵌的servlet容器提供了一个统一的Java
    DSL(领域特定语言)来自定义错误处理。 示例:

@Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
    return new MyCustomizer();
}
// ...
private static class MyCustomizer implements EmbeddedServletContainerCustomizer   {
    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        container.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
    }
}
  • 你也可以使用常规的Spring
    MVC特性来处理错误,比如@ExceptionHandler方法和@ControllerAdvice。ErrorController将会捡起任何没有处理的异常。

  • 如果你为一个路径注册一个ErrorPage,最终被一个过滤器(Filter)处理(对于一些非Spring
    web框架,像Jersey和Wicket这很常见),然后过滤器需要显式注册为一个ERROR分发器(dispatcher)。

@Bean
public FilterRegistrationBean myFilter() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new MyFilter());
...
    registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
    return registration;
}

注:默认的FilterRegistrationBean没有包含ERROR分发器类型。

3.2 创建pom.xml

现在让我们创建一个启动程序pom,它将为自动配置模块和greeter库带来依赖关系。

根据命名约定,所有不由核心Spring
Boot团队管理的启动器应该以库名称开头,后面跟后缀-spring-boot-starter。所以我们将把我们的启动器称为greeter-spring-boot-starter:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.peterwanghao.samples.springboot</groupId> <artifactId>spring-boot-custom-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>greeter-spring-boot-starter</artifactId> <name>greeter-spring-boot-starter</name> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>com.peterwanghao.samples.springboot</groupId> <artifactId>greeter-spring-boot-autoconfigure</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>com.peterwanghao.samples.springboot</groupId> <artifactId>greeter-library</artifactId> <version>${greeter.version}</version> </dependency> </dependencies> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <greeter.version>0.0.1-SNAPSHOT</greeter.version> <spring-boot.version>2.1.1.RELEASE</spring-boot.version> </properties></project>

让我们创建一个使用启动器的应用greeter-spring-boot-sample-app。在pom.xml中,我们需要将其添加为依赖项:

<dependency> <groupId>com.peterwanghao.samples.springboot</groupId> <artifactId>greeter-spring-boot-starter</artifactId> <version>${greeter-starter.version}</version></dependency>

Spring Boot将自动配置所有内容,我们将准备好注入和使用Greeter bean。

让我们通过使用peterwanghao.samples.greeter前缀在application.properties文件中定义GreeterProperties的一些属性值来改变它们的一些默认值:

peterwanghao.samples.greeter.userName=Peterpeterwanghao.samples.greeter.afternoonMessage=WohaAfternoon

最后,让我们在我们的应用程序中使用Greeter bean:

@SpringBootApplicationpublic class GreeterSampleApplication implements CommandLineRunner { @Autowired private Greeter greeter; public static void main(String[] args) { SpringApplication.run(GreeterSampleApplication.class, args); } public void run(String... args) throws Exception { String message = greeter.greet(); System.out.println; }}

在这个简单教程中,我们专注于创建自定义Spring
Boot启动器,以及这些启动器如何与自动配置机制一起工作 –
通过后台工作以消除大量手动配置。

我们在本文中创建的完整源代码都可以在GitHub上找到。

Spring HATEOAS

  • 如果你正在开发一个使用超媒体的RESTful API,Spring Boot将为Spring
    HATEOAS提供自动配置,这在多数应用中都工作良好。自动配置替换了对使用@EnableHypermediaSupport的需求,并注册一定数量的beans来简化构建基于超媒体的应用,这些beans包括一个LinkDiscoverer和配置好的用于将响应正确编排为想要的表示的ObjectMapper。ObjectMapper可以根据spring.jackson.*属性或一个存在的Jackson2ObjectMapperBuilder
    bean进行自定义。

  • 通过使用@EnableHypermediaSupport,你可以控制Spring
    HATEOAS的配置。注意这会禁用上述的对ObjectMapper的自定义。

JAX-RS和Jersey

  • 如果喜欢JAX-RS为REST端点提供的编程模型,你可以使用可用的实现替代Spring
    MVC。如果在你的应用上下文中将Jersey 1.x和Apache
    Celtix的Servlet或Filter注册为一个@Bean,那它们工作的相当好。Jersey
    2.x有一些原生的Spring支持,所以我们会在Spring
    Boot为它提供自动配置支持,连同一个启动器(starter)。
  • 想要开始使用Jersey
    2.x只需要加入spring-boot-starter-jersey依赖,然后你需要一个ResourceConfig类型的@Bean,用于注册所有的端点(endpoints)。

@Component
public class JerseyConfig extends ResourceConfig {
    public JerseyConfig() {
        register(Endpoint.class);
    }
}
  • 所有注册的端点都应该被@Components和HTTP资源annotations(比如@GET)注解。

@Component
@Path("/hello")
public class Endpoint {
    @GET
    public String message() {
        return "Hello";
    }
}
  • 由于Endpoint是一个Spring组件(@Component),所以它的生命周期受Spring管理,并且你可以使用@Autowired添加依赖及使用@Value注入外部配置。Jersey
    servlet将被注册,并默认映射到/*。你可以将@ApplicationPath添加到ResourceConfig来改变该映射。

  • 默认情况下,Jersey将在一个ServletRegistrationBean类型的@Bean中被设置成名称为jerseyServletRegistration的Servlet。通过创建自己的相同名称的bean,你可以禁止或覆盖这个bean。你也可以通过设置
    spring.jersey.type=filter
    来使用一个Filter代替Servlet(在这种情况下,被覆盖或替换的@Bean是jerseyFilterRegistration)。该servlet有@Order属性,你可以通过
    spring.jersey.filter.order
    进行设置。不管是Servlet还是Filter注册都可以使用spring.jersey.init.*定义一个属性集合作为初始化参数传递过去。

内嵌servlet容器支持

  • Spring Boot支持内嵌的Tomcat,
    Jetty和Undertow服务器。多数开发者只需要使用合适的’Starter
    POM’来获取一个完全配置好的实例即可。默认情况下,内嵌的服务器会在8080端口监听HTTP请求。

Servlets和Filters

  • 当使用内嵌的servlet容器时,你可以直接将servlet和filter注册为Spring的beans。在配置期间,如果你想引用来自application.properties的值,这是非常方便的。默认情况下,如果上下文只包含单一的Servlet,那它将被映射到根路径(/)。在多Servlet
    beans的情况下,bean的名称将被用作路径的前缀。过滤器会被映射到/*。
  • 如果基于约定(convention-based)的映射不够灵活,你可以使用ServletRegistrationBean和FilterRegistrationBean类实现完全的控制。如果你的bean实现了ServletContextInitializer接口,也可以直接注册它们。
EmbeddedWebApplicationContext
  • Spring
    Boot底层使用了一个新的ApplicationContext类型,用于对内嵌servlet容器的支持。EmbeddedWebApplicationContext是一个特殊类型的WebApplicationContext,它通过搜索一个单一的EmbeddedServletContainerFactory
    bean来启动自己。通常,TomcatEmbeddedServletContainerFactory,JettyEmbeddedServletContainerFactory或UndertowEmbeddedServletContainerFactory将被自动配置。

注:你通常不需要知道这些实现类。大多数应用将被自动配置,并根据你的行为创建合适的ApplicationContext和EmbeddedServletContainerFactory。

自定义内嵌servlet容器
  • 常见的Servlet容器设置可以通过Spring
    Environment属性进行配置。通常,你会把这些属性定义到application.properties文件中。
    常见的服务器设置包括:
  1. server.port – 进来的HTTP请求的监听端口号
  2. server.address – 绑定的接口地址
  3. server.sessionTimeout – session超时时间
编程方式的自定义
  • 如果需要以编程的方式配置内嵌的servlet容器,你可以注册一个实现EmbeddedServletContainerCustomizer接口的Springbean。EmbeddedServletContainerCustomizer提供对ConfigurableEmbeddedServletContainer的访问,ConfigurableEmbeddedServletContainer包含很多自定义的setter方法。

import org.springframework.boot.context.embedded.*;
import org.springframework.stereotype.Component;
@Component
public class CustomizationBean implements EmbeddedServletContainerCustomizer {
    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        container.setPort(9000);
    }
}
直接自定义ConfigurableEmbeddedServletContainer
  • 如果上面的自定义手法过于受限,你可以自己注册TomcatEmbeddedServletContainerFactory,JettyEmbeddedServletContainerFactory或UndertowEmbeddedServletContainerFactory。

@Bean
public EmbeddedServletContainerFactory servletContainer() {
    TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
    factory.setPort(9000);
    factory.setSessionTimeout(10, TimeUnit.MINUTES);
    factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notfound.html");
    return factory;
}
  • 很多可选的配置都提供了setter方法,也提供了一些受保护的钩子方法以满足你的某些特殊需求。具体参考相关文档。

JSP的限制

  • 在内嵌的servlet容器中运行一个Spring
    Boot应用时(并打包成一个可执行的存档archive),容器对JSP的支持有一些限制。
  1. tomcat只支持war的打包方式,不支持可执行的jar。
  2. 内嵌的Jetty目前不支持JSPs。
  3. Undertow不支持JSPs。