当大型项目进行大规模更新时,一切都不是那么简单:不可避免地会有细微的差别(换句话说,就是耙子)。 然后,无论文档的质量如何,只有您自己或他人的经验才能对您有所帮助。
在Joker 2018大会上,我谈到了我自己切换到Spring Boot 2时遇到的问题以及如何解决这些问题。 现在专门针对Habr-此报告的文本版本。 为了方便起见,该帖子同时提供了视频记录和目录:您无法阅读全部内容,但可以直接解决让您担心的问题。
目录
美好的一天! 我想告诉您有关将Spring Boot框架更新到第二个版本及其后续操作时可能遇到的一些功能(我们称它们为rakes)。
我叫VladimirPlizgá(
GitHub ),我在CFT工作,CFT是俄罗斯最大,最古老的软件开发商之一。 在过去的几年中,我一直在那里进行后端开发,负责预付卡在线银行的技术开发。 正是在这个项目中,我成为了从整体架构到微服务架构(仍在进行中)过渡的发起者和执行者。 好吧,由于我决定与您分享的大多数知识都是在这个特定项目的示例中积累的,因此,我将向您详细介绍一下。
简要介绍实验产品
这是一家互联网银行,为俄罗斯境内超过二十多家合作伙伴公司提供一手服务:它为最终客户提供了通过远程银行服务(移动应用程序,网站)管理其资金的能力。 合作伙伴之一是Beeline及其支付卡。 从
Markswebb Mobile Banking Rank的等级来看,事实证明,这对于Internet银行来说是相当不错的,我们的产品在初学者中
排名很高 。
“勇气”仍在过渡中,因此我们拥有一个整体,即所谓的核心,围绕它构建了23个微服务。 内部包含Spring Cloud Netflix微服务,Spring Integration等。 在Spring Boot 2上,整个过程从7月份开始就一直在进行。 而在这个地方,我们将更详细地介绍。 将这个项目翻译成第二版,我遇到了一些我想告诉你的功能。
报告大纲

Spring Boot 2的功能在很多地方都出现过,我们将尝试遍历所有领域。 为了快速做到这一点,我们需要经验丰富的侦探或调查员-像我们一样发掘所有这些东西的人。 由于Holmes和Watson已经在Joker上
做了演讲 ,我们将得到另一位专家科伦坡中尉的协助。 来吧!
春季靴/ 2
首先,简要介绍一下Spring Boot,尤其是第二个版本。 首先,发布此版本的目的是要适度而不是昨天发布:2018年3月1日,该版本已经正式发布。 开发人员追求的主要目标之一是在源代码级别完全支持Java 8。 也就是说,尽管运行时兼容,但不能在较小的版本上进行编译。 第五版的Spring Framework(比Spring Boot 2早一些发布)被作为基础,而这并不是唯一的依赖关系。 他还具有诸如
BOM (
物料清单 )之类的概念-这是一个巨大的XML,其中列出了对各种第三方库,其他框架,工具等的所有(对我们而言是可传递的)依赖项。
因此,第二个Spring Boot带来的所有特殊效果并非全部来自其自身或Spring生态系统。 已经为整个服务器场编写了两个出色的文档:
发行说明和
迁移指南 。 文档很酷,Spring在这个意义上通常做得很好。 但是,出于明显的原因,要覆盖那里的所有内容是远远不可能的:有些细节,偏差等无法包含或不应包含在其中。 我们将讨论这些功能。
编译时间。 API变更范例
让我们从或多或少简单和明显的角度开始:这些是在编译时出现的。 也就是说,如果仅将Boot脚本中的数字1更改为2,则甚至无法编译项目。
当然,更改的主要来源(成为Spring Boot进行此类编辑的基础)是Spring向Java 8的过渡。此外,相对而言,Spring 5和Spring Boot 2的Web堆栈也分为两部分。 现在它是servlet,对我们来说是传统的,并且是被动的。 此外,有必要考虑以前版本的许多缺点。 第三方库(从Spring外部)启动。 如果您查看发行说明,那么您将看不到任何陷阱,坦率地说,当我第一次阅读发行说明时,在我看来一切都很好。 对我来说,它看起来像这样:
但是,您可能会猜到,一切都不尽如人意。
编译将中断的内容(示例1):- 原因 :
WebMvcConfigurerAdapter
类不再存在; - 原因 :支持Java 8芯片(接口中的默认方法);
- 怎么做 :使用
WebMvcConfigurer
界面。
至少由于某些类不再存在的事实,项目可能无法编译。 怎么了 是的,因为在Java 8中不需要它们。 如果这些适配器具有方法的原始实现,则没有什么特别的解释,默认方法可以完美地解决所有这些问题。 这是此类的一个示例,很明显,使用接口本身就足够了,并且不需要适配器。
编译将中断(示例2):- 原因 :
PropertySourceLoader#load
方法开始返回源列表,而不是一个; - 原因 :支持多文档资源,例如YAML;
- 怎么做 :将响应包装在
singletonList()
(当被重写时)。
来自完全不同区域的示例。 有些方法甚至更改了签名。 如果您曾经使用过加载PropertySourceLoader方法,那么它现在将返回一个集合。 因此,这允许多文档资源的支持。 例如,在YAML中,可以通过三个破折号在一个文件中指定一堆文档。 如果现在需要通过Java使用它,请记住,这必须通过集合来完成。
编译将中断(示例3):- 原因 :
org.springframework.boot.autoconfigure.web
包中的某些类与org.springframework.boot.autoconfigure.web
包中的.reactive
和.reactive
; - 为什么 :与传统喷气机一样支持喷气机;
- 怎么做 :更新导入。
引入了更多的更改,从而进行堆叠。 例如,以前在同一个Web程序包中的内容现在已分成具有一堆类的两个程序包。 这些是
.reactive
和
.reactive
。 为什么要这样做? 因为喷气栈不应该成为servlet顶部的巨大拐杖。 必须这样做,以便他们可以维持自己的生命周期,朝着自己的方向发展并且不会互相干扰。 怎么办呢? 足以更改导入:这些类中的大多数在API级别上保持兼容。 大多数,但不是全部。
编译将中断(示例4):- 原因 :
ErrorAttributes
类的方法的签名已ErrorAttributes
:开始使用WebRequest(servlet)
和ServerRequest(reactive)
代替RequestAttributes
; - 为什么 :与传统喷气机一样支持喷气机;
- 怎么做 :替换签名中的类名。
例如,现在在ErrorAttributes类中,代替了RequestAttributes,在方法中开始使用了另外两个类:WebRequest和ServerRequest。 原因是一样的。 怎么办呢? 如果从第一个Spring Boot切换到第二个Spring Boot,则需要将RequestAttributes更改为WebRequest。 好吧,如果您已经处于第二个位置,请使用ServerRequest。 显然不是吗?
如何成为
有很多这样的例子;我们不会全部整理出来。 怎么办呢? 首先,值得一看的是《 Spring Boot 2.0迁移指南》,以便及时发现与您有关的更改。 例如,它提到重命名完全不明显的类。 但是,如果某些事情已经破裂和破裂,则值得考虑的是,“ web”的概念分为2:“ servlet”和“ reactive”。 通过在所有类和程序包中定向,这可以有所帮助。 另外,应该记住,不仅重命名了类和包本身,而且还重命名了整个依赖关系和工件。 例如,Spring Cloud就发生了这种情况。
内容类型。 确定HTTP响应的类型
从编译时开始,这些简单的事情就足够了,那里的一切都清晰明了。 让我们讨论一下,即使Spring Boot 2已经为您工作了很长时间,在运行时可能发生的事情并因此可以进行射击。 让我们谈谈内容类型的定义。

Spring可以编写页面和REST API的Web应用程序,并且可以呈现各种类型的内容(XML,JSON或其他类型),这已经不是什么秘密了。 Spring如此钟爱的魅力之一是,您不必费心在代码中指定类型的定义。 你可以希望魔术。 相对而言,这种魔术以三种不同的方式起作用:要么依赖于来自客户端的Accept标头,要么依赖于请求文件的扩展名,或者依赖于URL中的特殊参数,当然,也可以对其进行控制。
考虑一个简单的示例(
完整的源代码 )。 在下文中,我将使用Gradle的表示法,但是即使您是Maven粉丝,您也不难理解这里写的内容:我们在第一个Spring Boot上构建了一个微型应用程序,并且仅使用一个入门网站。
范例(v1.x):
dependencies { ext { springBootVersion = '1.5.14.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") }
作为可执行代码,我们有一个单独的类,其中的控制器方法立即被声明。
@GetMapping(value = "/download/{fileName: .+}", produces = {TEXT_HTML_VALUE, APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE}) public ResponseEntity<Resource> download(@PathVariable String fileName) {
它以某个文件名作为输入,据称它将形成并给出。 它实际上以三种指示的类型之一(由文件名确定)来形成其内容,但没有以任何方式指定内容类型-我们拥有Spring,他将自己做所有事情。

通常,您甚至可以尝试这样做。 确实,如果我们请求具有不同扩展名的同一文档,则将根据返回的内容返回正确的内容类型:如果需要-json,如果需要-txt,如果需要-html。 它像童话一样工作。
更新到v2.x
dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") }
现在该升级到第二个Spring Boot了。 我们只是将数字1更改为2。
Spring MVC路径匹配默认行为更改但是我们是工程师,我们将看一下《迁移指南》,突然之间有人对此发表意见。 但是它提到了某种“后缀路径匹配”。 它是关于如何使用URL正确映射Java中的方法。 但这不是我们的情况,尽管有点像。

因此,我们得分,检查并爆炸! -突然不起作用。 出于某种原因,随处可见文本/ html,如果您对其进行挖掘,不仅是文本/ html,还只是您在@GetMapping注释的Produces属性中指定的第一种类型。 为什么这样 坦率地说,它看起来令人难以理解。

在这里,任何发行说明都无济于事,您必须阅读源代码。
ContentNegotiationManagerFactoryBean
public ContentNegotiationManagerFactoryBean build() { List<ContentNegotiationStrategy> strategies = new ArrayList<>(); if (this.strategies != null) { strategies.addAll(this.strategies); } else { if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy;
在这里,您可以找到经典的名称,它的缩写很容易理解,其中提到了一个称为“在途中考虑扩展”(favorPathExtension)的标志。 该标志“ true”的值对应于某个策略的应用,该策略具有另一个易于理解的简短名称,从中可以明显看出,它仅负责通过文件扩展名确定内容类型。 如您所见,如果该标志为false,则该策略将不适用。

是的,可能很多人注意到Spring显然有某种指导方针,因此名称必须很好,至少20个字符长。

如果您深入研究,则可以挖掘出这样的片段。 在Spring框架本身中,而不是在第五版中,正如您所期望的那样,但是从远古时代开始,该标志默认情况下已设置为“ true”。 在Spring Boot和第二个版本中,它被另一个版本阻止,现在可以通过设置进行控制。 也就是说,现在我们可以从环境管理中引导他们,这仅是第二版。 你有感觉吗 在那里,他已经假设了“假”的含义。 也就是说,他们希望做到最好,将这个标志放在设置中(这很好),但是默认值已切换到另一个(不是很大)。
框架的开发人员也是人,他们也容易出错。 怎么办呢? 很明显,您需要在项目中切换参数,一切都会好起来的。
为了防患于未然,唯一值得做的事情就是看一下Spring Boot文档,以发现对该标志的任何提及。 在这里确实
提到了他,但只是在一些奇怪的情况下:
如果您了解了注意事项 ,但仍希望您的应用程序使用后缀模式匹配,则需要以下配置:
spring.mvc.contentnegotiation.favor-path-extension = true
...
他们说,如果您理解所有技巧,但仍然想使用后缀路径匹配,请选中该框。 感到差异? 似乎我们在此标记的上下文中讨论的是内容类型的定义,但是在这里我们在讨论Java方法和URL的匹配。 看起来有些难以理解。
我们必须进一步挖掘。 GitHub上有这样的
拉取请求 :

在此拉取请求的框架内,进行了这些更改-切换了默认值-框架的一位作者说此问题有两个方面:一个是路径匹配,第二是内容类型的定义。 也就是说,标志适用于两者,并且它们之间有着千丝万缕的联系。
当然,如果您只知道查找位置,则可以立即在GitHub上找到它。
后缀匹配此外,Spring框架本身的文档还指出,较早使用文件扩展名是必要的,但现在不再认为是必须的。 而且,在许多情况下,这被证明是有问题的。
总结一下
更改默认标志值根本不是一个错误,而是一个功能。 它与路径匹配的定义有着千丝万缕的联系,旨在完成
三件事 :
- 降低安全风险(我将澄清哪些风险);
- 调整WebFlux和WebMvc的行为,它们在这一方面有所不同;
- 使文档中的语句与框架代码对齐。
如何成为
首先,只要有可能,就不应该依赖扩展内容类型的定义。 我展示的示例是一个反例,无需这样做! 不仅不必依赖这样的事实,例如,形式为“ GET something.json”的请求将仅“ GET something”。 在Spring Framework 4和Spring Boot 1中就是这种情况。这不再起作用。 如果需要映射到具有扩展名的文件,则需要明确地执行此操作。 相反,最好依靠Accept标头或URL参数,您可以控制其名称。 好吧,如果您无法采取任何措施,例如,您有一些旧的移动客户端在上个世纪停止更新,那么您将必须返回此标志并将其设置为“ true”,并且所有内容都将像以前一样工作。
此外,为了获得一般性理解,您可以阅读Spring框架文档中的“后缀匹配”一章,开发人员将其视为该领域的一种最佳实践集合,并熟悉
Reflected File Download攻击是什么 ,只需使用以下操作即可实现文件扩展名。
排程。 计划或定期任务
让我们稍微改变一下范围,谈论按计划或定期完成任务。
任务示例。 每3秒记录一次消息
我认为所说的是可以理解的。 我们有一些业务需求,需要做一些重复,所以我们将立即进行示例。 假设我们有一个超级复杂的任务:每3秒向日志输出一些粪便。

显然,这可以通过多种方式来完成,对于Spring来说,无论如何,它们都已经存在了。 并找到它-很多方法。
选项1:在项目中搜索示例
@Service public class ReallyBusinessService {
我们可以查看自己的项目,并且可能会找到类似的内容。 一个注释将挂在公共方法上,从中可以清楚地看到,一旦挂起它,一切都将像童话故事一样工作。
选项2:搜索所需的注释

您可以直接按名称搜索注释,并且从文档中也可以清楚地看到您已将其挂起-一切正常。
选项3:Google搜索
如果您对自己不信任,那么可以谷歌搜索,并从
发现的内容中清楚地
发现,所有内容都将以一个注释开头。
@Component public class EventCreator { private static final Logger LOG = LoggerFactory.getLogger(EventCreator.class); private final EventRepository eventRepository; public EventCreator(final EventRepository eventRepository) { this.eventRepository = eventRepository; } @Scheduled(fixedRate = 1000) public void create() { final LocalDateTime start = LocalDateTime.now(); eventRepository.save( new Event(new EventKey("An event type", start, UUID.randomUUID()), Math.random() * 1000)); LOG.debug("Event created!"); } }
谁看到这个陷阱? 毕竟我们是工程师,让我们检查一下它是如何工作的。
给我看看代码!
考虑一个特定的任务(任务本身和代码
在我的资源库中 )。
谁不想读,您可以观看演示的视频片段(最多22分钟):
作为依赖,我们将使用具有两个启动器的第一个Spring Boot。 一种是用于网络,就像我们正在开发网络服务器,第二种是弹簧启动器致动器,因此我们具有可投入生产的功能,因此至少有点像真实的东西。
dependencies { ext { springBootVersion = '1.5.14.RELEASE'
而且我们的可执行代码将更加简单。
package tech.toparvion.sample.joker18.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.Scheduled; @SpringBootApplication public class EnableSchedulingDemoApplication { private static final Logger log = LoggerFactory.getLogger(EnableSchedulingDemoApplication.class); public static void main(String[] args) { SpringApplication.run(EnableSchedulingDemoApplication.class, args); } @Scheduled(fixedRate = 3000L) public void doOnSchedule() { log.info(“ 3 …”); } }
通常,除了悬挂注释的唯一方法之外,几乎没有什么特别的。 我们将其复制到某个地方,并期望它能正常工作。
让我们检查一下,我们是工程师。 我们开始。 我们假设每三秒钟会记录一次这样的消息。 一切都应该开箱即用,我们确保一切都在第一个Spring Boot上启动,并且我们期望得到期望的行的输出。 三秒钟过去-输出一行,六遍-显示一行。 乐观主义者赢了,一切正常。

只有时间来升级到第二个Spring Boot。 我们不会打扰,只需从一个切换到另一个即可:
dependencies { ext {
从理论上讲,《迁移指南》并没有警告我们任何事情,我们希望一切都会正常进行。 从可执行代码的角度来看,由于应用程序尽可能简单,因此我们没有前面提到的其他任何内容(在API级别或其他方面不兼容)。
我们开始。 首先,我们确信我们正在开发第二个Spring Boot,否则似乎没有任何偏差。

但是,经过3秒,即6、9,但是仍然没有德语-没有结论,没有任何效果。
通常,期望与现实不符。 他们经常在文档中给我们写信,实际上,所有东西在Spring Boot中都是开箱即用的,我们可以以最小的麻烦就这样开始,并且不需要任何配置。 但是,一旦谈到现实,通常就会发现仍然应该阅读该文档。 特别是,如果您深入研究,可以在以下几行中找到:
7.3.1。 启用计划注释
要启用对@Scheduled和Async批注的支持,可以将@EnableScheduling和@EnableAsync添加到您的@Configuration类之一。
为了使Scheduled注释起作用,您需要将另一个注释与另一个注释一起挂在类上。 好,像往常一样在春天。 但是为什么以前能起作用呢? 我们没有那样做。 显然,此注释在第一个Spring Boot的较早版本中已挂起,但由于某种原因现在不在第二个Spring Boot中。

我们开始翻阅第一个Spring Boot的源代码。 我们发现它应该挂在某个类上。 我们仔细观察一下,它被称为“ MetricExportAutoConfiguration”,并且显然负责将这些性能指标传递给一些集中的聚合器,并且确实具有此注释。

而且,它以这样的方式工作:它立即将其行为包括在整个应用程序中,而不必挂在单独的类上。 正是此类提供了这种行为,然后由于某种原因而没有这样做。 怎么了

相同的GitHub促使我们进行考古挖掘:作为向Spring Boot第二版过渡的一部分,该类与注释一起被删除。 怎么了 是的,因为指标交付引擎也发生了变化:他们不再使用自己的脚本,而是切换到了Micrometer-一个真正有意义的解决方案。 那只是他多余的东西。 也许这是正确的。
谁不想阅读,请观看30秒钟的简短演示:
因此,如果我们现在采用并手动将丢失的注释挂在我们的原始类中,那么从理论上讲,该行为应该变得正确。
package tech.toparvion.sample.joker18.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; @SpringBootApplication @EnableScheduling public class EnableSchedulingDemoApplication { private static final Logger log = LoggerFactory.getLogger(EnableSchedulingDemoApplication.class); public static void main(String[] args) { SpringApplication.run(EnableSchedulingDemoApplication.class, args); } @Scheduled(fixedRate = 3000L) public void doOnSchedule() { log.info(“ 3 …”); } }
您认为这行得通吗? 让我们检查一下。 我们开始。

可以看出,在3秒钟之后,6以后以及9以后,我们期望的消息仍然显示在日志中。
如何成为
在这种特殊且更普遍的情况下,该怎么办? 无论听起来有多道德,首先,值得一读的不仅是复制的文档片段,而且还有一点更广的范围,以涵盖这些方面。
其次,请记住,在Spring Boot中,即使许多功能都是现成的(计划,异步,缓存等),它们也不总是包括在内,必须显式启用。
第三,安全并不麻烦:在代码中添加
Enable *注释(及其整个家族),而不希望使用框架。 但是随后出现了一个问题:如果我和我的同事偶然添加一些注释,会发生什么情况? , . : . , .
, @EnableAsync
Enable Caching , , , , , . , . ? javadoc , . , . ,
Enable *, , . ? .
Spring Cloud & Co.

Spring Boot 2 , Spring Cloud — Service Discovery ( ). JavaMelody. - . , , JDBC, H2.

, , JavaMelody — , , . dev-, test, - , Prometheus.
Gradle :
dependencies { ext { springBootVersion = '2.0.4.RELEASE' springCloudVersion = '2.0.1.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") runtime("org.springframework.boot:spring-boot-starter-jdbc:$springBootVersion") runtime group: "org.springframework.cloud", name: "spring-clooud-starter-netflix-eureka-client", version: springCloudVersion runtime("net.bull.javamelody:javamelody-spring-boot-starter:1.72.0")
( )Spring Boot — web jdbc, Spring Cloud eureka (, , Service Discovery), JavaMelody. .
@SpringBootApplication public class HikariJavamelodyDemoApplication { public static void main(String[] args) { SpringApplication.run(HikariJavamelodyDemoApplication.class, args); } }
我们开始。

. , , - com.sun.proxy Hikari, HikariDataSource. , Hikari — , Tomcat, C3P0 .
? .
Spring Cloud dataSource
, Spring Cloud , dataSource ( ), . , AutoRefresh RefreshScope — . . CGLIB.
, , Spring Boot Spring : JDK ( , ) CGLIB ( ). BeanPostProcessor' BeanDefinition , .
JavaMelody dataSource
— JavaMelody. DataSource , , . JavaMelody JDK-, , . — BeanPostProcessor.
, , DataSource JDK-, CGLIB-. :

. , .
Spring Boot dataSource.unwrap()
Spring Boot, DataSource#unwrap(), JMX. JDK- ( ), CGLIB-, Spring Cloud, Spring Context. , , JDK-, CGLIB API .
, :
https://jira.spring.io/browse/SPR-17381, , , . , , , , - .

. Hikari?
, Hikari - , Spring Cloud . : Hikari Spring Boot 2. ? - - . , Spring Cloud? , - , ? . , .
…
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration .RefreshScopeBeanDefinitionEnhancer: private Set<String> refreshables = new HashSet<>( Arrays.asList("com.zaxxer.hikari.HikariDataSource"));
Spring Cloud autoconfiguration, Enhancer BeanDefinition', , Hikari. Spring Cloud . .
? Spring Cloud , CGLIB-. , , , , - . (jira.spring.io/browse/SPR-17381). BeanPostProcessor, . BeanDefinition , BeanPostProcessor'. Stack Overflow , - , , proxyTargetClass true false , . , . .
, - , .
:
- (, Tomcat JDBC Pool)
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
runtime 'org.apache.tomcat:tomcat-jdbc:8.5.29'
Hikari , , , , , Tomcat, Spring Boot. - JavaMelody, JDBC-, .
javamelody.excluded-datasources=scopedTarget.dataSource - Spring Cloud.
spring.cloud.refresh.enabled=false
, , , Service Discovery, .
. , .
( *)
* Spring Cloud ( JavaMelody)
@Component @ManagedResource @EnableAsync public class MyJmxResource { @ManagedOperation @Async public void launchLongLastingJob() {
:
github.com/toparvion/joker-2018-samples/tree/master/jmx-resource .
. , Spring Cloud. JavaMelody , Spring-, . , , , JMX . - , Async, JMX- . JMX, @ManagedOperation, , ( Spring — , OK).
, , , , , myJMXResource JMX, . , — , CGLIB JDK.

JDK CGLIB-. , - BeanPostProcessor.
, BeanPostProcessor':
AsyncAnnotationBeanPostProcessor
- : Async
- : org.springframework.scheduling
- : @EnableAsync ( Import )
2. DefaultAdvisorAutoProxyCreator
- : AOP-,
- : org.springframework.aop.framework.autoproxy
- : @Configuration- PointcutAdvisorConfig ( )
DefaultAdvisorAutoProxyCreator @Configuration-. , , JavaMelody, configuration-. , PointcutAdvisorConfig, .

, . PointcutAdvisorConfig, AdvisorConfig, , configuration-, , , , , .
, , , , -.

BeanPostProcessor'. , , , BeanPostProcessor . , Advised ( BeanPostProcessor'), , , , , , JDK-, . .
. , :

JMX . BeanPostProcessor. BeanPostProcessor', , , , , JAR, , .
如何成为
-, , Spring AOP, , . « »? , - Advice Advisor, , .
-, best practices. , JMX- , . - , , , . , autowire' () . . , , - .
Order , . , , , .. proxyTargetClass, .
: , . -, «Keep calm and YAGNI». , . « », - , - , , , . , , : -, , — , . . , Spring , , .
tolkkv , , 436- , . , .
Relax Binding. ()
, .
https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#boot-features-external-config-relaxed-binding, Relax Binding Spring Boot. - . , - firstName , acme.my-project.person, Spring Boot . : camel case, , , - — firstName. Relax Binding.
Spring Boot' , , — . , , , :
, , . - . , .
例如:
dependencies { ext { springBootVersion = '1.5.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter:$springBootVersion") }
(
)
- , web, Spring Boot, - .
@SpringBootApplication public class RelaxBindingApplication implements ApplicationRunner { private static final Logger log = LoggerFactory.getLogger(RelaxBindingDemoApplication.class); @Autowired private SecurityProperties securityProperties; public static void main(String[] args) { SpringApplication.run(RelaxBindingDemoApplication.class, args); } @Override public void main run(ApplicationArguments args) { log.info("KEYSTORE TYPE IS: {}", securityProperties.getKeyStoreType()); } }
, POJO- ( ) , KEYSTORE TYPE. POJO , applications.properties application.yaml, .
keystoreType, private String keystoreType, applications.properties: security.keystoreType=jks.
@Component @ConfigurationProperties(prefix = "security") public class SecurityProperties { private String keystorePath; private String keystoreType; public String getKeystorePath() { return keystorePath; } public void setKeystorePath(String keystorePath) { this.keystorePath = keystorePath; } public String getKeyStoreType() { return keystoreType; } public void setKeystoreType(String keystoreType) { this.keystoreType = keystoreType; } }
Spring Boot .

, , , . , .

, . , , , - , , - key-store-type. , , , .
. , .

. 2 , , . Java properties — , . , , , , , . — Java bean . , , . , «keystore» , : «Key» «Store». …

, , ? .
, , Relax Binding ( getStoreType()). , . , . , keyStoreType, . , Relax Binding, , , .
, - , - , . , . :

, - , , , , -, . .
如何成为
: - . -, , dev- , , YAML properties, — , , . -,
c , Relax Binding, . , , , Spring Boot .
Unit Testing. Mockito 2
, - , Mockito.
Mockito , Spring Boot Starter, Spring, Mockito.
$gradle -q dependencyInsight --configuration testCompile --dependency mockito org.mockito:mockito-core:2.15.0 variant "runtime" /--- org.springframework.boot:spring-boot-starter-test:2.0.2.RELEASE /---testCompile
? . Spring Boot Mockito , 1.5.2 Spring Boot Mockito 2, . - . Mockito 2.
Mockito , 2016- Mockito 2.0 Mockito.2.1— : Java 8 , Hamcrest - . , , .
, , ( ) .

, , JButton Swing, null, , - . , string' null, , null instanceof string. , Mockito 1 , Mockito 2 , anyString null , , . : null, .
, , , Mockito 1.
, , .
public class MyService { public void setTarget(Object target) {
, , . JButton. anyString. , : , — . , . - 10- , . Mockito 1 , :

Mockito 2 , , , anyString :

. , . , , , , SocketTimeoutException, - . , SocketTimeoutException , . Mockito 1.

Mockito 2 , :

, Mockito 1 , , . new SocketTimeoutException new, constructor, Mockito 1 .
怎么办呢? , , RuntimeException, , Mockito .
. , . compile-time. - , Hamcrest. Spring Boot, Mockito 1 @MockBean @SpyBean. , Spring Integration, review.
(:
https://docs.spring.io/spring-integration/docs/5.0.0.RELEASE/reference/htmlsingle/#testing )
如何成为
, , Mockito 1, Mockito 2 (
dzone.com/refcardz/mockito ).
-, , , Spring Boot 1.5.2 Mockito 2.
-, , , Mockito 2, :
,
.
Gradle Plugin. Spring Boot
, , — Spring Boot- Gradle.
Migration Guide , Spring Boot Gradle . , . : Gradle 4 (, settings.gradle ). dependency management plugin, . bootRepackage, , : bootWar bootJar. bootJar .
bootJar:
- , org.springframework.boot java;
- jar;
- mainClassName ( ) ( , - ).
, , — , , Gradle, Spring Boot.
? - Spring Boot 2, , , Gradle 4 Spring Boot-. , , : , , ( , ).

, . app1 , app2, app3 . . app1 lib.
«Show me the code!»
subprojects { repositories { mavenCentral() } apply plugin: 'java' apply plugin: 'org.springframework.boot' }
— : java Spring Boot .
, , , . , , , . .
app1:
dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter:$springBootVersion") compile project(':lib') }
lib , Spring Boot-.
app1:
@SpringBootApplication public class GradlePluginDemoApplication implements ApplicationRunner {
Util, .
lib:
public abstract class Util { public static String getAppVersion(Class<?> appClass) { return appClass.getPackage().getImplementationVersion(); } }
Util getAppVersion , , ImplementationVersion . .

IDE, , , . gradle build IDE , . Util. , , , , .
:
:
- ;
- , jar ( ImplementationVersion), .
? : .

Spring Boot- , , lib . .
2: SB Gradle Plugin Spring Boot-
bootJar { enabled = false }
, - , , , , , bootJar . , jar , .
, , , Spring Boot. .

Spring Boot : web-, , . - , , Spring Boot properties migrator, , , . , , -, .
Actuator. , () , Spring Security. .

Spring Cloud . , . , Netflix Feign.

Spring Integration, , Spring Framework, . — , Java DSL , , . , , , , handle handleWithAdapter.
.
, , :

, , , Web, .
(Properties Binding), , , Relax Binding.
— , : , AOP , , Spring Boot 2 .
, , , — , Mockito 1 Mockito 2. - , ?
-, , , , YAGNI. , - , . , , .
-, - - , , , . , , , , . Migration Guide. , , , , , .
, Spring Boot. , Spring Boot, , … .
, : 5-6 JPoint , Spring Boot: Spring Boot- Java 8 Java 11. — .