本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

大多数应用程序在某些时候都需要处理输入和输出问题。 Spring Boot提供了实用程序,并与一系列技术集成,在你需要IO功能时提供帮助。 本节包括标准的IO功能,如缓存和验证,以及更高级的主题,如调度和分布式事务。 我们还将介绍调用远程REST或SOAP服务以及发送电子邮件。

1. 缓存

Spring框架提供了对透明地添加缓存到应用程序的支持。 在其核心部分,该抽象将缓存应用于方法,从而根据缓存中的可用信息减少执行的次数。 缓存逻辑的应用是透明的,对调用者没有任何干扰。 只要通过使用 @EnableCaching 注解启用缓存支持,Spring Boot就会自动配置缓存基础设施。

请查看Spring框架参考文献中的 相关章节 以了解更多细节。

简而言之,为了给你的服务的某个操作添加缓存,请在其方法中添加相关的注解,如下面的例子所示。

Java
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component
public class MyMathService {

    @Cacheable("piDecimals")
    public int computePiDecimal(int precision) {
        ...
    }

}
Kotlin
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Component

@Component
class MyMathService {

    @Cacheable("piDecimals")
    fun computePiDecimal(precision: Int): Int {
        ...
    }

}

这个例子演示了在一个潜在的高成本操作中使用缓存。 在调用 computePiDecimal 之前,该抽象概念在 piDecimals 缓存中寻找一个与 i 参数相匹配的条目。 如果找到一个条目,缓存中的内容将立即返回给调用者,并且不调用该方法。 否则,该方法被调用,并在返回值之前更新缓存。

你也可以透明地使用标准的JSR-107(JCache)注解(如 @CacheResult)。 然而,我们强烈建议你不要混合使用Spring Cache和JCache注解。

如果你不添加任何特定的缓存库,Spring Boot会自动配置一个简单的提供者,使用内存中的 concurrent map。当需要缓存的时候(比如前面例子中的 piDecimals),这个提供者就会为你创建缓存。简单的提供者并不推荐在生产中使用,但它对于开始使用并确保你了解这些功能是非常好的。当你决定使用哪个缓存提供者时,请确保阅读它的文档,以弄清楚如何配置你的应用程序所使用的缓存。几乎所有的提供者都要求你明确地配置你在应用程序中使用的每一个缓冲区。有些提供了一种方法来定制由 spring.cache.cache-names 属性定义的默认缓存。

也可以透明地 更新 或 驱逐 缓存中的数据。

1.1. 支持的缓存供应商(Provider)

缓存抽象不提供实际的存储,而是依赖于 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口实现的抽象。

如果你没有定义一个 CacheManager 类型的Bean或者一个名为 CacheResolver 的 CacheResolver (见 CachingConfigurer),Spring Boot会尝试检测以下提供者(按所示顺序)。

  1. Generic

  2. JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan 和其他)

  3. Hazelcast

  4. Infinispan

  5. Couchbase

  6. Redis

  7. Caffeine

  8. Cache2k

  9. Simple

也可以通过设置 spring.cache.type 属性来强制使用特定的缓存提供者。 如果你需要在某些环境中完全禁用缓存(如测试),请使用此属性。
使用 spring-boot-starter-cache “Starter” 来快速添加基本的缓存依赖。 “Starter” 带来了 spring-context-support。 如果你手动添加依赖,你必须包括 spring-context-support,以便使用JCache或Caffeine支持。

如果 CacheManager 是由Spring Boot自动配置的,你可以通过暴露一个实现 CacheManagerCustomizer 接口的bean,在它完全初始化之前进一步调整其配置。 下面的例子设置了一个标志,表示 null 值不应该被传递给底层map。

Java
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyCacheManagerConfiguration {

    @Bean
    public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
        return (cacheManager) -> cacheManager.setAllowNullValues(false);
    }

}
Kotlin
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer
import org.springframework.cache.concurrent.ConcurrentMapCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyCacheManagerConfiguration {

    @Bean
    fun cacheManagerCustomizer(): CacheManagerCustomizer<ConcurrentMapCacheManager> {
        return CacheManagerCustomizer { cacheManager ->
            cacheManager.isAllowNullValues = false
        }
    }

}
在前面的例子中,预计会有一个自动配置的 ConcurrentMapCacheManager。 如果不是这样(要么是你提供了自己的配置,要么是自动配置了不同的缓存提供者),customizer根本就不会被调用。 你可以有任意多的customizer,你也可以通过使用 @Order 或 Ordered 对它们进行排序。

1.1.1. 通用的缓存

如果context至少定义了一个 org.springframework.cache.Cache bean,就会使用通用的缓存。 一个封装所有该类型的Bean的 CacheManager 被创建。

1.1.2. JCache (JSR-107)

JCache 是通过classpath上存在 javax.cache.spi.CachingProvider 来引导的(也就是说,classpath上存在一个符合JSR-107标准的缓存库), JCacheCacheManager 由 spring-boot-starter-cache “Starter”.提供。各种兼容的库都可以使用,Spring Boot为Ehcache 3、Hazelcast和Infinispan提供了依赖管理。也可以添加任何其他兼容的库。

可能会出现不止一个提供者的情况,在这种情况下,必须明确指定提供者。 即使JSR-107标准没有强制执行定义配置文件位置的标准化方式,Spring Boot也会尽力适应设置具有实现细节的缓存,如下面的例子所示。

Properties
# Only necessary if more than one provider is present
spring.cache.jcache.provider=com.example.MyCachingProvider
spring.cache.jcache.config=classpath:example.xml
Yaml
# Only necessary if more than one provider is present
spring:
  cache:
    jcache:
      provider: "com.example.MyCachingProvider"
      config: "classpath:example.xml"
当一个缓存库同时提供本地实现和JSR-107支持时,Spring Boot更倾向于JSR-107支持,这样,如果你切换到不同的JSR-107实现,同样的功能也可以使用。
Spring Boot对Hazelcast有普遍支持。如果有一个 HazelcastInstance,它也会自动被 CacheManager 重用,除非指定 spring.cache.jcache.config 属性。

有两种方法可以定制底层的 javax.cache.cacheManager。

  • 通过设置 spring.cache.cache-names 属性,可以在启动时创建缓存。 如果定义了一个自定义的 javax.cache.configuration.Configuration bean,它就可以用来定制它们。

  • org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer Bean被调用,并引用 CacheManager 进行完全定制。

如果定义了一个标准的 javax.cache.CacheManager Bean,它将被自动包装在抽象所期望的 org.springframework.cache.CacheManager 实现中。 没有进一步的定制应用于它。

1.1.3. Hazelcast

Spring Boot对Hazelcast有普遍支持。如果 HazelcastInstance 已被自动配置,并且classpath上有 com.hazelcast:hazelcast-spring ,那么它就会被自动封装成 CacheManager。

Hazelcast 可以作为兼容JCache的缓存或作为兼容Spring CacheManager 的缓存。当把 spring.cache.type 设置为 hazelcast 时,Spring Boot 将使用基于 CacheManager 的实现。如果你想使用 Hazelcast 作为兼容 JCache 的缓存,请将 spring.cache.type 设置为 jcache。如果你有多个兼容JCache的缓存提供者,并想强制使用Hazelcast,你必须明确设置JCache提供者。

1.1.4. Infinispan

Infinispan 没有默认的配置文件位置,所以必须明确指定它。 否则,将使用默认的引导(bootstrap)文件。

Properties
spring.cache.infinispan.config=infinispan.xml
Yaml
spring:
  cache:
    infinispan:
      config: "infinispan.xml"

通过设置 spring.cache.cache-names 属性,可以在启动时创建缓存。 如果定义了一个自定义的 ConfigurationBuilder bean,它就可以用来定制缓存。

为了与Spring Boot的Jakarta EE 9基线兼容,必须使用Infinispan的 -jakarta 模块。 对于每一个带有 -jakarta 变体的模块,必须用变体来代替标准模块。 例如,infinispan-core-jakarta 和 infinispan-commons-jakarta 必须分别用来代替 infinispan-core 和 infinispan-commons。

1.1.5. Couchbase

如果Spring Data Couchbase可用并且配置了Couchbase,就会自动配置 CouchbaseCacheManager。通过设置 spring.cache.cache-names 属性可以在启动时创建额外的缓存,并且可以通过 spring.cache.couchbase.* 属性配置缓存的默认值。例如,下面的配置创建了 cache1 和 cache2 缓存,条目过期时间为10分钟。

Properties
spring.cache.cache-names=cache1,cache2
spring.cache.couchbase.expiration=10m
Yaml
spring:
  cache:
    cache-names: "cache1,cache2"
    couchbase:
      expiration: "10m"

如果你需要对配置进行更多的控制,可以考虑注册一个 CouchbaseCacheManagerBuilderCustomizer bean。 下面的例子显示了一个customizer,它为 cache1 和 cache2 配置了一个特定的条目到期。

Java
import java.time.Duration;

import org.springframework.boot.autoconfigure.cache.CouchbaseCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration;

@Configuration(proxyBeanMethods = false)
public class MyCouchbaseCacheManagerConfiguration {

    @Bean
    public CouchbaseCacheManagerBuilderCustomizer myCouchbaseCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration("cache1", CouchbaseCacheConfiguration
                        .defaultCacheConfig().entryExpiry(Duration.ofSeconds(10)))
                .withCacheConfiguration("cache2", CouchbaseCacheConfiguration
                        .defaultCacheConfig().entryExpiry(Duration.ofMinutes(1)));

    }

}
Kotlin
import org.springframework.boot.autoconfigure.cache.CouchbaseCacheManagerBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration
import java.time.Duration

@Configuration(proxyBeanMethods = false)
class MyCouchbaseCacheManagerConfiguration {

    @Bean
    fun myCouchbaseCacheManagerBuilderCustomizer(): CouchbaseCacheManagerBuilderCustomizer {
        return CouchbaseCacheManagerBuilderCustomizer { builder ->
            builder
                .withCacheConfiguration(
                    "cache1", CouchbaseCacheConfiguration
                        .defaultCacheConfig().entryExpiry(Duration.ofSeconds(10))
                )
                .withCacheConfiguration(
                    "cache2", CouchbaseCacheConfiguration
                        .defaultCacheConfig().entryExpiry(Duration.ofMinutes(1))
                )
        }
    }

}

1.1.6. Redis

如果 Redis 可用且已配置,则会自动配置一个 RedisCacheManager。通过设置 spring.cache.cache-names 属性,可以在启动时创建额外的缓存,缓存默认值可以通过使用 spring.cache.redis.* 属性进行配置。例如,下面的配置创建了 cache1 和 cache2 缓存,生存时间为10分钟。

Properties
spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=10m
Yaml
spring:
  cache:
    cache-names: "cache1,cache2"
    redis:
      time-to-live: "10m"
默认情况下,添加了一个key的前缀,这样,如果两个独立的缓存使用同一个键,Redis就不会有重叠的key,也不能返回无效的值。 如果你创建了自己的 RedisCacheManager,我们强烈建议保持这一设置的启用。
你可以通过添加你自己的 RedisCacheConfiguration @Bean 来完全控制默认配置。 如果你需要定制默认的序列化策略,这将会非常有用。

如果你需要对配置进行更多的控制,可以考虑注册一个 RedisCacheManagerBuilderCustomizer bean。 下面的例子显示了一个customizer,它为 cache1 和 cache2 配置了一个特定的生存时间。

Java
import java.time.Duration;

import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;

@Configuration(proxyBeanMethods = false)
public class MyRedisCacheManagerConfiguration {

    @Bean
    public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration("cache1", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofSeconds(10)))
                .withCacheConfiguration("cache2", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofMinutes(1)));

    }

}
Kotlin
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.cache.RedisCacheConfiguration
import java.time.Duration

@Configuration(proxyBeanMethods = false)
class MyRedisCacheManagerConfiguration {

    @Bean
    fun myRedisCacheManagerBuilderCustomizer(): RedisCacheManagerBuilderCustomizer {
        return RedisCacheManagerBuilderCustomizer { builder ->
            builder
                .withCacheConfiguration(
                    "cache1", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofSeconds(10))
                )
                .withCacheConfiguration(
                    "cache2", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofMinutes(1))
                )
        }
    }

}

1.1.7. Caffeine

Caffeine 是Java 8对Guava缓存的重写,取代了对Guava的支持。如果有Caffeine,就会自动配置一个 CaffeineCacheManager(由 spring-boot-starter-cache “Starter” 提供)。缓存可以通过设置 spring.cache.cache-names 属性在启动时创建,并可以通过以下方式之一进行定制(按所示顺序)。

  1. 一个由 spring.cache.caffeine.spec 定义的缓存规范。

  2. 定义了一个 com.github.benmanes.caffeine.cache.CaffeineSpec Bean。

  3. 定义了一个 com.github.benmanes.caffeine.cache.Caffeine Bean。

例如,下面的配置创建了 cache1 和 cache2 缓存,最大容量为500,生存时间为10分钟。

Properties
spring.cache.cache-names=cache1,cache2
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
Yaml
spring:
  cache:
    cache-names: "cache1,cache2"
    caffeine:
      spec: "maximumSize=500,expireAfterAccess=600s"

如果定义了一个 com.github.benmanes.caffeine.cache.CacheLoader bean,它就会自动与 CaffeineCacheManager 相关联。 由于 CacheLoader 将与缓存管理器管理的所有缓存相关联,它必须被定义为 CacheLoader<Object, Object>。 自动配置会忽略任何其他的通用类型。

1.1.8. Cache2k

Cache2k 是一个内存缓存。如果有Cache2k spring集成,就会自动配置一个 SpringCache2kCacheManager。

通过设置 spring.cache.cache-names 属性,可以在启动时创建缓存。 缓存的默认值可以通过 Cache2kBuilderCustomizer bean来定制。 下面的例子显示了一个customizer,它将缓存的容量配置为200条,过期时间为5分钟。

Java
import java.util.concurrent.TimeUnit;

import org.springframework.boot.autoconfigure.cache.Cache2kBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyCache2kDefaultsConfiguration {

    @Bean
    public Cache2kBuilderCustomizer myCache2kDefaultsCustomizer() {
        return (builder) -> builder.entryCapacity(200)
                .expireAfterWrite(5, TimeUnit.MINUTES);
    }

}
Kotlin
import org.springframework.boot.autoconfigure.cache.Cache2kBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.TimeUnit

@Configuration(proxyBeanMethods = false)
class MyCache2kDefaultsConfiguration {

    @Bean
    fun myCache2kDefaultsCustomizer(): Cache2kBuilderCustomizer {
        return Cache2kBuilderCustomizer { builder ->
            builder.entryCapacity(200)
                .expireAfterWrite(5, TimeUnit.MINUTES)
        }
    }
}

1.1.9. Simple

如果找不到其他的提供者,就配置一个简单的实现,使用 ConcurrentHashMap 作为缓存存储。 如果你的应用程序中没有缓存库,这就是默认的。 默认情况下,缓存是根据需要创建的,但你可以通过设置 cache-names 属性来限制可用的缓存列表。 例如,如果你只想要 cache1 和 cache2 缓存,设置 cache-names 属性如下。

Properties
spring.cache.cache-names=cache1,cache2
Yaml
spring:
  cache:
    cache-names: "cache1,cache2"

如果你这样做了,而你的应用程序使用了一个没有列出的缓存,那么在运行时需要缓存时就会失败,但在启动时不会。 这与 "真正的" 缓存提供者在你使用未声明的缓存时的行为类似。

1.1.10. None

当 @EnableCaching 出现在你的配置中时,预计也会有一个合适的缓存配置。 如果你需要在某些环境中完全禁用缓存,请将缓存类型强制为 none,以使用一个无操作的实现,如下例所示。

Properties
spring.cache.type=none
Yaml
spring:
  cache:
    type: "none"

2. Hazelcast

如果 Hazelcast 在classpath上并且找到了合适的配置,Spring Boot就会自动配置一个 HazelcastInstance,你可以把它注入到你的应用程序中。

Spring Boot首先尝试通过检查以下配置选项来创建一个客户端。

  • 存在一个 com.hazelcast.client.config.ClientConfig Bean。

  • 一个由 spring.hazelcast.config 属性定义的配置文件。

  • 存在 hazelcast.client.config 系统属性(system property)。

  • 在工作目录或classpath根目录下有一个 hazelcast-client.xml。

  • 工作目录或classpath根目录中的 hazelcast-client.yaml(或 hazelcast-client.yml)。

如果不能创建一个客户端,Spring Boot会尝试配置一个嵌入式服务器。 如果你定义了一个 com.hazelcast.config.Config Bean,Spring Boot就会使用该Bean。 如果你的配置定义了一个实例名称,Spring Boot会尝试定位一个现有的实例,而不是创建一个新的。

你也可以通过配置指定要使用的Hazelcast配置文件,如下面的例子所示。

Properties
spring.hazelcast.config=classpath:config/my-hazelcast.xml
Yaml
spring:
  hazelcast:
    config: "classpath:config/my-hazelcast.xml"

否则,Spring Boot会尝试从默认位置找到Hazelcast配置:工作目录或classpath根部的 hazelcast.xml,或相同位置的 YAML 对应文件。我们也会检查 hazelcast.config 系统属性是否被设置。更多细节请参见 Hazelcast 文档 。

默认情况下,支持Hazelcast组件上的 @SpringAware。 ManagementContext 可以通过声明一个 HazelcastConfigCustomizer bean来重写,其 @Order 高于零。
Spring Boot对Hazelcast也有 明确的缓存支持。如果启用了缓存,HazelcastInstance 会被自动封装在 CacheManager 实现中。

3. Quartz Scheduler

Spring Boot为与 Quartz scheduler 合作提供了一些便利,包括 spring-boot-starter-quartz “Starter”。如果Quartz可用,就会自动配置一个 Scheduler(通过 SchedulerFactoryBean 的抽象)。

以下类型的Bean会被自动拾取并与 Scheduler 关联。

  • JobDetail: 定义了一个特定的 Job。 JobDetail 实例可以通过 JobBuilder API建立。

  • Calendar.

  • Trigger: 定义了一个特定的Job何时被触发。

默认情况下,使用内存中的 JobStore。 然而,如果你的应用程序中有一个 DataSource Bean,并且相应地配置了 spring.quartz.job-store-type 属性,也可以配置一个基于JDBC的store,如下面的例子所示。

Properties
spring.quartz.job-store-type=jdbc
Yaml
spring:
  quartz:
    job-store-type: "jdbc"

当使用JDBC存储时,schema可以在启动时被初始化,如下面的例子中所示。

Properties
spring.quartz.jdbc.initialize-schema=always
Yaml
spring:
  quartz:
    jdbc:
      initialize-schema: "always"
默认情况下,数据库是通过使用Quartz库提供的标准脚本来检测和初始化的。 这些脚本会删除现有的表,在每次重启时删除所有触发器。 也可以通过设置 spring.quartz.jdbc.schema 属性提供一个自定义脚本。

要让Quartz使用应用程序主 DataSource 以外的 DataSource,可以声明一个 DataSource bean,用 @QuartzDataSource 注释其 @Bean 方法。这样做可以确保Quartz特定的数据源被 SchedulerFactoryBean 和schema初始化所使用。同样地,为了让Quartz使用应用程序的主 TransactionManager 以外的 TransactionManager,需要声明一个 TransactionManager Bean,用 @QuartzTransactionManager 注释其 @Bean 方法。

默认情况下,通过配置创建的Job不会覆盖已经从持久性 JobStore 中读取的已注册Job。 要启用覆盖现有作业定义,请设置 spring.quartz.overwrite-existing-jobs 属性。

Quartz Scheduler的配置可以使用 spring.quartz 属性和 SchedulerFactoryBeanCustomizer Bean来定制,它允许以编程方式定制 SchedulerFactoryBean。 高级quartz配置属性可以使用 spring.quartz.properties.* 来定制。

特别是,Executor bean与Scheduler没有关联,因为Quartz提供了一种通过 spring.quartz.properties 来配置调度器的方法。 如果你需要定制任务执行器,可以考虑实现 SchedulerFactoryBeanCustomizer。

Job可以定义setter来注入数据映射属性。 常规的Bean也可以用类似的方式注入,如下面的例子中所示。

Java
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import org.springframework.scheduling.quartz.QuartzJobBean;

public class MySampleJob extends QuartzJobBean {

    // fields ...

    private MyService myService;

    private String name;

    // Inject "MyService" bean
    public void setMyService(MyService myService) {
        this.myService = myService;
    }

    // Inject the "name" job data property
    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        this.myService.someMethod(context.getFireTime(), this.name);
    }

}
Kotlin
import org.quartz.JobExecutionContext
import org.springframework.scheduling.quartz.QuartzJobBean

class MySampleJob : QuartzJobBean() {

    // fields ...

    private var myService: MyService? = null

    private var name: String? = null

    // Inject "MyService" bean
    fun setMyService(myService: MyService?) {
        this.myService = myService
    }

    // Inject the "name" job data property
    fun setName(name: String?) {
        this.name = name
    }

    override fun executeInternal(context: JobExecutionContext) {
        myService!!.someMethod(context.fireTime, name)
    }

}

4. 发送 Email

Spring框架通过使用 JavaMailSender 接口为发送电子邮件提供了一个抽象,Spring Boot为其提供了自动配置以及一个starter模块。

关于如何使用 JavaMailSender 的详细解释,请参见 参考文档。

如果 spring.mail.host 和相关库(如 spring-boot-starter-mail 所定义的)可用,如果没有,就会创建一个默认的 JavaMailSender。发件人可以通过 spring.mail 命名空间的配置项进一步定制。参见 MailProperties 获取更多细节。

特别是,某些默认的超时值是无限的,你可能想改变它,以避免线程被无响应的邮件服务器阻塞,如下例所示。

Properties
spring.mail.properties[mail.smtp.connectiontimeout]=5000
spring.mail.properties[mail.smtp.timeout]=3000
spring.mail.properties[mail.smtp.writetimeout]=5000
Yaml
spring:
  mail:
    properties:
      "[mail.smtp.connectiontimeout]": 5000
      "[mail.smtp.timeout]": 3000
      "[mail.smtp.writetimeout]": 5000

也可以用JNDI中现有的 Session 来配置 JavaMailSender。

Properties
spring.mail.jndi-name=mail/Session
Yaml
spring:
  mail:
    jndi-name: "mail/Session"

当 jndi-name 被设置时,它优先于所有其他与Session相关的设置。

5. 校验(Validation)

只要在classpath上有JSR-303实现(如Hibernate验证器),Bean Validation 1.1所支持的方法验证功能就会自动启用。 这让Bean方法在其参数和/或返回值上被注解为 jakarta.validation 约束。 有这种注解的方法的目标类需要在类型级别上使用 @Validated 注解,以便它们的方法能够被搜索到内联约束注解。

例如,以下服务触发了对第一个参数的验证,确保其大小在8和10之间。

Java
import jakarta.validation.constraints.Size;

import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Service
@Validated
public class MyBean {

    public Archive findByCodeAndAuthor(@Size(min = 8, max = 10) String code, Author author) {
        return ...
    }

}
Kotlin
import jakarta.validation.constraints.Size
import org.springframework.stereotype.Service
import org.springframework.validation.annotation.Validated

@Service
@Validated
class MyBean {

    fun findByCodeAndAuthor(code: @Size(min = 8, max = 10) String?, author: Author?): Archive? {
        return null
    }

}

在解决约束信息中的 {parameters} 时,会使用应用程序的 MessageSource。这允许你使用你的应用程序的 messages.properties 文件来处理 Bean Validation 消息。一旦参数被解析,消息插值将使用Bean Validation的默认interpolator完成。

为了定制用于构建 ValidatorFactory 的 Configuration,定义一个 ValidationConfigurationCustomizer bean。 当定义了多个customizer Bean时,它们会根据它们的 @Order 注解或 Ordered 实现按顺序被调用。

6. 调用REST服务

如果你的应用程序调用远程REST服务,Spring Boot使用 RestTemplate 或 WebClient 使之非常方便。

6.1. RestTemplate

如果你需要从你的应用程序调用远程REST服务,你可以使用Spring框架的 RestTemplate 类。 由于 RestTemplate 实例在使用前往往需要进行定制,Spring Boot没有提供任何单一的自动配置的 RestTemplate bean。 然而,它确实自动配置了一个 RestTemplateBuilder,在需要时可以用来创建 RestTemplate 实例。 自动配置的 RestTemplateBuilder 确保合理的 HttpMessageConverters 应用于 RestTemplate 实例。

下面的代码显示了一个典型的例子。

Java
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MyService {

    private final RestTemplate restTemplate;

    public MyService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    public Details someRestCall(String name) {
        return this.restTemplate.getForObject("/{name}/details", Details.class, name);
    }

}
Kotlin
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class MyService(restTemplateBuilder: RestTemplateBuilder) {

    private val restTemplate: RestTemplate

    init {
        restTemplate = restTemplateBuilder.build()
    }

    fun someRestCall(name: String): Details {
        return restTemplate.getForObject("/{name}/details", Details::class.java, name)!!
    }

}

RestTemplateBuilder 包括许多有用的方法,可以用来快速配置一个 RestTemplate。例如,要添加 BASIC 认证支持,你可以使用 builder.basicAuthentication("user", "password").build()。

6.1.1. 定制 RestTemplate

RestTemplate 的定制有三种主要方法,取决于你希望定制的适用范围有多广。

为了使任何定制的范围尽可能的窄,注入自动配置的 RestTemplateBuilder,然后根据需要调用其方法。 每个方法的调用都会返回一个新的 RestTemplateBuilder 实例,所以自定义只影响构建器的这个用途。

要进行全应用的、附加的定制,请使用 RestTemplateCustomizer bean。 所有这样的Bean会自动注册到自动配置的 RestTemplateBuilder 中,并应用到任何用它建立的模板中。

下面的例子显示了一个自定义器,它配置了对除 192.168.0.5 以外的所有主机使用代理。

Java
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.protocol.HttpContext;

import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public class MyRestTemplateCustomizer implements RestTemplateCustomizer {

    @Override
    public void customize(RestTemplate restTemplate) {
        HttpRoutePlanner routePlanner = new CustomRoutePlanner(new HttpHost("proxy.example.com"));
        HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build();
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
    }

    static class CustomRoutePlanner extends DefaultProxyRoutePlanner {

        CustomRoutePlanner(HttpHost proxy) {
            super(proxy);
        }

        @Override
        protected HttpHost determineProxy(HttpHost target, HttpContext context) throws HttpException {
            if (target.getHostName().equals("192.168.0.5")) {
                return null;
            }
            return super.determineProxy(target, context);
        }

    }

}
Kotlin
import org.apache.hc.client5.http.classic.HttpClient
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner
import org.apache.hc.client5.http.routing.HttpRoutePlanner
import org.apache.hc.core5.http.HttpException
import org.apache.hc.core5.http.HttpHost
import org.apache.hc.core5.http.protocol.HttpContext
import org.springframework.boot.web.client.RestTemplateCustomizer
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
import org.springframework.web.client.RestTemplate

class MyRestTemplateCustomizer : RestTemplateCustomizer {

    override fun customize(restTemplate: RestTemplate) {
        val routePlanner: HttpRoutePlanner = CustomRoutePlanner(HttpHost("proxy.example.com"))
        val httpClient: HttpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build()
        restTemplate.requestFactory = HttpComponentsClientHttpRequestFactory(httpClient)
    }

    internal class CustomRoutePlanner(proxy: HttpHost?) : DefaultProxyRoutePlanner(proxy) {

        @Throws(HttpException::class)
        public override fun determineProxy(target: HttpHost, context: HttpContext): HttpHost? {
            if (target.hostName == "192.168.0.5") {
                return null
            }
            return  super.determineProxy(target, context)
        }

    }

}

最后,你可以定义你自己的 RestTemplateBuilder bean。 这样做将取代自动配置的构建器。 如果你想让任何 RestTemplateCustomizer Bean 应用于你的自定义构建器,就像自动配置那样,用 RestTemplateBuilderConfigurer 来配置它。 下面的例子展示了一个 RestTemplateBuilder,它与Spring Boot的自动配置所做的一致,只是还指定了自定义连接和读取超时。

Java
import java.time.Duration;

import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyRestTemplateBuilderConfiguration {

    @Bean
    public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {
        return configurer.configure(new RestTemplateBuilder())
            .setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(2));
    }

}
Kotlin
import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Duration

@Configuration(proxyBeanMethods = false)
class MyRestTemplateBuilderConfiguration {

    @Bean
    fun restTemplateBuilder(configurer: RestTemplateBuilderConfigurer): RestTemplateBuilder {
        return configurer.configure(RestTemplateBuilder()).setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(2))
    }

}

最极端(也很少使用)的选择是创建你自己的 RestTemplateBuilder bean,而不使用configurer。 除了取代自动配置的生成器外,这也会阻止任何 RestTemplateCustomizer Bean被使用。

6.1.2. RestTemplate SSL 的支持

如果你需要在 RestTemplate 上进行自定义SSL配置,你可以将 SSL bundle 应用到 RestTemplateBuilder 上,如本例所示:

Java
import org.springframework.boot.docs.io.restclient.resttemplate.Details;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MyService {

    private final RestTemplate restTemplate;

    public MyService(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) {
        this.restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build();
    }

    public Details someRestCall(String name) {
        return this.restTemplate.getForObject("/{name}/details", Details.class, name);
    }

}
Kotlin
import org.springframework.boot.docs.io.restclient.resttemplate.Details
import org.springframework.boot.ssl.SslBundles
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class MyService(restTemplateBuilder: RestTemplateBuilder, sslBundles: SslBundles) {

    private val restTemplate: RestTemplate

    init {
        restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build()
    }

    fun someRestCall(name: String): Details {
        return restTemplate.getForObject("/{name}/details", Details::class.java, name)!!
    }

}

6.2. WebClient

如果你的classpath上有Spring WebFlux,你也可以选择使用 WebClient 来调用远程REST服务。与 RestTemplate 相比,这个客户端有更多的功能感,而且是完全响应式的。你可以在Spring框架文档中专门的 部分 了解更多关于 WebClient 的信息。

Spring Boot为你创建并预先配置了一个 WebClient.Builder。强烈建议在你的组件中注入它,用它来创建 WebClient 实例。Spring Boot正在配置该生成器,以共享HTTP资源,反映出以与服务器相同的方式设置的编解码器(见WebFlux HTTP编解码器自动配置),以及更多。

下面的代码显示了一个典型的例子。

Java
import reactor.core.publisher.Mono;

import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

@Service
public class MyService {

    private final WebClient webClient;

    public MyService(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("https://example.org").build();
    }

    public Mono<Details> someRestCall(String name) {
        return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
    }

}
Kotlin
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

@Service
class MyService(webClientBuilder: WebClient.Builder) {

    private val webClient: WebClient

    init {
        webClient = webClientBuilder.baseUrl("https://example.org").build()
    }

    fun someRestCall(name: String?): Mono<Details> {
        return webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details::class.java)
    }

}

6.2.1. WebClient 运行时

Spring Boot会根据应用程序classpath上可用的库,自动检测使用哪个 ClientHttpConnector 来驱动 WebClient。 目前,支持Reactor Netty、Jetty ReactiveStream 客户端、Apache HttpClient和JDK的HttpClient。

spring-boot-starter-webflux 启动器默认依赖 io.projectreactor.netty:reactor-netty,它同时带来了服务器和客户端的实现。 如果你选择使用Jetty作为响应式服务器,你应该添加对Jetty响应式HTTP客户端库的依赖,org.eclipse.jetty:jetty-reactive-httpclient。 在服务器和客户端使用相同的技术有其优势,因为它将自动在客户端和服务器之间共享HTTP资源。

开发者可以通过提供自定义的 ReactorResourceFactory 或 JettyResourceFactory bean来覆盖Jetty和Reactor Netty的资源配置—​这将同时应用于客户端和服务器。

如果你希望覆盖客户端的选择,你可以定义你自己的 ClientHttpConnector bean,并完全控制客户端的配置。

6.2.2. 定制 WebClient

WebClient 的定制有三种主要方式,取决于你希望定制的适用范围。

为了使任何定制的范围尽可能的小,注入自动配置的 WebClient.Builder,然后根据需要调用其方法。 WebClient.Builder 实例是有状态的。构建器上的任何变化都会反映在随后用它创建的所有客户端上。 如果你想用同一个生成器创建多个客户端,你也可以考虑用 WebClient.Builder other = builder.clone(); 来克隆生成器。

要对所有的 WebClient.Builder 实例进行全应用的、附加的定制,你可以声明 WebClientCustomizer Bean,并在注入点本地改变 WebClient.Builder。

最后,你可以退回到原来的API,使用 WebClient.create()。 在这种情况下,没有自动配置或 WebClientCustomizer 被应用。

6.2.3. WebClient SSL 的支持

如果你需要在 WebClient 使用的 ClientHttpConnector 上进行自定义SSL配置,你可以注入一个 WebClientSsl 实例,该实例可以与 builder 的 apply 方法一起使用。

WebClientSsl 接口提供了对你在 application.properties 或 application.yaml 文件中定义的任何 SSL bundles 的访问。

下面的代码显示了一个典型的例子:

Java
import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientSsl;
import org.springframework.boot.docs.io.restclient.webclient.Details;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

@Service
public class MyService {

    private final WebClient webClient;

    public MyService(WebClient.Builder webClientBuilder, WebClientSsl ssl) {
        this.webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();
    }

    public Mono<Details> someRestCall(String name) {
        return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
    }

}
Kotlin
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientSsl
import org.springframework.boot.docs.io.restclient.webclient.Details
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

@Service
class MyService(webClientBuilder: WebClient.Builder, ssl: WebClientSsl) {

    private val webClient: WebClient

    init {
        webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build()
    }

    fun someRestCall(name: String?): Mono<Details> {
        return webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details::class.java)
    }

}

7. Web Services

Spring Boot提供了Web Services的自动配置,因此你所要做的就是定义你的 Endpoints。

Spring Web Services功能可以通过 spring-boot-starter-webservices 模块轻松访问。

SimpleWsdl11Definition 和 SimpleXsdSchema Bean可以分别为你的WSDLs和XSDs自动创建。 要做到这一点,请配置它们的位置,如下面的例子所示。

Properties
spring.webservices.wsdl-locations=classpath:/wsdl
Yaml
spring:
  webservices:
    wsdl-locations: "classpath:/wsdl"

7.1. 使用 WebServiceTemplate 调用 Web Services

如果你需要从你的应用程序中调用远程Web services,你可以使用 WebServiceTemplate 类。 由于 WebServiceTemplate 实例在使用前通常需要进行定制,Spring Boot没有提供任何单一的自动配置的 WebServiceTemplate bean。 然而,它确实自动配置了一个 WebServiceTemplateBuilder,在需要时可以用来创建 WebServiceTemplate 实例。

下面的代码显示了一个典型的例子。

Java
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.soap.client.core.SoapActionCallback;

@Service
public class MyService {

    private final WebServiceTemplate webServiceTemplate;

    public MyService(WebServiceTemplateBuilder webServiceTemplateBuilder) {
        this.webServiceTemplate = webServiceTemplateBuilder.build();
    }

    public SomeResponse someWsCall(SomeRequest detailsReq) {
        return (SomeResponse) this.webServiceTemplate.marshalSendAndReceive(detailsReq,
                new SoapActionCallback("https://ws.example.com/action"));
    }

}
Kotlin
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder
import org.springframework.stereotype.Service
import org.springframework.ws.client.core.WebServiceTemplate
import org.springframework.ws.soap.client.core.SoapActionCallback

@Service
class MyService(webServiceTemplateBuilder: WebServiceTemplateBuilder) {

    private val webServiceTemplate: WebServiceTemplate

    init {
        webServiceTemplate = webServiceTemplateBuilder.build()
    }

    fun someWsCall(detailsReq: SomeRequest?): SomeResponse {
        return webServiceTemplate.marshalSendAndReceive(
            detailsReq,
            SoapActionCallback("https://ws.example.com/action")
        ) as SomeResponse
    }

}

默认情况下,WebServiceTemplateBuilder 使用classpath上可用的HTTP客户端库检测一个合适的基于HTTP的 WebServiceMessageSender。 你还可以自定义读取和连接超时,如下。

Java
import java.time.Duration;

import org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder;
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.transport.WebServiceMessageSender;

@Configuration(proxyBeanMethods = false)
public class MyWebServiceTemplateConfiguration {

    @Bean
    public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) {
        WebServiceMessageSender sender = new HttpWebServiceMessageSenderBuilder()
                .setConnectTimeout(Duration.ofSeconds(5))
                .setReadTimeout(Duration.ofSeconds(2))
                .build();
        return builder.messageSenders(sender).build();
    }

}
Kotlin
import org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.ws.client.core.WebServiceTemplate
import java.time.Duration

@Configuration(proxyBeanMethods = false)
class MyWebServiceTemplateConfiguration {

    @Bean
    fun webServiceTemplate(builder: WebServiceTemplateBuilder): WebServiceTemplate {
        val sender = HttpWebServiceMessageSenderBuilder()
            .setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(2))
            .build()
        return builder.messageSenders(sender).build()
    }

}

8. JTA 和 分布式事务

Spring Boot通过使用从JNDI检索的事务管理器,支持跨多个XA资源的分布式JTA事务。

当检测到JTA环境时,Spring的 JtaTransactionManager 被用来管理事务。 自动配置的JMS、DataSource和JPA Bean被升级以支持XA事务。 你可以使用标准的Spring习语,如 @Transactional,来参与分布式事务。 如果你在JTA环境中,仍然想使用本地事务,你可以将 spring.jta.enabled 属性设置为 false,以禁用JTA自动配置。

8.1. 使用Jakarta EE管理的事务管理器

如果你将Spring Boot应用程序打包成 war 或 ear 文件,并将其部署到Jakarta EE应用服务器上,你可以使用应用服务器的内置事务管理器。 Spring Boot试图通过查看常见的JNDI位置(java:comp/UserTransaction,java:comp/TransactionManager,等等)来自动配置一个事务管理器。 当使用应用服务器提供的事务服务时,你一般也要确保所有资源都由服务器管理,并通过JNDI暴露。 Spring Boot试图通过在JNDI路径(java:/JmsXA 或 java:/XAConnectionFactory)寻找 ConnectionFactory 来自动配置JMS,你可以使用 spring.datasource.jndi-name property 来配置 DataSource。

8.2. 混合XA和非XA的JMS连接

当使用JTA时,主要的JMS ConnectionFactory Bean是XA-aware并参与分布式事务。 你可以注入你的Bean而不需要使用任何 @Qualifier。

Java
public MyBean(ConnectionFactory connectionFactory) {
    // ...
}
Kotlin

在某些情况下,你可能想通过使用非XA的 ConnectionFactory 来处理某些JMS消息。 例如,你的JMS处理逻辑可能需要比XA的超时时间更长。

如果你想使用一个非XA的 ConnectionFactory,你可以使用 nonXaJmsConnectionFactory bean。

Java
public MyBean(@Qualifier("nonXaJmsConnectionFactory") ConnectionFactory connectionFactory) {
    // ...
}
Kotlin

为了保持一致性,jmsConnectionFactory Bean 也通过使用bean的别名 xaJmsConnectionFactory 来提供。

Java
public MyBean(@Qualifier("xaJmsConnectionFactory") ConnectionFactory connectionFactory) {
    // ...
}
Kotlin

8.3. 支持嵌入式事务管理器

XAConnectionFactoryWrapper 和 XADataSourceWrapper 接口可用于支持嵌入式事务管理器。 这些接口负责包装 XAConnectionFactory 和 XADataSource Bean,并将其作为常规的 ConnectionFactory 和 DataSource Bean公开,透明地加入分布式事务。 数据源和JMS的自动配置使用JTA的变体,只要你有一个 JtaTransactionManager Bean和适当的XA Wrapper Bean在你的 ApplicationContext 中注册。

9. 接下来看什么

现在你应该对Spring Boot的核心功能和Spring Boot通过自动配置提供支持的各种技术有了充分的了解。

接下来的几节将详细介绍将应用程序部署到云平台的问题。你可以在下一节中阅读关于构建容器镜像的内容,或者跳到生产就绪的功能部分。