(window.webpackJsonp=window.webpackJsonp||[]).push([[406],{837:function(e,a,t){"use strict";t.r(a);var r=t(56),n=Object(r.a)({},(function(){var e=this,a=e.$createElement,t=e._self._c||a;return t("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[t("h1",{attrs:{id:"io"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#io"}},[e._v("#")]),e._v(" IO")]),e._v(" "),t("p",[e._v("大多数应用程序将需要在某个时刻处理输入和输出问题。 Spring Boot 提供了一系列技术的实用工具和集成,以在需要 IO 功能时提供帮助。本节介绍了标准的 IO 特性,如缓存和验证,以及更高级的主题,如调度和分布式事务。我们还将覆盖呼叫远程 REST 或 SOAP 服务和发送电子邮件。")]),e._v(" "),t("h2",{attrs:{id:"_1-缓存"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-缓存"}},[e._v("#")]),e._v(" 1. 缓存")]),e._v(" "),t("p",[e._v("Spring 框架提供了对向应用程序透明地添加缓存的支持。在其核心部分,抽象将缓存应用于方法,从而减少了基于缓存中可用信息的执行次数。缓存逻辑是透明地应用的,不会对调用程序造成任何干扰。 Spring 只要使用"),t("code",[e._v("@EnableCaching")]),e._v("注释启用缓存支持,启动就会自动配置缓存基础设施。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("查看 Spring 框架引用的"),t("a",{attrs:{href:"https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/integration.html#cache",target:"_blank",rel:"noopener noreferrer"}},[e._v("相关部分"),t("OutboundLink")],1),e._v("以获得更多详细信息。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("简而言之,要将缓存添加到服务的操作中,请将相关的注释添加到其方法中,如以下示例所示:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('import org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class MyMathService {\n\n @Cacheable("piDecimals")\n public int computePiDecimal(int precision) {\n ...\n }\n\n}\n\n')])])]),t("p",[e._v("这个示例演示了在可能代价高昂的操作上使用缓存的方法。在调用"),t("code",[e._v("computePiDecimal")]),e._v("之前,抽象在"),t("code",[e._v("piDecimals")]),e._v("缓存中查找与"),t("code",[e._v("i")]),e._v("参数匹配的条目。如果找到一个条目,缓存中的内容将立即返回给调用者,并且不调用该方法。否则,将调用该方法,并在返回值之前更新缓存。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("你也可以透明地使用标准的 JSR-107 注释(例如"),t("code",[e._v("@CacheResult")]),e._v(")。但是,我们强烈建议你不要混合和匹配 Spring 缓存和 JCache 注释。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("如果不添加任何特定的缓存库, Spring 引导自动配置一个"),t("a",{attrs:{href:"#io.caching.provider.simple"}},[e._v("简单提供者")]),e._v(",它在内存中使用并发映射。当需要缓存时(例如前面示例中的"),t("code",[e._v("piDecimals")]),e._v("),此提供程序将为你创建它。简单的提供者并不是真正推荐用于生产使用的,但是它对于开始使用和确保你了解这些特性是非常好的。当你决定使用缓存提供程序时,请务必阅读其文档,以了解如何配置应用程序使用的缓存。几乎所有的提供程序都要求你显式地配置应用程序中使用的每个缓存。一些提供了一种方法来定制由"),t("code",[e._v("spring.cache.cache-names")]),e._v("属性定义的默认缓存。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("还可以透明地从缓存中获取"),t("a",{attrs:{href:"https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/integration.html#cache-annotations-put",target:"_blank",rel:"noopener noreferrer"}},[e._v("update"),t("OutboundLink")],1),e._v("或"),t("a",{attrs:{href:"https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/integration.html#cache-annotations-evict",target:"_blank",rel:"noopener noreferrer"}},[e._v("evict"),t("OutboundLink")],1),e._v("数据。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h3",{attrs:{id:"_1-1-支持的缓存提供程序"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-支持的缓存提供程序"}},[e._v("#")]),e._v(" 1.1.支持的缓存提供程序")]),e._v(" "),t("p",[e._v("缓存抽象不提供实际的存储,而是依赖于由"),t("code",[e._v("org.springframework.cache.Cache")]),e._v("和"),t("code",[e._v("org.springframework.cache.CacheManager")]),e._v("接口实现的抽象。")]),e._v(" "),t("p",[e._v("如果你没有定义类型"),t("code",[e._v("CacheManager")]),e._v("的 Bean 或类型"),t("code",[e._v("CacheResolver")]),e._v("的"),t("code",[e._v("cacheResolver")]),e._v("(参见["),t("code",[e._v("CachingConfigurer")]),e._v("](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/cache/annotation/cachingconfigurer.html)), Spring boot 将尝试检测以下提供者(按指定的顺序):")]),e._v(" "),t("ol",[t("li",[t("p",[t("a",{attrs:{href:"#io.caching.provider.generic"}},[e._v("Generic")])])]),e._v(" "),t("li",[t("p",[t("a",{attrs:{href:"#io.caching.provider.jcache"}},[e._v("JCache(JSR-107)")]),e._v("(Ehcache3、黑泽尔卡斯特、Infinispan 等)")])]),e._v(" "),t("li",[t("p",[t("a",{attrs:{href:"#io.caching.provider.ehcache2"}},[e._v("Ehcache2.x")])])]),e._v(" "),t("li",[t("p",[t("a",{attrs:{href:"#io.caching.provider.hazelcast"}},[e._v("Hazelcast")])])]),e._v(" "),t("li",[t("p",[t("a",{attrs:{href:"#io.caching.provider.infinispan"}},[e._v("Infinispan")])])]),e._v(" "),t("li",[t("p",[t("a",{attrs:{href:"#io.caching.provider.couchbase"}},[e._v("Couchbase")])])]),e._v(" "),t("li",[t("p",[t("a",{attrs:{href:"#io.caching.provider.redis"}},[e._v("Redis")])])]),e._v(" "),t("li",[t("p",[t("a",{attrs:{href:"#io.caching.provider.caffeine"}},[e._v("Caffeine")])])]),e._v(" "),t("li",[t("p",[t("a",{attrs:{href:"#io.caching.provider.simple"}},[e._v("Simple")])])])]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("通过设置"),t("code",[e._v("spring.cache.type")]),e._v("属性,也可以"),t("em",[e._v("力")]),e._v("特定的缓存提供程序。"),t("br"),e._v("如果在某些环境(例如测试)中需要"),t("a",{attrs:{href:"#io.caching.provider.none"}},[e._v("完全禁用缓存")]),e._v(",请使用此属性。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("使用"),t("code",[e._v("spring-boot-starter-cache")]),e._v("“starter”快速添加基本的缓存依赖关系。"),t("br"),e._v("starter 带来"),t("code",[e._v("spring-context-support")]),e._v("。"),t("br"),e._v("如果手动添加依赖关系,则必须包含"),t("code",[e._v("spring-context-support")]),e._v("才能使用 JCache、EhCache2.x 或咖啡因支持。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("如果"),t("code",[e._v("CacheManager")]),e._v("是通过 Spring 引导自动配置的,那么可以通过公开实现"),t("code",[e._v("CacheManagerCustomizer")]),e._v("接口的 Bean 来在它完全初始化之前进一步优化其配置。下面的示例设置了一个标志,表示"),t("code",[e._v("null")]),e._v("值不应向下传递到底层映射:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;\nimport org.springframework.cache.concurrent.ConcurrentMapCacheManager;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration(proxyBeanMethods = false)\npublic class MyCacheManagerConfiguration {\n\n @Bean\n public CacheManagerCustomizer cacheManagerCustomizer() {\n return (cacheManager) -> cacheManager.setAllowNullValues(false);\n }\n\n}\n\n")])])]),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("在前面的示例中,需要自动配置"),t("code",[e._v("ConcurrentMapCacheManager")]),e._v("。,如果不是这样(要么你提供了自己的配置,要么自动配置了其他缓存提供程序),则根本不会调用自定义程序,"),t("br"),e._v("你可以根据需要配置任意多的自定义程序,你还可以使用"),t("code",[e._v("@Order")]),e._v("或"),t("code",[e._v("Ordered")]),e._v("来订购它们。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h4",{attrs:{id:"_1-1-1-泛型"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-1-泛型"}},[e._v("#")]),e._v(" 1.1.1.泛型")]),e._v(" "),t("p",[e._v("如果上下文定义"),t("em",[e._v("至少")]),e._v("one"),t("code",[e._v("org.springframework.cache.Cache")]),e._v(" Bean,则使用泛型缓存。创建了包装该类型的所有 bean 的"),t("code",[e._v("CacheManager")]),e._v("。")]),e._v(" "),t("h4",{attrs:{id:"_1-1-2-jcache-jsr-107"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-2-jcache-jsr-107"}},[e._v("#")]),e._v(" 1.1.2.JCache(JSR-107)")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://jcp.org/en/jsr/detail?id=107",target:"_blank",rel:"noopener noreferrer"}},[e._v("JCache"),t("OutboundLink")],1),e._v("是通过在 Classpath 上存在"),t("code",[e._v("javax.cache.spi.CachingProvider")]),e._v("(即在 Classpath 上存在符合 JSR-107 的缓存库)来引导的,而"),t("code",[e._v("JCacheCacheManager")]),e._v("是由"),t("code",[e._v("spring-boot-starter-cache")]),e._v("“starter”提供的。各种兼容的库是可用的,并且 Spring Boot 为 Ehcache3、HazelCast 和 Infinispan 提供了依赖管理。还可以添加任何其他兼容的库。")]),e._v(" "),t("p",[e._v("可能会出现多个提供程序,在这种情况下,必须显式地指定提供程序。 Spring 即使 JSR-107 标准没有强制使用一种标准化的方式来定义配置文件的位置, Spring Boot 也会尽其所能地适应使用实现细节来设置缓存,如以下示例所示:")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("# Only necessary if more than one provider is present\nspring.cache.jcache.provider=com.example.MyCachingProvider\nspring.cache.jcache.config=classpath:example.xml\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('# Only necessary if more than one provider is present\nspring:\n cache:\n jcache:\n provider: "com.example.MyCachingProvider"\n config: "classpath:example.xml"\n')])])]),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("Spring 当缓存库同时提供本机实现和 JSR-107 支持时,引导更倾向于 JSR-107 支持,这样,如果切换到不同的 JSR-107 实现,就可以使用相同的功能。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("Spring 引导具有"),t("a",{attrs:{href:"#io.hazelcast"}},[e._v("对 Hazelcast 的一般支持")]),e._v("。"),t("br"),e._v("如果单个"),t("code",[e._v("HazelcastInstance")]),e._v("是可用的,那么它也会自动被用于"),t("code",[e._v("CacheManager")]),e._v(",除非指定了"),t("code",[e._v("spring.cache.jcache.config")]),e._v("属性。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("有两种方法可以定制底层"),t("code",[e._v("javax.cache.cacheManager")]),e._v(":")]),e._v(" "),t("ul",[t("li",[t("p",[e._v("通过设置"),t("code",[e._v("spring.cache.cache-names")]),e._v("属性,可以在启动时创建缓存。如果定义了自定义"),t("code",[e._v("javax.cache.configuration.Configuration")]),e._v(" Bean,则使用它来自定义它们。")])]),e._v(" "),t("li",[t("p",[t("code",[e._v("org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer")]),e._v("bean 是用"),t("code",[e._v("CacheManager")]),e._v("的引用调用的,用于完全定制。")])])]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("如果定义了一个标准"),t("code",[e._v("javax.cache.CacheManager")]),e._v(" Bean,则它会自动包装在抽象所期望的"),t("code",[e._v("org.springframework.cache.CacheManager")]),e._v("实现中。"),t("br"),e._v("不会对其应用进一步的自定义。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h4",{attrs:{id:"_1-1-3-ehcache2-x"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-3-ehcache2-x"}},[e._v("#")]),e._v(" 1.1.3.Ehcache2.x")]),e._v(" "),t("p",[e._v("如果在 Classpath 的根位置可以找到名为"),t("code",[e._v("ehcache.xml")]),e._v("的文件,则使用"),t("a",{attrs:{href:"https://www.ehcache.org/",target:"_blank",rel:"noopener noreferrer"}},[e._v("EhCache"),t("OutboundLink")],1),e._v("2.x。如果找到了 Ehcache2.x,则使用"),t("code",[e._v("spring-boot-starter-cache")]),e._v("“starter”提供的"),t("code",[e._v("EhCacheCacheManager")]),e._v("引导缓存管理器。还可以提供一个替代配置文件,如以下示例所示:")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.cache.ehcache.config=classpath:config/another-config.xml\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n cache:\n ehcache:\n config: "classpath:config/another-config.xml"\n')])])]),t("h4",{attrs:{id:"_1-1-4-黑泽尔卡斯特"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-4-黑泽尔卡斯特"}},[e._v("#")]),e._v(" 1.1.4.黑泽尔卡斯特")]),e._v(" "),t("p",[e._v("Spring 引导有"),t("a",{attrs:{href:"#io.hazelcast"}},[e._v("对 Hazelcast 的一般支持")]),e._v("。如果"),t("code",[e._v("HazelcastInstance")]),e._v("已被自动配置,则它将自动包装在"),t("code",[e._v("CacheManager")]),e._v("中。")]),e._v(" "),t("h4",{attrs:{id:"_1-1-5-无限极"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-5-无限极"}},[e._v("#")]),e._v(" 1.1.5.无限极")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://infinispan.org/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Infinispan"),t("OutboundLink")],1),e._v("没有默认的配置文件位置,因此必须显式地指定它。否则,将使用默认的引导程序。")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.cache.infinispan.config=infinispan.xml\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n cache:\n infinispan:\n config: "infinispan.xml"\n')])])]),t("p",[e._v("通过设置"),t("code",[e._v("spring.cache.cache-names")]),e._v("属性,可以在启动时创建缓存。如果定义了自定义"),t("code",[e._v("ConfigurationBuilder")]),e._v(" Bean,则使用它来定制缓存。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("Spring boot 中对 Infinispan 的支持仅限于嵌入式模式,非常基本。"),t("br"),e._v("如果你想要更多选项,应该使用官方的 Infinispan Spring boot starter。"),t("br"),e._v("有关更多详细信息,请参见"),t("a",{attrs:{href:"https://github.com/infinispan/infinispan-spring-boot",target:"_blank",rel:"noopener noreferrer"}},[e._v("Infinispan 的文档"),t("OutboundLink")],1),e._v("。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h4",{attrs:{id:"_1-1-6-couchbase"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-6-couchbase"}},[e._v("#")]),e._v(" 1.1.6.Couchbase")]),e._v(" "),t("p",[e._v("如果 Spring 数据 Couchbase 是可用的,并且 Couchbase 是"),t("RouterLink",{attrs:{to:"/spring-boot/data.html#data.nosql.couchbase"}},[e._v("configured")]),e._v(",则将自动配置"),t("code",[e._v("CouchbaseCacheManager")]),e._v("。通过设置"),t("code",[e._v("spring.cache.cache-names")]),e._v("属性,可以在启动时创建额外的缓存,并且可以使用"),t("code",[e._v("spring.cache.couchbase.*")]),e._v("属性配置缓存默认值。例如,下面的配置使用一个 10 分钟的条目"),t("em",[e._v("过期")]),e._v("创建"),t("code",[e._v("cache1")]),e._v("和"),t("code",[e._v("cache2")]),e._v("缓存:")],1),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.cache.cache-names=cache1,cache2\nspring.cache.couchbase.expiration=10m\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n cache:\n cache-names: "cache1,cache2"\n couchbase:\n expiration: "10m"\n')])])]),t("p",[e._v("如果需要对配置进行更多控制,可以考虑注册"),t("code",[e._v("CouchbaseCacheManagerBuilderCustomizer")]),e._v(" Bean。下面的示例显示了一个定制程序,它为"),t("code",[e._v("cache1")]),e._v("和"),t("code",[e._v("cache2")]),e._v("配置一个特定的条目过期:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('import java.time.Duration;\n\nimport org.springframework.boot.autoconfigure.cache.CouchbaseCacheManagerBuilderCustomizer;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration;\n\n@Configuration(proxyBeanMethods = false)\npublic class MyCouchbaseCacheManagerConfiguration {\n\n @Bean\n public CouchbaseCacheManagerBuilderCustomizer myCouchbaseCacheManagerBuilderCustomizer() {\n return (builder) -> builder\n .withCacheConfiguration("cache1", CouchbaseCacheConfiguration\n .defaultCacheConfig().entryExpiry(Duration.ofSeconds(10)))\n .withCacheConfiguration("cache2", CouchbaseCacheConfiguration\n .defaultCacheConfig().entryExpiry(Duration.ofMinutes(1)));\n\n }\n\n}\n\n')])])]),t("h4",{attrs:{id:"_1-1-7-雷迪斯"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-7-雷迪斯"}},[e._v("#")]),e._v(" 1.1.7.雷迪斯")]),e._v(" "),t("p",[e._v("如果"),t("a",{attrs:{href:"https://redis.io/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Redis"),t("OutboundLink")],1),e._v("可用并已配置,则自动配置"),t("code",[e._v("RedisCacheManager")]),e._v("。通过设置"),t("code",[e._v("spring.cache.cache-names")]),e._v("属性,可以在启动时创建额外的缓存,并且可以使用"),t("code",[e._v("spring.cache.redis.*")]),e._v("属性配置缓存默认值。例如,下面的配置使用 10 分钟的"),t("em",[e._v("活下去的时间")]),e._v("创建"),t("code",[e._v("cache1")]),e._v("和"),t("code",[e._v("cache2")]),e._v("缓存:")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.cache.cache-names=cache1,cache2\nspring.cache.redis.time-to-live=10m\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n cache:\n cache-names: "cache1,cache2"\n redis:\n time-to-live: "10m"\n')])])]),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("默认情况下,会添加一个密钥前缀,这样,如果两个独立的缓存使用相同的密钥,则 Redis 没有重叠的密钥,并且不能返回无效的值。"),t("br"),e._v("如果你创建自己的"),t("code",[e._v("RedisCacheManager")]),e._v(",我们强烈建议启用此设置。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("你可以通过添加自己的"),t("code",[e._v("RedisCacheConfiguration``@Bean")]),e._v("来完全控制默认的配置。"),t("br"),e._v("如果你需要自定义默认的序列化策略,这将非常有用。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("如果需要对配置进行更多控制,可以考虑注册"),t("code",[e._v("RedisCacheManagerBuilderCustomizer")]),e._v(" Bean。下面的示例显示了一个定制程序,它为"),t("code",[e._v("cache1")]),e._v("和"),t("code",[e._v("cache2")]),e._v("配置了一个特定的生存时间:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('import java.time.Duration;\n\nimport org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.cache.RedisCacheConfiguration;\n\n@Configuration(proxyBeanMethods = false)\npublic class MyRedisCacheManagerConfiguration {\n\n @Bean\n public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer() {\n return (builder) -> builder\n .withCacheConfiguration("cache1", RedisCacheConfiguration\n .defaultCacheConfig().entryTtl(Duration.ofSeconds(10)))\n .withCacheConfiguration("cache2", RedisCacheConfiguration\n .defaultCacheConfig().entryTtl(Duration.ofMinutes(1)));\n\n }\n\n}\n\n')])])]),t("h4",{attrs:{id:"_1-1-8-咖啡因"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-8-咖啡因"}},[e._v("#")]),e._v(" 1.1.8.咖啡因")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://github.com/ben-manes/caffeine",target:"_blank",rel:"noopener noreferrer"}},[e._v("Caffeine"),t("OutboundLink")],1),e._v("是对番石榴缓存的 Java8 重写,取代了对番石榴的支持。如果存在咖啡因,则自动配置"),t("code",[e._v("CaffeineCacheManager")]),e._v("(由"),t("code",[e._v("spring-boot-starter-cache")]),e._v("“starter”提供)。通过设置"),t("code",[e._v("spring.cache.cache-names")]),e._v("属性,可以在启动时创建缓存,并且可以通过以下方式之一(按指定的顺序)进行定制:")]),e._v(" "),t("ol",[t("li",[t("p",[e._v("由"),t("code",[e._v("spring.cache.caffeine.spec")]),e._v("定义的缓存规范")])]),e._v(" "),t("li",[t("p",[e._v("定义了"),t("code",[e._v("com.github.benmanes.caffeine.cache.CaffeineSpec")]),e._v(" Bean")])]),e._v(" "),t("li",[t("p",[e._v("定义了"),t("code",[e._v("com.github.benmanes.caffeine.cache.Caffeine")]),e._v(" Bean")])])]),e._v(" "),t("p",[e._v("例如,下面的配置创建了"),t("code",[e._v("cache1")]),e._v("和"),t("code",[e._v("cache2")]),e._v("缓存,缓存的最大大小为 500,"),t("em",[e._v("活下去的时间")]),e._v("为 10 分钟")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.cache.cache-names=cache1,cache2\nspring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n cache:\n cache-names: "cache1,cache2"\n caffeine:\n spec: "maximumSize=500,expireAfterAccess=600s"\n')])])]),t("p",[e._v("如果定义了"),t("code",[e._v("com.github.benmanes.caffeine.cache.CacheLoader")]),e._v(" Bean,则它将自动关联到"),t("code",[e._v("CaffeineCacheManager")]),e._v("。由于"),t("code",[e._v("CacheLoader")]),e._v("将与由缓存管理器管理的"),t("em",[e._v("全部")]),e._v("缓存相关联,因此必须将其定义为"),t("code",[e._v("CacheLoader")]),e._v("。自动配置忽略任何其他通用类型。")]),e._v(" "),t("h4",{attrs:{id:"_1-1-9-简单"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-9-简单"}},[e._v("#")]),e._v(" 1.1.9.简单")]),e._v(" "),t("p",[e._v("如果找不到其他提供程序,则配置一个使用"),t("code",[e._v("ConcurrentHashMap")]),e._v("作为缓存存储的简单实现。如果应用程序中不存在缓存库,这是默认的。默认情况下,缓存是根据需要创建的,但是你可以通过设置"),t("code",[e._v("cache-names")]),e._v("属性来限制可用缓存的列表。例如,如果只想要"),t("code",[e._v("cache1")]),e._v("和"),t("code",[e._v("cache2")]),e._v("缓存,请将"),t("code",[e._v("cache-names")]),e._v("属性设置如下:")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.cache.cache-names=cache1,cache2\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n cache:\n cache-names: "cache1,cache2"\n')])])]),t("p",[e._v("如果你这样做,而你的应用程序使用了一个未列出的缓存,那么当需要缓存时,它会在运行时失败,而不是在启动时。如果使用未声明的缓存,这类似于“真正的”缓存提供者的行为。")]),e._v(" "),t("h4",{attrs:{id:"_1-1-10-无"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_1-1-10-无"}},[e._v("#")]),e._v(" 1.1.10.无")]),e._v(" "),t("p",[e._v("当"),t("code",[e._v("@EnableCaching")]),e._v("出现在你的配置中时,还需要一个合适的缓存配置。如果需要在某些环境中完全禁用缓存,请强制缓存类型"),t("code",[e._v("none")]),e._v("使用无操作实现,如以下示例所示:")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.cache.type=none\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n cache:\n type: "none"\n')])])]),t("h2",{attrs:{id:"_2-hazelcast"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_2-hazelcast"}},[e._v("#")]),e._v(" 2. Hazelcast")]),e._v(" "),t("p",[e._v("如果"),t("a",{attrs:{href:"https://hazelcast.com/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Hazelcast"),t("OutboundLink")],1),e._v("在 Classpath 上,并且找到了合适的配置, Spring 引导自动配置一个"),t("code",[e._v("HazelcastInstance")]),e._v(",你可以将其注入到应用程序中。")]),e._v(" "),t("p",[e._v("Spring 引导首先尝试通过检查以下配置选项来创建客户端:")]),e._v(" "),t("ul",[t("li",[t("p",[e._v("a"),t("code",[e._v("com.hazelcast.client.config.ClientConfig")]),e._v(" Bean 的存在。")])]),e._v(" "),t("li",[t("p",[e._v("由"),t("code",[e._v("spring.hazelcast.config")]),e._v("属性定义的配置文件。")])]),e._v(" "),t("li",[t("p",[e._v("存在"),t("code",[e._v("hazelcast.client.config")]),e._v("系统属性。")])]),e._v(" "),t("li",[t("p",[e._v("Classpath 的工作目录或根目录中的"),t("code",[e._v("hazelcast-client.xml")]),e._v("。")])]),e._v(" "),t("li",[t("p",[e._v("Classpath 的工作目录或根目录中的"),t("code",[e._v("hazelcast-client.yaml")]),e._v("。")])])]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("Spring Boot 同时支持 Hazelcast4 和 Hazelcast3。"),t("br"),e._v("如果降级为 Hazelcast3,则应将"),t("code",[e._v("hazelcast-client")]),e._v("添加到 Classpath 中以配置客户端。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("如果不能创建客户端, Spring 引导将尝试配置嵌入式服务器。如果你定义了"),t("code",[e._v("com.hazelcast.config.Config")]),e._v(" Bean, Spring boot 就会使用它。如果你的配置定义了一个实例名, Spring 引导将尝试定位一个现有的实例,而不是创建一个新的实例。")]),e._v(" "),t("p",[e._v("你还可以指定通过配置使用的 HazelCast 配置文件,如以下示例所示:")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.hazelcast.config=classpath:config/my-hazelcast.xml\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n hazelcast:\n config: "classpath:config/my-hazelcast.xml"\n')])])]),t("p",[e._v("否则, Spring 引导将尝试从默认位置查找 Hazelcast 配置:在工作目录中或 Classpath 的根目录中的"),t("code",[e._v("hazelcast.xml")]),e._v(",或在相同位置中的"),t("code",[e._v(".yaml")]),e._v("对应位置。我们还检查"),t("code",[e._v("hazelcast.config")]),e._v("系统属性是否已设置。有关更多详细信息,请参见"),t("a",{attrs:{href:"https://docs.hazelcast.org/docs/latest/manual/html-single/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Hazelcast 文档"),t("OutboundLink")],1),e._v("。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("Spring 引导还具有"),t("a",{attrs:{href:"#io.caching.provider.hazelcast"}},[e._v("对 HazelCast 的显式缓存支持")]),e._v("。"),t("br"),e._v("如果启用了缓存,"),t("code",[e._v("HazelcastInstance")]),e._v("将自动包装在"),t("code",[e._v("CacheManager")]),e._v("实现中。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h2",{attrs:{id:"_3-石英调度器"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_3-石英调度器"}},[e._v("#")]),e._v(" 3. 石英调度器")]),e._v(" "),t("p",[e._v("Spring Boot 为使用"),t("a",{attrs:{href:"https://www.quartz-scheduler.org/",target:"_blank",rel:"noopener noreferrer"}},[e._v("石英调度器"),t("OutboundLink")],1),e._v("提供了几种便利,包括"),t("code",[e._v("spring-boot-starter-quartz")]),e._v("“starter”。如果 Quartz 可用,则自动配置"),t("code",[e._v("Scheduler")]),e._v("(通过"),t("code",[e._v("SchedulerFactoryBean")]),e._v("抽象)。")]),e._v(" "),t("p",[e._v("以下类型的 bean 将被自动拾取并与"),t("code",[e._v("Scheduler")]),e._v("关联:")]),e._v(" "),t("ul",[t("li",[t("p",[t("code",[e._v("JobDetail")]),e._v(":定义特定的作业。"),t("code",[e._v("JobDetail")]),e._v("实例可以用"),t("code",[e._v("JobBuilder")]),e._v("API 构建。")])]),e._v(" "),t("li",[t("p",[t("code",[e._v("Calendar")]),e._v(".")])]),e._v(" "),t("li",[t("p",[t("code",[e._v("Trigger")]),e._v(":定义触发特定作业的时间。")])])]),e._v(" "),t("p",[e._v("默认情况下,使用内存"),t("code",[e._v("JobStore")]),e._v("。但是,如果"),t("code",[e._v("DataSource")]),e._v(" Bean 在你的应用程序中可用,并且"),t("code",[e._v("spring.quartz.job-store-type")]),e._v("属性被相应地配置,则可以配置基于 JDBC 的存储,如以下示例所示:")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.quartz.job-store-type=jdbc\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n quartz:\n job-store-type: "jdbc"\n')])])]),t("p",[e._v("当使用 JDBC 存储时,可以在启动时初始化模式,如以下示例所示:")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.quartz.jdbc.initialize-schema=always\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n quartz:\n jdbc:\n initialize-schema: "always"\n')])])]),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("默认情况下,通过使用 Quartz 库提供的标准脚本来检测和初始化数据库。"),t("br"),e._v("这些脚本删除现有的表,在每次重新启动时删除所有触发器。"),t("br"),e._v("还可以通过设置"),t("code",[e._v("spring.quartz.jdbc.schema")]),e._v("属性来提供自定义脚本。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("要让 Quartz 使用应用程序的主"),t("code",[e._v("DataSource")]),e._v("以外的"),t("code",[e._v("DataSource")]),e._v(",请声明一个"),t("code",[e._v("DataSource")]),e._v(" Bean,并用"),t("code",[e._v("@QuartzDataSource")]),e._v("注释其"),t("code",[e._v("@Bean")]),e._v("方法。这样做可以确保"),t("code",[e._v("SchedulerFactoryBean")]),e._v("和模式初始化都使用特定于石英的"),t("code",[e._v("DataSource")]),e._v("。类似地,要使 Quartz 使用一个"),t("code",[e._v("TransactionManager")]),e._v("而不是应用程序的主"),t("code",[e._v("TransactionManager")]),e._v("声明一个"),t("code",[e._v("TransactionManager")]),e._v(" Bean,用"),t("code",[e._v("@QuartzTransactionManager")]),e._v("注释其"),t("code",[e._v("@Bean")]),e._v("方法。")]),e._v(" "),t("p",[e._v("默认情况下,通过配置创建的作业不会覆盖已注册的、已从持久作业存储区读取的作业。要启用覆盖现有的作业定义,请设置"),t("code",[e._v("spring.quartz.overwrite-existing-jobs")]),e._v("属性。")]),e._v(" "),t("p",[e._v("Quartz 调度器配置可以使用"),t("code",[e._v("spring.quartz")]),e._v("属性和"),t("code",[e._v("SchedulerFactoryBeanCustomizer")]),e._v("bean 进行自定义,这允许程序化的"),t("code",[e._v("SchedulerFactoryBean")]),e._v("自定义。可以使用"),t("code",[e._v("spring.quartz.properties.*")]),e._v("定制高级石英配置属性。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("特别地,"),t("code",[e._v("Executor")]),e._v(" Bean 不与调度程序相关联,因为 Quartz 提供了一种通过"),t("code",[e._v("spring.quartz.properties")]),e._v("配置调度程序的方法。"),t("br"),e._v("如果需要定制任务执行器,请考虑实现"),t("code",[e._v("SchedulerFactoryBeanCustomizer")]),e._v("。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("作业可以定义 setter 来注入数据映射属性。普通的豆子也可以以类似的方式注入,如以下示例所示:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('import org.quartz.JobExecutionContext;\nimport org.quartz.JobExecutionException;\n\nimport org.springframework.scheduling.quartz.QuartzJobBean;\n\npublic class MySampleJob extends QuartzJobBean {\n\n // fields ...\n\n private MyService myService;\n\n private String name;\n\n // Inject "MyService" bean\n public void setMyService(MyService myService) {\n this.myService = myService;\n }\n\n // Inject the "name" job data property\n public void setName(String name) {\n this.name = name;\n }\n\n @Override\n protected void executeInternal(JobExecutionContext context) throws JobExecutionException {\n this.myService.someMethod(context.getFireTime(), this.name);\n }\n\n}\n\n')])])]),t("h2",{attrs:{id:"_4-发送电子邮件"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_4-发送电子邮件"}},[e._v("#")]),e._v(" 4. 发送电子邮件")]),e._v(" "),t("p",[e._v("Spring 框架提供了用于通过使用"),t("code",[e._v("JavaMailSender")]),e._v("接口发送电子邮件的抽象,并且 Spring 启动为其提供了自动配置以及一个启动模块。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("有关如何使用"),t("code",[e._v("JavaMailSender")]),e._v("的详细说明,请参见"),t("a",{attrs:{href:"https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/integration.html#mail",target:"_blank",rel:"noopener noreferrer"}},[e._v("参考文献"),t("OutboundLink")],1),e._v("。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("如果"),t("code",[e._v("spring.mail.host")]),e._v("和相关的库(由"),t("code",[e._v("spring-boot-starter-mail")]),e._v("定义)是可用的,则如果不存在,则创建一个默认的"),t("code",[e._v("JavaMailSender")]),e._v("。发送方可以通过"),t("code",[e._v("spring.mail")]),e._v("名称空间中的配置项进行进一步定制。详见["),t("code",[e._v("Mail属性")]),e._v("](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autofigure/SRC/main/java/org/springframework/boot/autofigure/mail/mailproperties.java)了解更多详情。")]),e._v(" "),t("p",[e._v("特别是,某些默认的超时值是无限的,你可能希望对其进行更改,以避免线程被无响应的邮件服务器阻塞,如以下示例所示:")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.mail.properties[mail.smtp.connectiontimeout]=5000\nspring.mail.properties[mail.smtp.timeout]=3000\nspring.mail.properties[mail.smtp.writetimeout]=5000\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n mail:\n properties:\n "[mail.smtp.connectiontimeout]": 5000\n "[mail.smtp.timeout]": 3000\n "[mail.smtp.writetimeout]": 5000\n')])])]),t("p",[e._v("也可以使用 JNDI 中现有的"),t("code",[e._v("Session")]),e._v("配置"),t("code",[e._v("JavaMailSender")]),e._v(":")]),e._v(" "),t("p",[e._v("属性")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.mail.jndi-name=mail/Session\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n mail:\n jndi-name: "mail/Session"\n')])])]),t("p",[e._v("当设置"),t("code",[e._v("jndi-name")]),e._v("时,它优先于所有其他与会话相关的设置。")]),e._v(" "),t("h2",{attrs:{id:"_5-验证"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_5-验证"}},[e._v("#")]),e._v(" 5. 验证")]),e._v(" "),t("p",[e._v("Bean 验证 1.1 支持的方法验证功能是自动启用的,只要在 Classpath 上有一个 JSR-303 实现(例如 Hibernate 验证器)。这使得 Bean 方法在其参数和/或其返回值上使用"),t("code",[e._v("javax.validation")]),e._v("约束进行注释。具有这种注释方法的目标类需要在类型级别使用"),t("code",[e._v("@Validated")]),e._v("注释进行注释,以便在内联约束注释中搜索它们的方法。")]),e._v(" "),t("p",[e._v("例如,下面的服务将触发第一个参数的验证,以确保其大小在 8 到 10 之间:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("import javax.validation.constraints.Size;\n\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\n@Service\n@Validated\npublic class MyBean {\n\n public Archive findByCodeAndAuthor(@Size(min = 8, max = 10) String code, Author author) {\n return ...\n }\n\n}\n\n")])])]),t("p",[e._v("在解析约束消息中的"),t("code",[e._v("{parameters}")]),e._v("时,将使用应用程序的"),t("code",[e._v("MessageSource")]),e._v("。这允许你使用[应用程序的"),t("code",[e._v("messages.properties")]),e._v("文件](features.html#features.internationalization)来获取 Bean 验证消息。一旦解决了参数问题,就使用 Bean 验证的默认内插器完成消息插值。")]),e._v(" "),t("h2",{attrs:{id:"_6-呼叫-rest-服务"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_6-呼叫-rest-服务"}},[e._v("#")]),e._v(" 6. 呼叫 REST 服务")]),e._v(" "),t("p",[e._v("如果你的应用程序调用远程 REST 服务, Spring boot 使用"),t("code",[e._v("RestTemplate")]),e._v("或"),t("code",[e._v("WebClient")]),e._v("使这一点非常方便。")]),e._v(" "),t("h3",{attrs:{id:"_6-1-resttemplate"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_6-1-resttemplate"}},[e._v("#")]),e._v(" 6.1.RESTTemplate")]),e._v(" "),t("p",[e._v("如果需要从应用程序调用远程 REST 服务,可以使用 Spring 框架的["),t("code",[e._v("RestTemplate")]),e._v("](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/client/resttemplate.html)类。由于"),t("code",[e._v("RestTemplate")]),e._v("实例在使用之前通常需要进行自定义, Spring Boot 不提供任何单独的自动配置"),t("code",[e._v("RestTemplate")]),e._v(" Bean。但是,它可以自动配置"),t("code",[e._v("RestTemplateBuilder")]),e._v(",在需要时可以使用它来创建"),t("code",[e._v("RestTemplate")]),e._v("实例。自动配置的"),t("code",[e._v("RestTemplateBuilder")]),e._v("确保将合理的"),t("code",[e._v("HttpMessageConverters")]),e._v("应用于"),t("code",[e._v("RestTemplate")]),e._v("实例。")]),e._v(" "),t("p",[e._v("下面的代码展示了一个典型的示例:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('import org.springframework.boot.web.client.RestTemplateBuilder;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.client.RestTemplate;\n\n@Service\npublic class MyService {\n\n private final RestTemplate restTemplate;\n\n public MyService(RestTemplateBuilder restTemplateBuilder) {\n this.restTemplate = restTemplateBuilder.build();\n }\n\n public Details someRestCall(String name) {\n return this.restTemplate.getForObject("/{name}/details", Details.class, name);\n }\n\n}\n\n')])])]),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[t("code",[e._v("RestTemplateBuilder")]),e._v("包括许多有用的方法,可以用来快速配置"),t("code",[e._v("RestTemplate")]),e._v("。"),t("br"),e._v("例如,要添加基本的 auth 支持,可以使用"),t("code",[e._v('builder.basicAuthentication("user", "password").build()')]),e._v("。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h4",{attrs:{id:"_6-1-1-resttemplate-定制"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_6-1-1-resttemplate-定制"}},[e._v("#")]),e._v(" 6.1.1.resttemplate 定制")]),e._v(" "),t("p",[e._v("对于"),t("code",[e._v("RestTemplate")]),e._v("定制,有三种主要的方法,这取决于你希望定制应用的范围。")]),e._v(" "),t("p",[e._v("要使任何定制的范围尽可能窄,请插入自动配置的"),t("code",[e._v("RestTemplateBuilder")]),e._v(",然后根据需要调用其方法。每个方法调用返回一个新的"),t("code",[e._v("RestTemplateBuilder")]),e._v("实例,因此自定义仅影响构建器的这种使用。")]),e._v(" "),t("p",[e._v("要进行应用程序范围的可加性定制,请使用"),t("code",[e._v("RestTemplateCustomizer")]),e._v(" Bean。所有这样的 bean 都会自动注册到自动配置的"),t("code",[e._v("RestTemplateBuilder")]),e._v("中,并应用到用它构建的任何模板中。")]),e._v(" "),t("p",[e._v("下面的示例显示了一个定制程序,该定制程序为所有主机配置代理的使用,但"),t("code",[e._v("192.168.0.5")]),e._v("除外:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('import org.apache.http.HttpException;\nimport org.apache.http.HttpHost;\nimport org.apache.http.HttpRequest;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.conn.routing.HttpRoutePlanner;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.impl.conn.DefaultProxyRoutePlanner;\nimport org.apache.http.protocol.HttpContext;\n\nimport org.springframework.boot.web.client.RestTemplateCustomizer;\nimport org.springframework.http.client.HttpComponentsClientHttpRequestFactory;\nimport org.springframework.web.client.RestTemplate;\n\npublic class MyRestTemplateCustomizer implements RestTemplateCustomizer {\n\n @Override\n public void customize(RestTemplate restTemplate) {\n HttpRoutePlanner routePlanner = new CustomRoutePlanner(new HttpHost("proxy.example.com"));\n HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build();\n restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));\n }\n\n static class CustomRoutePlanner extends DefaultProxyRoutePlanner {\n\n CustomRoutePlanner(HttpHost proxy) {\n super(proxy);\n }\n\n @Override\n public HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {\n if (target.getHostName().equals("192.168.0.5")) {\n return null;\n }\n return super.determineProxy(target, request, context);\n }\n\n }\n\n}\n\n')])])]),t("p",[e._v("最后,你可以定义自己的"),t("code",[e._v("RestTemplateBuilder")]),e._v(" Bean。这样做将取代自动配置的构建器。如果你希望将任何"),t("code",[e._v("RestTemplateCustomizer")]),e._v("bean 应用到你的自定义构建器,正如自动配置所做的那样,请使用"),t("code",[e._v("RestTemplateBuilderConfigurer")]),e._v("对其进行配置。下面的示例公开了一个"),t("code",[e._v("RestTemplateBuilder")]),e._v(",它与 Spring boot 的自动配置所做的匹配,除了还指定了自定义连接和读取超时之外:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("import java.time.Duration;\n\nimport org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer;\nimport org.springframework.boot.web.client.RestTemplateBuilder;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration(proxyBeanMethods = false)\npublic class MyRestTemplateBuilderConfiguration {\n\n @Bean\n public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {\n return configurer.configure(new RestTemplateBuilder()).setConnectTimeout(Duration.ofSeconds(5))\n .setReadTimeout(Duration.ofSeconds(2));\n }\n\n}\n\n")])])]),t("p",[e._v("最极端(也很少使用)的选项是在不使用配置器的情况下创建自己的"),t("code",[e._v("RestTemplateBuilder")]),e._v(" Bean。除了替换自动配置的生成器外,这还可以防止使用任何"),t("code",[e._v("RestTemplateCustomizer")]),e._v("bean。")]),e._v(" "),t("h3",{attrs:{id:"_6-2-webclient"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_6-2-webclient"}},[e._v("#")]),e._v(" 6.2.WebClient")]),e._v(" "),t("p",[e._v("如果你的 Classpath 上有 Spring WebFlux,你还可以选择使用"),t("code",[e._v("WebClient")]),e._v("来调用远程 REST 服务。与"),t("code",[e._v("RestTemplate")]),e._v("相比,该客户机具有更多的功能感觉,并且完全具有反应性。你可以在专用的"),t("a",{attrs:{href:"https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web-reactive.html#webflux-client",target:"_blank",rel:"noopener noreferrer"}},[e._v("section in the Spring Framework docs"),t("OutboundLink")],1),e._v("中了解有关"),t("code",[e._v("WebClient")]),e._v("的更多信息。")]),e._v(" "),t("p",[e._v("Spring 引导为你创建并预配置一个"),t("code",[e._v("WebClient.Builder")]),e._v("。强烈建议将其注入到组件中,并使用它来创建"),t("code",[e._v("WebClient")]),e._v("实例。 Spring 引导正在配置该构建器来共享 HTTP 资源,以与服务器相同的方式反映编解码器设置(参见"),t("RouterLink",{attrs:{to:"/spring-boot/web.html#web.reactive.webflux.httpcodecs"}},[e._v("WebFlux HTTP 编解码器自动配置")]),e._v("),等等。")],1),e._v(" "),t("p",[e._v("下面的代码展示了一个典型的示例:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('import org.neo4j.cypherdsl.core.Relationship.Details;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.reactive.function.client.WebClient;\n\n@Service\npublic class MyService {\n\n private final WebClient webClient;\n\n public MyService(WebClient.Builder webClientBuilder) {\n this.webClient = webClientBuilder.baseUrl("https://example.org").build();\n }\n\n public Mono
someRestCall(String name) {\n return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);\n }\n\n}\n\n')])])]),t("h4",{attrs:{id:"_6-2-1-webclient-运行时"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_6-2-1-webclient-运行时"}},[e._v("#")]),e._v(" 6.2.1.WebClient 运行时")]),e._v(" "),t("p",[e._v("Spring 启动将自动检测使用哪个"),t("code",[e._v("ClientHttpConnector")]),e._v("来驱动"),t("code",[e._v("WebClient")]),e._v(",这取决于应用程序上可用的库 Classpath。目前,反应堆网络和 Jetty RS 客户端是受支持的。")]),e._v(" "),t("p",[e._v("默认情况下,"),t("code",[e._v("spring-boot-starter-webflux")]),e._v("starter 依赖于"),t("code",[e._v("io.projectreactor.netty:reactor-netty")]),e._v(",这带来了服务器和客户端实现。如果你选择使用 Jetty 作为反应性服务器,那么你应该在 Jetty 反应性 HTTP 客户库上添加一个依赖项,"),t("code",[e._v("org.eclipse.jetty:jetty-reactive-httpclient")]),e._v("。使用同样的技术对服务器和客户端都有它的优点,因为它会自动地在客户端和服务器之间共享 HTTP 资源。")]),e._v(" "),t("p",[e._v("开发人员可以通过提供自定义"),t("code",[e._v("ReactorResourceFactory")]),e._v("或"),t("code",[e._v("JettyResourceFactory")]),e._v(" Bean 来覆盖 Jetty 和反应器网络的资源配置-这将同时应用于客户机和服务器。")]),e._v(" "),t("p",[e._v("如果你希望为客户机重写该选择,那么你可以定义自己的"),t("code",[e._v("ClientHttpConnector")]),e._v(" Bean 并对客户机配置拥有完全的控制权。")]),e._v(" "),t("p",[e._v("你可以在 Spring 框架参考文档中了解有关["),t("code",[e._v("WebClient")]),e._v("配置选项的更多信息](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/web-active.html#webflux-client-builder)。")]),e._v(" "),t("h4",{attrs:{id:"_6-2-2-webclient-自定义"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_6-2-2-webclient-自定义"}},[e._v("#")]),e._v(" 6.2.2.WebClient 自定义")]),e._v(" "),t("p",[e._v("对于"),t("code",[e._v("WebClient")]),e._v("定制,有三种主要的方法,这取决于你希望定制应用的范围。")]),e._v(" "),t("p",[e._v("要使任何自定义的范围尽可能窄,请插入自动配置的"),t("code",[e._v("WebClient.Builder")]),e._v(",然后根据需要调用其方法。"),t("code",[e._v("WebClient.Builder")]),e._v("实例是有状态的:构建器上的任何更改都会反映在随后使用它创建的所有客户机中。如果你想用同一个构建器创建多个客户机,还可以考虑用"),t("code",[e._v("WebClient.Builder other = builder.clone();")]),e._v("克隆构建器。")]),e._v(" "),t("p",[e._v("要对所有"),t("code",[e._v("WebClient.Builder")]),e._v("实例进行应用程序范围的加性定制,你可以声明"),t("code",[e._v("WebClientCustomizer")]),e._v("bean,并在注入点本地更改"),t("code",[e._v("WebClient.Builder")]),e._v("。")]),e._v(" "),t("p",[e._v("最后,你可以返回到原始 API 并使用"),t("code",[e._v("WebClient.create()")]),e._v("。在这种情况下,不应用自动配置或"),t("code",[e._v("WebClientCustomizer")]),e._v("。")]),e._v(" "),t("h2",{attrs:{id:"_7-web-服务"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_7-web-服务"}},[e._v("#")]),e._v(" 7. Web 服务")]),e._v(" "),t("p",[e._v("Spring 启动提供了 Web 服务自动配置,因此你所必须做的就是定义你的"),t("code",[e._v("Endpoints")]),e._v("。")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://docs.spring.io/spring-ws/docs/3.1.2/reference/html/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Spring Web Services features"),t("OutboundLink")],1),e._v("模块可以轻松访问"),t("code",[e._v("spring-boot-starter-webservices")]),e._v("。")]),e._v(" "),t("p",[t("code",[e._v("SimpleWsdl11Definition")]),e._v("和"),t("code",[e._v("SimpleXsdSchema")]),e._v("bean 可以分别为你的 WSDL 和 XSD 自动创建。要做到这一点,请配置它们的位置,如以下示例所示:")]),e._v(" "),t("p",[e._v("Properties")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("spring.webservices.wsdl-locations=classpath:/wsdl\n")])])]),t("p",[e._v("Yaml")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('spring:\n webservices:\n wsdl-locations: "classpath:/wsdl"\n')])])]),t("h3",{attrs:{id:"_7-1-使用-webservicetemplate-调用-web-服务"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_7-1-使用-webservicetemplate-调用-web-服务"}},[e._v("#")]),e._v(" 7.1.使用 WebServiceTemplate 调用 Web 服务")]),e._v(" "),t("p",[e._v("如果需要从应用程序调用远程 Web 服务,可以使用["),t("code",[e._v("WebServiceTemplate")]),e._v("](https://DOCS. Spring.io/ Spring-ws/DOCS/3.1.2/reference/html/#client-web-service-template)类。由于"),t("code",[e._v("WebServiceTemplate")]),e._v("实例在使用之前通常需要进行自定义, Spring Boot 不提供任何单独的自动配置"),t("code",[e._v("WebServiceTemplate")]),e._v(" Bean。但是,它可以自动配置"),t("code",[e._v("WebServiceTemplateBuilder")]),e._v(",在需要时可以使用它来创建"),t("code",[e._v("WebServiceTemplate")]),e._v("实例。")]),e._v(" "),t("p",[e._v("下面的代码展示了一个典型的示例:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;\nimport org.springframework.stereotype.Service;\nimport org.springframework.ws.client.core.WebServiceTemplate;\nimport org.springframework.ws.soap.client.core.SoapActionCallback;\n\n@Service\npublic class MyService {\n\n private final WebServiceTemplate webServiceTemplate;\n\n public MyService(WebServiceTemplateBuilder webServiceTemplateBuilder) {\n this.webServiceTemplate = webServiceTemplateBuilder.build();\n }\n\n public SomeResponse someWsCall(SomeRequest detailsReq) {\n return (SomeResponse) this.webServiceTemplate.marshalSendAndReceive(detailsReq,\n new SoapActionCallback("https://ws.example.com/action"));\n }\n\n}\n\n')])])]),t("p",[e._v("默认情况下,"),t("code",[e._v("WebServiceTemplateBuilder")]),e._v("使用 Classpath 上可用的 HTTP 客户库来检测合适的基于 HTTP 的"),t("code",[e._v("WebServiceMessageSender")]),e._v("。你还可以自定义读取和连接超时,如下所示:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("import java.time.Duration;\n\nimport org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder;\nimport org.springframework.boot.webservices.client.WebServiceTemplateBuilder;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.ws.client.core.WebServiceTemplate;\nimport org.springframework.ws.transport.WebServiceMessageSender;\n\n@Configuration(proxyBeanMethods = false)\npublic class MyWebServiceTemplateConfiguration {\n\n @Bean\n public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) {\n WebServiceMessageSender sender = new HttpWebServiceMessageSenderBuilder()\n .setConnectTimeout(Duration.ofSeconds(5))\n .setReadTimeout(Duration.ofSeconds(2))\n .build();\n return builder.messageSenders(sender).build();\n }\n\n}\n\n")])])]),t("h2",{attrs:{id:"_8-使用-jta-的分布式事务"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_8-使用-jta-的分布式事务"}},[e._v("#")]),e._v(" 8. 使用 JTA 的分布式事务")]),e._v(" "),t("p",[e._v("Spring 通过使用"),t("a",{attrs:{href:"https://www.atomikos.com/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Atomikos"),t("OutboundLink")],1),e._v("嵌入式事务管理器,启动支持跨多个 XA 资源的分布式 JTA 事务。当部署到合适的 Java EE 应用程序服务器时,也支持 JTA 事务。")]),e._v(" "),t("p",[e._v("当检测到 JTA 环境时,将使用 Spring 的"),t("code",[e._v("JtaTransactionManager")]),e._v("来管理事务。升级了自动配置的 JMS、数据源和 JPA bean,以支持 XA 事务。你可以使用标准 Spring 习惯用法,例如"),t("code",[e._v("@Transactional")]),e._v(",来参与分布式事务。如果你处于 JTA 环境中,并且仍然希望使用本地事务,那么可以将"),t("code",[e._v("spring.jta.enabled")]),e._v("属性设置为"),t("code",[e._v("false")]),e._v("以禁用 JTA 自动配置。")]),e._v(" "),t("h3",{attrs:{id:"_8-1-使用-atomikos-事务管理器"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_8-1-使用-atomikos-事务管理器"}},[e._v("#")]),e._v(" 8.1.使用 Atomikos 事务管理器")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://www.atomikos.com/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Atomikos"),t("OutboundLink")],1),e._v("是一种流行的开源事务管理器,它可以嵌入到你的 Spring 引导应用程序中。你可以使用"),t("code",[e._v("spring-boot-starter-jta-atomikos")]),e._v("启动器来获取适当的 Atomikos 库。 Spring 引导自动配置 Atomikos,并确保将适当的"),t("code",[e._v("depends-on")]),e._v("设置应用到你的 Spring bean,以进行正确的启动和关闭排序。")]),e._v(" "),t("p",[e._v("默认情况下,Atomikos 事务日志被写入应用程序主目录(应用程序 jar 文件所在的目录)中的"),t("code",[e._v("transaction-logs")]),e._v("目录。可以通过在"),t("code",[e._v("application.properties")]),e._v("文件中设置"),t("code",[e._v("spring.jta.log-dir")]),e._v("属性来自定义此目录的位置。以"),t("code",[e._v("spring.jta.atomikos.properties")]),e._v("开头的属性也可用于自定义 Atomikos"),t("code",[e._v("UserTransactionServiceImp")]),e._v("。有关完整的详细信息,请参见["),t("code",[e._v("AtomikosProperties")]),e._v("Javadoc](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/springframework/boot/jta/atomikos/atomikosproperties.html)。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("为了确保多个事务管理器能够安全地协调相同的资源管理器,每个 Atomikos 实例都必须配置一个唯一的 ID,"),t("br"),e._v("默认情况下,这个 ID 是运行 Atomikos 的机器的 IP 地址,"),t("br"),e._v("以确保生产中的唯一性,你应该为应用程序的每个实例配置具有不同值的"),t("code",[e._v("spring.jta.transaction-manager-id")]),e._v("属性。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h3",{attrs:{id:"_8-2-使用-java-ee-托管事务管理器"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_8-2-使用-java-ee-托管事务管理器"}},[e._v("#")]),e._v(" 8.2.使用 Java EE 托管事务管理器")]),e._v(" "),t("p",[e._v("如果你将 Spring 引导应用程序打包为"),t("code",[e._v("war")]),e._v("或"),t("code",[e._v("ear")]),e._v("文件并将其部署到 Java EE 应用程序服务器,则可以使用应用程序服务器的内置事务管理器。 Spring 引导试图通过查看常见的 JNDI 位置("),t("code",[e._v("java:comp/UserTransaction")]),e._v(","),t("code",[e._v("java:comp/TransactionManager")]),e._v(",以此类推)来自动配置事务管理器。如果使用应用程序服务器提供的事务服务,通常还需要确保所有资源都由服务器管理,并通过 JNDI 公开。 Spring Boot 试图通过在 JNDI 路径("),t("code",[e._v("java:/JmsXA")]),e._v("或"),t("code",[e._v("java:/XAConnectionFactory")]),e._v(")上查找"),t("code",[e._v("ConnectionFactory")]),e._v("来自动配置 JMS,并且你可以使用["),t("code",[e._v("spring.datasource.jndi-name")]),e._v("属性]来配置你的"),t("code",[e._v("DataSource")]),e._v("。")]),e._v(" "),t("h3",{attrs:{id:"_8-3-混合-xa-和非-xa-jms-连接"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_8-3-混合-xa-和非-xa-jms-连接"}},[e._v("#")]),e._v(" 8.3.混合 XA 和非 XA JMS 连接")]),e._v(" "),t("p",[e._v("在使用 JTA 时,主 JMS"),t("code",[e._v("ConnectionFactory")]),e._v(" Bean 是可感知 XA 的,并参与分布式事务。你可以在不需要使用任何"),t("code",[e._v("@Qualifier")]),e._v("的情况下注入到你的 Bean:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("public MyBean(ConnectionFactory connectionFactory) {\n // ...\n}\n\n")])])]),t("p",[e._v("在某些情况下,你可能希望通过使用非 XA"),t("code",[e._v("ConnectionFactory")]),e._v("来处理某些 JMS 消息。例如,你的 JMS 处理逻辑可能需要比 XA 超时更长的时间。")]),e._v(" "),t("p",[e._v("如果要使用非 XA"),t("code",[e._v("ConnectionFactory")]),e._v(",则可以使用"),t("code",[e._v("nonXaJmsConnectionFactory")]),e._v(" Bean:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('public MyBean(@Qualifier("nonXaJmsConnectionFactory") ConnectionFactory connectionFactory) {\n // ...\n}\n\n')])])]),t("p",[e._v("为了保持一致性,"),t("code",[e._v("jmsConnectionFactory")]),e._v(" Bean 还通过使用 Bean 别名"),t("code",[e._v("xaJmsConnectionFactory")]),e._v("来提供:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('public MyBean(@Qualifier("xaJmsConnectionFactory") ConnectionFactory connectionFactory) {\n // ...\n}\n\n')])])]),t("h3",{attrs:{id:"_8-4-支持另一种嵌入式事务管理器"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_8-4-支持另一种嵌入式事务管理器"}},[e._v("#")]),e._v(" 8.4.支持另一种嵌入式事务管理器")]),e._v(" "),t("p",[e._v("["),t("code",[e._v("XAConnectionFactoryWrapper")]),e._v('](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot/SRC/main/java/java/org/springframework/boot/boot/jms/xaconnectionfactorywrapper.java.java)和[<](https://github.com/ Spring-projects/[ Spring-tree/-tree/tree/v2.6.4/ Spring-boo 接口负责包装'),t("code",[e._v("XAConnectionFactory")]),e._v("和"),t("code",[e._v("XADataSource")]),e._v("bean,并将它们公开为常规的"),t("code",[e._v("ConnectionFactory")]),e._v("和"),t("code",[e._v("DataSource")]),e._v("bean,这些 bean 透明地登记在分布式事务中。数据源和 JMS 自动配置使用 JTA 变体,只要你有"),t("code",[e._v("JtaTransactionManager")]),e._v(" Bean 和在"),t("code",[e._v("ApplicationContext")]),e._v("中注册的适当的 XA 包装 bean。")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://github.com/spring-projects/spring-boot/tree/v2.6.4/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosXAConnectionFactoryWrapper.java",target:"_blank",rel:"noopener noreferrer"}},[e._v("AtomikosxaconnectionFactoryWrapper"),t("OutboundLink")],1),e._v("和"),t("a",{attrs:{href:"https://github.com/spring-projects/spring-boot/tree/v2.6.4/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosXADataSourceWrapper.java",target:"_blank",rel:"noopener noreferrer"}},[e._v("Atomikosxadatasourcewrapper"),t("OutboundLink")],1),e._v("为如何编写 XA 包装器提供了很好的示例。")]),e._v(" "),t("h2",{attrs:{id:"_9-接下来要读什么"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#_9-接下来要读什么"}},[e._v("#")]),e._v(" 9. 接下来要读什么?")]),e._v(" "),t("p",[e._v("你现在应该对 Spring boot 的"),t("RouterLink",{attrs:{to:"/spring-boot/features.html#features"}},[e._v("核心功能")]),e._v("以及 Spring boot 通过自动配置提供支持的各种技术有了很好的了解。")],1),e._v(" "),t("p",[e._v("接下来的几节将详细介绍如何将应用程序部署到云平台上。你可以在下一节中阅读有关"),t("RouterLink",{attrs:{to:"/spring-boot/container-images.html#container-images"}},[e._v("构建容器图像")]),e._v("的内容,也可以跳到"),t("RouterLink",{attrs:{to:"/spring-boot/actuator.html#actuator"}},[e._v("可投入生产的功能")]),e._v("的部分。")],1)])}),[],!1,null,null,null);a.default=n.exports}}]);