[spring-projects/spring-boot]Spring Boot 应该允许配置需要重新加载哪些依赖项

2024-06-26 866 views
1

假设我们有一个带有子项目的 gradle 项目。

.
├── app
│   ...
│   └── build.gradle.kts
├── lib
│   ...
│   └── build.gradle.kts
└── settings.gradle.kts
// settings.gradle.kts
rootProject.name = "myproject"
include("app")
include("lib")
// app/build.gradle.kts

dependencies {
  developmentOnly("org.springframework.boot:spring-boot-devtools")
  implementation(project(":lib"))
}

现在,我们运行:app:bootRun。每次我们运行:app:compileKotlinkotlin 项目(或:app:compileJavajava 项目)时,应用程序都会使用子项目中的更改重新加载app=> 一切正常,热重载完美运行。

当我们运行的时候:lib:compileKotlin,应用程序被重新加载。但是,子项目的更改lib并不是最新的。

据我所知,Spring Boot 分为 2 个类路径。一个用于app子项目 (类路径 1),另一个用于所有其他依赖项 (类路径 2)。

如果有办法告诉 Spring Boot 将特定依赖项从“classpath 2”移动到“classpath 1”,那就太好了。在本例中,我们想告诉 Spring Boot 还应该重新加载依赖项的编译文件:lib

我不是这方面的专家,但我想象在 application.properties 中有类似这样的内容:

spring.devtools.restart.additional-hot-reload-packages=com.company.myproject.lib

如果 spring hot reload 机制需要在加载 application.properties 之前进行此配置,我们可以通过环境变量进行配置。

这样,无论 maven、gradle、IDE 或任何工具,配置都可以起作用。

回答

6

这应该已经是可能的了。请参阅参考文档的相关部分以了解详细信息。

哦,谢谢您的及时回复!

我将检查它是如何工作的。

0

@wilkinsona 仍然不起作用。

我添加restart.include.lib=/lib-[\\s\\S]+\.jar到了app/resources/META-INF/spring-devtools.properties

然后我为org.springframework.boot.devtools.settings.DevToolsSettings#isRestartInclude()方法创建一个断点。断点返回jartrue的文件路径:lib。所以,我认为一切都配置正确。

我将其添加spring.devtools.restart.additional-paths到现在build的目录中:lib ,如果我编译:lib:app则会重新加载但仍没有更改。如果我运行:lib:build,这会为创建一个新的 jar 文件:lib,我收到错误:Caused by: java.io.EOFException: Unexpected end of ZLIB input stream 请注意,在中:lib,我有带有 Spring 注释的类@Service

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.....Application]
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:178) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:398) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:283) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:344) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:115) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:745) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:565) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.0.2.jar:3.0.2]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[spring-boot-3.0.2.jar:3.0.2]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432) ~[spring-boot-3.0.2.jar:3.0.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-3.0.2.jar:3.0.2]
    at com.....DevApplication.main(DevApplication.kt:92) ~[development/:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-3.0.2.jar:3.0.2]
Caused by: java.io.EOFException: Unexpected end of ZLIB input stream
    at java.util.zip.ZipFile$ZipFileInflaterInputStream.fill(ZipFile.java:446) ~[?:?]
    at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:158) ~[?:?]
    at java.io.FilterInputStream.read(FilterInputStream.java:132) ~[?:?]
    at org.springframework.asm.ClassReader.readStream(ClassReader.java:322) ~[spring-core-6.0.4.jar:6.0.4]
    at org.springframework.asm.ClassReader.<init>(ClassReader.java:287) ~[spring-core-6.0.4.jar:6.0.4]
    at org.springframework.core.type.classreading.SimpleMetadataReader.getClassReader(SimpleMetadataReader.java:56) ~[spring-core-6.0.4.jar:6.0.4]
    at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:48) ~[spring-core-6.0.4.jar:6.0.4]
    at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103) ~[spring-core-6.0.4.jar:6.0.4]
    at org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.createMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:86) ~[spring-boot-3.0.2.jar:3.0.2]
    at org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.getMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:73) ~[spring-boot-3.0.2.jar:3.0.2]
    at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:81) ~[spring-core-6.0.4.jar:6.0.4]
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:187) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:297) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:243) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:196) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:164) ~[spring-context-6.0.4.jar:6.0.4]
    ... 16 more

Caused by: java.io.EOFException: Unexpected end of ZLIB input stream
5

不幸的是,由于存在多个活动部件,仅通过堆栈跟踪很难诊断问题。重启配置或类路径(例如,使用 jar 而不是类目录)可能存在问题。如果您可以提供一个完整但最小的样本来重现问题,我们可以重新打开问题并查看。

0

@wilkinsona 当然!

我有 2 个示例。它们都有 2 个子项目:app:lib

在第一个示例中,:lib只是一个普通的 kotlin 项目。在第二个示例中,:lib有一个带有 Spring Boot@Service注释的类。

我稍后会将这两个例子分成不同的评论。

3

谢谢。请将示例作为单独的存储库推送到 GitHub 或将其压缩并附加到此问题中来分享。

9

谢谢。该问题是否特定于 Kotlin,还是在使用 Java 时也会发生?如果在使用 Java 时发生,请在示例中使用 Java 而不是 Kotlin,因为这样可以更容易地诊断问题。

4

谢谢。该问题是否特定于 Kotlin,还是在使用 Java 时也会发生?如果在使用 Java 时发生,请在示例中使用 Java 而不是 Kotlin,因为这样可以更容易地诊断问题。

我马上用 java 创建一个分支。

2

谢谢。我知道现在发生了什么。

正如我所怀疑的,您的lib项目作为 jar 文件而不是目录位于应用程序的类路径上。DevTools 不支持监视 jar 文件的更改,因此更改不会被注意到。一般来说,对目录的需求不是问题,因为 DevTools 通常用于 IDE,其中类目录默认位于类路径上。在您的情况下,这是一个问题,因为您正在使用它bootRun来运行应用程序,并且项目依赖项将 lib jar 放在类路径上。

我认为避免此问题的最佳方法是在 IDE 中打开项目并直接运行其主要方法。如果您希望继续使用bootRun,则必须将 Gradle 配置为尽可能依赖于类而不是 jar。如下所示:

configurations {
    runtimeClasspath {
        attributes {
            attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class.java, LibraryElements.CLASSES))
        }
    }
}

您的示例对我有用,已删除app/src/main/resources/META-INF/spring-devtools.propertiesapp/build.gradle.kts更改为以下内容:

plugins {
  java
  id("org.springframework.boot") version "3.0.2"
  id("io.spring.dependency-management") version "1.1.0"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
  mavenCentral()
}

dependencies {
  implementation("org.springframework.boot:spring-boot-starter-web")
  developmentOnly("org.springframework.boot:spring-boot-devtools")
  implementation(project(":lib"))
}

configurations {
    runtimeClasspath {
        attributes {
            attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class.java, LibraryElements.CLASSES))
        }
    }
}

我没能重现这个问题:重启时出现故障,但更改仍然可用。我怀疑这是由于在更改过程中发生重启造成的。您可能需要配置文件系统监视程序以匹配您的计算机的性能。

1

感谢@wilkinsona 的检查并提出解决方案!

我不会依赖 IDE,所以我想找到一种无论工作环境如何都能始终有效的解决方案。

简而言之,我们这里有两个根本问题:

  1. 仅当一切准备就绪时才触发重新加载

您建议进行微调spring.devtools.restart.poll-intervalspring.devtools.restart.quiet-period老实说,我不喜欢这个,因为它不稳定。正如我提到的,我希望有一个始终有效的解决方案。

但是,如果我改用 configure 你会怎么想spring.devtools.restart.trigger-file?所以,我会想办法在一切准备就绪后才更新触发器文件。只有更新此文件后,DevTools 才会重新加载。

  1. DevTools 不支持重新加载 jar 文件

我将检查您提出的解决方案并稍后在此更新结果。

8

无论如何,您写道“DevTools 不支持监视 jar 文件的更改,因此更改不会被注意到”。那么,您能解释一下在 中定义 jar 文件的用途吗META-INF/spring-devtools.properties?我想我不明白这个文件的用途。

3

但是,如果我配置为使用 spring.devtools.restart.trigger-file,您觉得怎么样?

这对你来说听起来是个不错的选择。

那么,您能解释一下在 META-INF/spring-devtools.properties 中定义 jar 文件的目的吗?

有些库难以与 DevTools 使用的两个类加载器兼容。定义 jar 允许spring-boot-devtools.properties您控制哪个类加载器将加载其类,这可以修复第三方代码中的类加载问题。例如,将依赖项移至重新启动类加载器可能会防止由于不同的类加载器加载同一个类而导致的类转换异常。