# 6.8. 开发者工具

Spring Boot 包含一组额外的工具，可以使应用程序开发体验更加愉快。`spring-boot-devtools`模块可以包含在任何项目中以提供额外的开发时功能。要包含 devtools 支持，请将模块依赖项添加到您的构建中，如以下 Maven 和 Gradle 列表所示：

*Maven*

```
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
```

*Gradle*

```
dependencies {
    developmentOnly("org.springframework.boot:spring-boot-devtools")
}
```

> 开发工具可能会导致类加载问题，特别是在多模块项目中。 [诊断类加载问题](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.diagnosing-classloading-issues)解释了如何诊断和解决这些问题。
>
> 运行完全打包的应用程序时，开发人员工具会自动禁用。如果您的应用程序是从`java -jar`特殊的类加载器启动的，则它被视为“生产应用程序”。您可以使用`spring.devtools.restart.enabled`系统属性来控制此行为。要启用 devtools，无论用于启动应用程序的类加载器如何，请设置`-Dspring.devtools.restart.enabled=true`系统属性。不得在运行 devtools 存在安全风险的生产环境中执行此操作。要禁用开发工具，请排除依赖项或设置`-Dspring.devtools.restart.enabled=false`系统属性。
>
> 在 Maven 中将依赖项标记为可选或使用`developmentOnly`Gradle 中的配置（如上所示）可防止 devtools 传递应用于使用您的项目的其他模块。
>
> 重新打包的存档默认不包含开发工具。如果你想使用[某个远程开发工具功能](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.remote-applications)，你需要包含它。使用 Maven 插件时，将该`excludeDevtools`属性设置为`false`。使用 Gradle 插件时，[配置任务的类路径以包含`developmentOnly`配置](https://docs.spring.io/spring-boot/docs/3.2.0/gradle-plugin/reference/htmlsingle/#packaging-executable-configuring-including-development-only-dependencies).

**6.8.1. 诊断类加载问题**

[如重新启动与重新加载](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.restart-vs-reload)部分所述，重新启动功能是通过使用两个类加载器来实现的。对于大多数应用程序来说，这种方法效果很好。但是，它有时会导致类加载问题，特别是在多模块项目中。

要诊断类加载问题是否确实是由 devtools 及其两个类加载器引起的，[请尝试禁用 restart](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.disable)。如果这解决了您的问题，[请自定义重新启动类加载器](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.customizing-the-classload)以包含您的整个项目。

**6.8.2. 属性默认值**

Spring Boot 支持的一些库使用缓存来提高性能。例如，[模板引擎](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.template-engines)会缓存编译后的模板，以避免重复解析模板文件。此外，Spring MVC 可以在提供静态资源时向响应添加 HTTP 缓存标头。

虽然缓存在生产中非常有益，但在开发过程中可能会适得其反，使您无法看到刚刚在应用程序中所做的更改。因此，spring-boot-devtools 默认禁用缓存选项。

缓存选项通常通过`application.properties`文件中的设置进行配置。例如，Thymeleaf 就提供`spring.thymeleaf.cache`属性。该模块不需要手动设置这些属性，而是`spring-boot-devtools`自动应用合理的开发时配置。

下表列出了所有应用的属性：

| 姓名                                               | 默认值               |
| ------------------------------------------------ | ----------------- |
| `server.error.include-binding-errors`            | `always`          |
| `server.error.include-message`                   | `always`          |
| `server.error.include-stacktrace`                | `always`          |
| `server.servlet.jsp.init-parameters.development` | `true`            |
| `server.servlet.session.persistent`              | `true`            |
| `spring.docker.compose.readiness.wait`           | `only-if-started` |
| `spring.freemarker.cache`                        | `false`           |
| `spring.graphql.graphiql.enabled`                | `true`            |
| `spring.groovy.template.cache`                   | `false`           |
| `spring.h2.console.enabled`                      | `true`            |
| `spring.mustache.servlet.cache`                  | `false`           |
| `spring.mvc.log-resolved-exception`              | `true`            |
| `spring.reactor.netty.shutdown-quiet-period`     | `0s`              |
| `spring.template.provider.cache`                 | `false`           |
| `spring.thymeleaf.cache`                         | `false`           |
| `spring.web.resources.cache.period`              | `0`               |
| `spring.web.resources.chain.cache`               | `false`           |

如果您不希望应用属性默认值，您可以在`application.properties`将相应的`spring.devtools.add-properties`设置为 `false`

由于您在开发 Spring MVC 和 Spring WebFlux 应用程序时需要有关 Web 请求的更多信息，因此开发人员工具建议您为`web`日志记录组启用`DEBUG`日志记录。这将为您提供有关传入请求、正在处理该请求的处理程序、响应结果以及其他详细信息的信息。如果您希望记录所有请求详细信息（包括潜在的敏感信息），您可以打开`spring.mvc.log-request-details`或`spring.codec.log-request-details`配置属性。

**6.8.3. 自动重启**

`spring-boot-devtools`每当类路径上的文件发生更改时，使用自动重新启动的应用程序。在 IDE 中工作时，这可能是一个有用的功能，因为它为代码更改提供了非常快速的反馈循环。默认情况下，类路径上指向目录的任何条目都会受到监视以了解更改。请注意，某些资源（例如静态资产和视图模板）[不需要重新启动应用程序](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.excluding-resources)。

> **触发重启**
>
> 由于 DevTools 监视类路径资源，触发重新启动的唯一方法是更新类路径。无论您使用 IDE 还是构建插件之一，都必须重新编译修改的文件才能触发重新启动。更新类路径的方式取决于您使用的工具：
>
> * 在 Eclipse 中，保存修改的文件会导致类路径更新并触发重新启动。
> * 在 IntelliJ IDEA 中，构建项目 ( `Build +→+ Build Project`) 具有相同的效果。
> * 如果使用构建插件，运行Maven`mvn compile` 或Gradle 将 `gradle build`触发重新启动。
>
> 如果您使用构建插件重新启动 Maven 或 Gradle，则必须将设置保留`forking`为`enabled`. 如果禁用分叉，则将不会创建 devtools 使用的隔离应用程序类加载器，并且重新启动将无法正常运行。
>
> 与 LiveReload 一起使用时，自动重启效果非常好。 [有关详细信息，请参阅 LiveReload 部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.livereload)。如果您使用 JRebel，则会禁用自动重新启动，以支持动态类重新加载。仍然可以使用其他开发工具功能（例如 LiveReload 和属性覆盖）。
>
> DevTools 依赖应用程序上下文的关闭挂钩在重新启动期间将其关闭。如果您禁用了关闭挂钩 (`SpringApplication.setRegisterShutdownHook(false)` )，它将无法正常工作。
>
> DevTools 需要通过`ApplicationContext`自定义`ResourceLoader`. 如果您的应用程序已经提供了一个`ApplicationContext`，那么它将被包装。将不支持`ApplicationContext`直接重写方法`getResource`。
>
> 使用 AspectJ 编织时不支持自动重启。

> **重新启动与重新加载**
>
> Spring Boot 提供的重启技术通过使用两个类加载器来工作。不更改的类（例如，来自第三方 jar 的类）将加载到*基*类加载器中。您正在积极开发的类将加载到*重新启动的*类加载器中。当应用程序重新启动时，*重新启动的*类加载器将被丢弃并创建一个新的类加载器。这种方法意味着应用程序重新启动通常比“冷启动”快得多，因为*基类*加载器已经可用并已填充。
>
> 如果您发现应用程序的重新启动速度不够快或者遇到类加载问题，您可以考虑重新加载技术，例如ZeroTurnaround 的[JRebel 。](https://jrebel.com/software/jrebel/)这些工作原理是在加载类时重写类，使它们更适合重新加载。

**记录条件计算的变化**

默认情况下，每次应用程序重新启动时，都会记录一份显示增量的条件计算报告。当您进行更改（例如添加或删除 Bean 以及设置配置属性）时，该报告会显示应用程序自动配置的更改。

要禁用报告日志记录，请设置以下属性：

```
spring.devtools.restart.log-condition-evaluation-delta=false
```

**排除资源**

某些资源在更改时不一定需要触发重新启动。例如，Thymeleaf 模板可以就地编辑。默认情况下，更改`/META-INF/maven`、`/META-INF/resources`、`/resources`、`/static`、`/public`或`/templates`中的资源不会触发重新启动，但会触发[实时重新加载](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.livereload)。如果您想自定义这些排除项，可以使用该`spring.devtools.restart.exclude`属性。例如，要仅排除`/static`，`/public`您可以设置以下属性：

```
spring.devtools.restart.exclude=static/**,public/**
```

> 如果您想保留这些默认值并*添加*其他排除项，请改用`spring.devtools.restart.additional-exclude`属性

**监控其他路径**

当您对不在类路径上的文件进行更改时，您可能希望重新启动或重新加载应用程序。为此，请使用该`spring.devtools.restart.additional-paths`属性配置其他路径以监视更改。您可以使用[前面描述的](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.excluding-resources)`spring.devtools.restart.exclude`属性来控制其他路径下的更改是否触发完全重新启动或[实时重新加载](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.livereload)。

**禁用重新启动**

如果您不想使用重新启动功能，可以使用`spring.devtools.restart.enabled` 属性将其禁用。在大多数情况下，您可以在您的`application.properties`文件中设置此属性（这样做仍然会初始化重新启动类加载器，但它不会监视文件更改）。

如果您需要*完全*禁用重新启动支持（例如，因为它不适用于特定库），则需要在 `SpringApplication.run(…)`之前将`spring.devtools.restart.enabled` `System`属性设置为`false`，如以下示例所示：

```
@SpringBootApplication
public class MyApplication {
​
    public static void main(String[] args) {
        System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(MyApplication.class, args);
    }
​
}
```

**使用触发文件**

如果您使用连续编译更改的文件的 IDE，您可能更愿意仅在特定时间触发重新启动。为此，您可以使用“触发文件”，这是一个特殊文件，当您想要实际触发重新启动检查时必须修改该文件。

> 对文件的任何更新都会触发检查，但只有在 Devtools 检测到有事情要做时才会真正重新启动。

要使用触发器文件，请将该`spring.devtools.restart.trigger-file`属性设置为触发器文件的名称（不包括任何路径）。触发器文件必须出现在类路径上的某个位置。

例如，如果您有一个具有以下结构的项目：

```
src
+- main
   +- resources
      +- .reloadtrigger
```

那么你的`trigger-file`属性将是：

```
spring.devtools.restart.trigger-file=.reloadtrigger
```

现在只有在更新时才会重新启动`src/main/resources/.reloadtrigger`。

> 您可能希望设置`spring.devtools.restart.trigger-file`为[全局设置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.globalsettings)，以便所有项目都以相同的方式运行。

某些 IDE 具有使您无需手动更新触发器文件的功能。 [Spring Tools for Eclipse](https://spring.io/tools)和[IntelliJ IDEA（终极版）](https://www.jetbrains.com/idea/)都有这样的支持。使用 Spring Tools，您可以使用控制台视图中的“重新加载”按钮（只要您的`trigger-file`名称为`.reloadtrigger`）。对于 IntelliJ IDEA，您可以按照[其文档中的说明](https://www.jetbrains.com/help/idea/spring-boot.html#application-update-policies)进行操作。

**自定义重启类加载器**

[如前面的“重新启动与重新加载”](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.restart-vs-reload)部分所述，重新启动功能是通过使用两个类加载器来实现的。如果这导致问题，您可能需要自定义由哪个类加载器加载的内容。

默认情况下，IDE 中任何打开的项目都会使用“重新启动”类加载器加载，任何常规`.jar`文件都会使用“基本”类加载器加载。如果您使用`mvn spring-boot:run`或者`gradle bootRun`，情况也是如此：包含`@SpringBootApplication`的项目使用“重新启动”类加载器加载，其他所有内容都使用“基本”类加载器加载。

您可以通过创建`META-INF/spring-devtools.properties`文件来指示 Spring Boot 使用不同的类加载器加载项目的部分内容。`spring-devtools.properties`文件可以包含前缀为`restart.exclude`和`restart.include` 的属性。这些`include`元素是应该被拉入“重新启动”类加载器的项目，这些`exclude`元素是应该被推入“基本”类加载器的项目。该属性的值是应用于类路径的正则表达式模式，如以下示例所示：

```
restart.exclude.companycommonlibs=/mycorp-common-[\\w\\d-\\.]+\\.jar
restart.include.projectcommon=/mycorp-myproj-[\\w\\d-\\.]+\\.jar
```

> 所有属性键必须是唯一的。只要一个属性以`restart.include.`或`restart.exclude.`开始，就会被采纳。
>
> 所有`META-INF/spring-devtools.properties`来自类路径的内容都会被加载。您可以将文件打包到项目内或项目使用的库中。

**已知限制**

重新启动功能不适用于使用标准`ObjectInputStream`. 如果您需要反序列化数据，可能需要结合使用Spring的`ConfigurableObjectInputStream`与`Thread.currentThread().getContextClassLoader()`.

不幸的是，一些第三方库在不考虑上下文类加载器的情况下进行反序列化。如果您发现此类问题，您需要向原作者请求修复。

**6.8.4. 实时重载**

`spring-boot-devtools`模块包括一个嵌入式 LiveReload 服务器，可用于在资源更改时触发浏览器刷新。LiveReload 浏览器扩展可免费用于 Chrome、Firefox 和 Safari。您可以通过在所选浏览器的市场或商店中搜索“LiveReload”来找到这些扩展。

如果您不想在应用程序运行时启动 LiveReload 服务器，可以将`spring.devtools.livereload.enabled`属性设置为`false`。

> 您一次只能运行一台 LiveReload 服务器。在启动应用程序之前，请确保没有其他 LiveReload 服务器正在运行。如果您从 IDE 启动多个应用程序，则只有第一个应用程序具有 LiveReload 支持。
>
> 要在文件更改时触发 LiveReload，必须启用 [自动重新启动。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart)

**6.8.5. 全局设置**

您可以通过将以下任意文件添加到`$HOME/.config/spring-boot`目录来配置全局 devtools 设置：

1. `spring-boot-devtools.properties`
2. `spring-boot-devtools.yaml`
3. `spring-boot-devtools.yml`

添加到这些文件的任何属性都适用于您计算机上使用 devtools 的*所有*Spring Boot 应用程序。例如，要将重新启动配置为始终使用[触发器文件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.triggerfile)，您可以将以下属性添加到`spring-boot-devtools`文件中：

```
spring.devtools.restart.trigger-file=.reloadtrigger
```

默认情况下，`$HOME`是用户的主目录。要自定义此位置，请设置`SPRING_DEVTOOLS_HOME`环境变量或`spring.devtools.home`系统属性。

> 如果在`$HOME/.config/spring-boot` 中找不到 devtools 配置文件，则会在`$HOME`目录的根目录中搜索是否存在`.spring-boot-devtools.properties`文件。这允许您与不支持该`$HOME/.config/spring-boot`位置的旧版本 Spring Boot 上的应用程序共享 devtools 全局配置。
>
> devtools properties/yaml 文件不支持配置文件。
>
> 在 `.spring-boot-devtools.properties`中激活的任何配置文件都不会影响特定于配置文件的配置文件的加载。 YAML 和 Properties 文件中的配置文件特定文件名（格式为 `spring-boot-devtools-.properties`）和 `spring.config.activate.on-profile`文档不受支持。

**配置文件系统观察器**

[FileSystemWatcher 的](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java)工作原理是按照一定的时间间隔轮询类更改，然后等待预定义的安静期以确保不再有更改。由于 Spring Boot 完全依赖 IDE 来编译文件并将文件复制到 Spring Boot 可以读取它们的位置，因此您可能会发现，有时当 devtools 重新启动应用程序时，某些更改不会反映出来。如果您经常观察到此类问题，请尝试将`spring.devtools.restart.poll-interval`和`spring.devtools.restart.quiet-period`参数增加到适合您的开发环境的值：

```
spring.devtools.restart.poll-interval=2s
spring.devtools.restart.quiet-period=1s
```

现在每 2 秒轮询一次受监视的类路径目录是否有更改，并保持 1 秒的安静期以确保没有其他类更改。

**6.8.6. 远程应用程序**

Spring Boot开发者工具不仅限于本地开发。您还可以在远程运行应用程序时使用多种功能。远程支持是可选的，因为启用它可能会带来安全风险。仅当在受信任的网络上运行或使用 SSL 保护时才应启用它。如果您无法使用这些选项，则不应使用 DevTools 的远程支持。您永远不应该启用对生产部署的支持。

要启用它，您需要确保它`devtools`包含在重新打包的存档中，如下列表所示：

```
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludeDevtools>false</excludeDevtools>
            </configuration>
        </plugin>
    </plugins>
</build>
```

然后你需要设置`spring.devtools.remote.secret`属性。与任何重要的密码或秘密一样，该值应该是唯一且强大的，以便无法猜测或暴力破解。

远程开发工具支持分为两部分：接受连接的服务器端端点和在 IDE 中运行的客户端应用程序。设置`spring.devtools.remote.secret`属性后，服务器组件会自动启用。客户端组件必须手动启动。

> Spring WebFlux 应用程序不支持远程开发工具。

**运行远程客户端应用程序**

远程客户端应用程序设计为在 IDE 中运行。您需要使用`org.springframework.boot.devtools.RemoteSpringApplication`与连接到的远程项目相同的类路径来运行。应用程序的唯一必需参数是它连接的远程 URL。

例如，如果您使用 Eclipse 或 Spring Tools，并且您有一个名为`my-app`您已部署到 Cloud Foundry 的项目，则您将执行以下操作：

* 从`Run`菜单中选择`Run Configurations…`。
* 创建一个新的`Java Application`“启动配置”。
* 浏览`my-app`项目。
* `org.springframework.boot.devtools.RemoteSpringApplication`作为主类使用。
* 添加`https://myapp.cfapps.io`到`Program arguments`（或任何您的远程 URL）。

正在运行的远程客户端可能类似于以下列表：

```
  。____ _ __ _ _
 /\\ / ___'_ __ _ _(_)_ __ __ _ ___ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | | _ \___ _ __ ___| |_ ___ \ \ \ \
 \\/ ___)| |_)| | | | | || (_| []::::::[] / -_) ' \/ _ \ _/ -_) ) ) ) )
  ' |____| .__|_| |_|_| |_\__, | |_|_\___|_|_|_\___/\__\___|/ / / /
 ========|_|==============|___/====================== =============/_/_/_/
 :: Spring Boot 远程 :: (v3.2.0)
​
2023-11-23T13:40:36.387Z INFO 39306 --- [ main] osbdevtools.RemoteSpringApplication ：使用 PID 39306 的 Java 17.0.9 启动 RemoteSpringApplication v3.2.0 (/Users/myuser/.m2/repository/org/springframework/ boot/spring-boot-devtools/3.2.0/spring-boot-devtools-3.2.0.jar 由 myuser 在 /opt/apps/ 中启动）
2023-11-23T13:40:36.394Z INFO 39306 --- [ main] osbdevtools.RemoteSpringApplication ：没有设置活动配置文件，回退到 1 个默认配置文件：“default”
2023-11-23T13:40:36.794Z INFO 39306 --- [ main] osbdaOptionalLiveReloadServer ：LiveReload 服务器正在端口 35729 上运行
2023-11-23T13:40:36.823Z INFO 39306 --- [ main] osbdevtools.RemoteSpringApplication ：在 0.923 秒内启动 RemoteSpringApplication （进程运行 1.313）
```

> 由于远程客户端使用与真实应用程序相同的类路径，因此它可以直接读取应用程序属性。这就是`spring.devtools.remote.secret`读取属性并将其传递到服务器进行身份验证的方式。
>
> 始终建议使用`https://`作为连接协议，以便对流量进行加密并且密码无法被拦截。
>
> 如果需要使用代理访问远程应用程序，请配置`spring.devtools.remote.proxy.host`和`spring.devtools.remote.proxy.port`属性。

**远程更新**

[远程客户端以与本地重新启动](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart)相同的方式监视应用程序类路径的更改。任何更新的资源都会被推送到远程应用程序并（*如果需要*）触发重新启动。如果您迭代使用本地没有的云服务的功能，这会很有帮助。一般来说，远程更新和重新启动比完整的重建和部署周期要快得多。

在较慢的开发环境中，可能会出现安静期不够的情况，类中的更改可能会被分成批次。第一批类更改上传后，服务器将重新启动。由于服务器正在重新启动，因此无法将下一批发送到应用程序。

这通常通过`RemoteSpringApplication`日志中有关无法上传某些类的警告以及随后的重试来体现。但也可能导致应用代码不一致，第一批变更上传后无法重启。如果您经常观察到此类问题，请尝试将`spring.devtools.restart.poll-interval`和`spring.devtools.restart.quiet-period`参数增加到适合您的开发环境的值。有关配置这些属性的信息，请参阅[配置文件系统观察器部分。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.globalsettings.configuring-file-system-watcher)

> 仅当远程客户端运行时才会监视文件。如果在启动远程客户端之前更改文件，则该文件不会推送到远程服务器。
