BuildProperties 的自动装配失败 Spring Boot 2.1.5 & eclipse

Failed Autowired of BuildProperties Spring Boot 2.1.5 & eclipse

我正在使用几个 REST API 创建一个非常简单的应用程序,它目前工作正常,直到我尝试在我的健康检查 API 中使用 BuildProperties。启动我的应用程序时出现以下错误:

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-06-17 09:54:29.210 ERROR 10796 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Field buildProperties in com.controller.HealthCheck required a bean of type 'org.springframework.boot.info.BuildProperties' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)

The following candidates were found but could not be injected:
    - Bean method 'buildProperties' in 'ProjectInfoAutoConfiguration' not loaded because @ConditionalOnResource did not find resource '${spring.info.build.location:classpath:META-INF/build-info.properties}'


Action:

Consider revisiting the entries above or defining a bean of type 'org.springframework.boot.info.BuildProperties' in your configuration.

我去了构建文件,我也查看了构建创建的 jar 文件,我看到构建-info.properties 实际上在那里。在 jar 文件中,文件的路径是 "BOOT-INF\classes\META-INF\"。我还有其他 "Autowired" 个没有问题的元素。

我的代码失败的地方:

@RestController
public class HealthCheck {

    @Autowired
    Environment environment;

    @Autowired 
    BuildProperties buildProperties;


    @GetMapping("/health")
    public HealthCheckResponse healthCheck() {
        return getHealthCheckResponse();
    }

    private HealthCheckResponse getHealthCheckResponse(){
        HealthCheckResponse healthResponse = new HealthCheckResponse();
        String[] profiles = environment.getActiveProfiles();

        healthResponse.setServerTime(new Date());
        healthResponse.setVersion(buildProperties.getVersion());
        healthResponse.setEnvironment(profiles[0]);

        return healthResponse;
    }

我的 gradle 构建文件:

plugins {
    id 'org.asciidoctor.convert' version '1.5.3'
    id 'org.springframework.boot' version '2.1.5.RELEASE'
    id 'java'
}

apply plugin: 'io.spring.dependency-management'
apply plugin: 'eclipse'
apply plugin: 'java'

group = 'com'
version = '0.0.1'
sourceCompatibility = '12'

repositories {
    mavenCentral()
}

ext {
    set('snippetsDir', file("build/generated-snippets"))
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-jersey'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:2.1.1'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.restdocs:spring-restdocs-webtestclient'
    testImplementation 'org.springframework.security:spring-security-test'
}

test {
    outputs.dir snippetsDir
}

asciidoctor {
    inputs.dir snippetsDir
    dependsOn test
}

springBoot {
    buildInfo()
}

构建-info.properties:

#Properties
#Mon Jun 17 10:52:04 EDT 2019
build.version=0.0.1
build.group=com
build.name=app
build.artifact=app
build.time=2019-06-17T14\:52\:04.829909200Z

我建议在 JDK 8 下尝试 运行ning 并从命令行使用 运行 java -jar <your jar name> 只是为了确保您正确获得构建属性。

也许 Jdk 12 还不适合 spring-boot。我想你可能还有其他问题。许多 Java 框架并未 100% 证明它们可以与 JDK 12.

一起使用

我认为计划是从 Spring Boot 2.2 开始正式支持 Java 12

正如@Borislav Markov 建议的那样,我通过命令行尝试了 运行ning 它,无论我使用 JDK 12 还是 JDK 8,它似乎都能正常工作。我认为这个问题与我通过 IDE.

运行 应用程序使用的 eclipse 插件有关

我认为您的 IDE 对 "fat jar" 覆盖普通 jar 的事实感到困惑。 IDE 了解普通 jar 的类路径 + `build-info.properties.

生成的资源

我总是以不同的名字构建 jar,这样我就可以避免这种部分构建的问题。

https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/html/#packaging-executable-and-normal

To avoid the executable archive and the normal archive from being written to the same location, one or the other should be configured to use a different location. One way to do so is by configuring a classifier:

bootJar {
    classifier = 'boot'
}

Add/Modify在pom.xml结尾:

<build>
        <finalName>{NAME_YOUR_PROJECT}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>build-info</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

简而言之:这个问题是IDE相关的(我在Eclipse和Idea上检查过),这不影响running/debugging spring 通过 gradle 构建系统在启动脚本中启动应用程序。

此外,eclipse 的引导插件和 JDK 产生此问题的前提也不完全正确。

此问题的根源: 使用不同编译器编译并丢失的构建工件的不同位置 build-info.properties

解释:

当 gradle 执行构建时,它通常使用 JDK 编译器生成 Java 工件并将输出放入构建目录。

另一方面,当 eclipse 执行构建时,它会使用 Eclipse JDT 生成 arifacts 并将输出放入 bin 目录中。

请注意,混合使用这两者可能会导致意外行为。此 'feature' 已被 eclipse 团队分析并被拒绝(因无效而关闭)。更多信息 here

根据 gradle 任务 buildInfo 是 运行 的事实 gradle,这解释了 build-info.properties 文件存在于 [=72] 中的事实=] 的默认输出文件夹(默认情况下必须位于此处:build/resources/main/META-INF/)。

从@ROOTKILL的提问可见,他试图从BuildProperties中获取信息class。在幕后,当 Spring 检测到 class 路径上有 build-info.properties 文件时,除非明确声明,否则它会创建 BuildProperties bean。 有用的信息是 here.

请看一下这个方法:

@ConditionalOnResource(resources = "${spring.info.build.location:classpath:META-INF/build-info.properties}")
@ConditionalOnMissingBean
@Bean
public BuildProperties buildProperties() throws Exception {
    return new BuildProperties(
            loadFrom(this.properties.getBuild().getLocation(), "build"));
}

根据 IDE 使用不同输出目录的事实,缺少 build-info.properties 文件,这会产生显示错误 (Bean method 'buildProperties' in 'ProjectInfoAutoConfiguration' not loaded because @ConditionalOnResource did not find resource '${spring.info.build.location:classpath:META-INF/build-info.properties}')。 另一方面,这解释了为什么一切都可以使用 gradle.

运行

解法:

根据这些事实,解决方案很明确:eclipse 和 IntelliJ Idea IDE 都必须使用 gradle 的任务而不是 running/debugging 自己的任务。

  • 对于 Eclipse IDE:可以通过 gradle 任务启动应用程序(从 gradle 任务视图引导运行)。
  • 对于想法IDE:可以添加将IDE build/run 操作委托给gradle 的设置,这@user666 之前已经指出了。

由于此解决方案使用 gradle,因此将从 build/resources/main/META-INF/ 位置(gradle 的默认值)使用 build-info.properties 文件,当然它是可见的。因此,bean BuildProperties 将被创建并可用。

只是 运行 一个 mvn clean package 然后从 Eclipse/IntelliJ.

重新启动 spring 启动应用程序

我的 Spring 引导服务在 maven-spring-boot 插件中有一个 Maven build-info 部分,所以当我尝试 运行 时出现此错误 BuildProperties cannot be found不是来自 .jar 存档时的服务。因此,对于 运行 我的服务作为常规 Spring 引导 运行 配置,我必须添加这个条件 bean,现在一切正常,既作为 .jar 版本,也作为它 运行s 作为调试 non-release:

@ConditionalOnMissingBean
@Bean
public BuildProperties buildProperties() {
    Properties properties = new Properties();
    properties.put("group", "com.example");
    properties.put("artifact", "demo");
    properties.put("version", "not-jarred");
    return new BuildProperties(properties);
}

正如其他答案所表明的那样,这是由于 IDE 没有创建文件。对于 vscode,我通过定义一个任务从 Gradle 的 [= 复制 build-info.properties 文件来解决它32=] 目录到 vscode 的 bin/main/META-INF 中,这样构建就可以找到它。

通常我最近有 运行 一个 Gradle 构建,那个略显陈旧的构建版本-info.properties 足以让我在 [=43] 中调试 运行 =].

创建一个 .vscode/tasks.json 类似于:

{
    "version": "2.0.0",
    "tasks": [
        {
            // This task keeps vscode from failing when using BuildProperties
            "label": "Copy build-info from Gradle",
            "type": "shell",
            "command": "mkdir -p ./bin/main/META-INF/ && cp ./build/resources/main/META-INF/build-info.properties ./bin/main/META-INF/build-info.properties"
        }
    ]
}

然后将任务作为 preLaunchTask 添加到您的 .vscode/launch.json:

{
    "configurations": [
        {
            "type": "java",
            "name": "Spring Boot-Application<example>",
            "request": "launch",
            "cwd": "${workspaceFolder}",
            "console": "internalConsole",
            "mainClass": "com.example.Application",
            "projectName": "example",
            "args": "",
            "preLaunchTask": "Copy build-info from Gradle"
        }
    ]
}

我尝试了 ProjectInfoAutoConfiguration 解决方案,但认为最好改用 @ConditionalOnMissingBean (BuildProperties.class) @ConditionalOnResource (resources = "$ {spring.info.build.location: classpath: META-INF / build-info.properties}").

因为我可以控制 BuildProperties 的创建方式:

    @ConditionalOnMissingBean(BuildProperties.class)
    @Bean
    public BuildProperties buildProperties() throws IOException {
            Resource r = this.properties.getBuild().getLocation();
            if (r.exists())
                // build-info.properties exists in my jar
                return new BuildProperties(
                    loadFrom(r, "build")); // see ProjectInfoAutoConfiguration code
            else {
                // No, we running via IDE. So let's build a stub:
                Properties properties = new Properties();
                properties.put("group", "com.example");
                properties.put("artifact", "demo");
                properties.put("version", "not-jarred");
                return new BuildProperties(properties);
            }
    }

如果您使用的是 lombok,请确保排除 spring-boot-maven-plugin 的配置,例如,

    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <excludes>
                <exclude>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                </exclude>
            </excludes>
        </configuration>
        <executions>
            <execution>
                <id>build-info</id>
                <goals>
                    <goal>build-info</goal>
                </goals>
            </execution>
        </executions>
    </plugin>

正如 , the cause of issue is here 所正确提到的:

Both the Maven plugin and the Gradle plugin allow generating build information containing the coordinates, name, and version of the project. The plugins can also be configured to add additional properties through configuration. When such a file is present, Spring Boot auto-configures a BuildProperties bean.

因此您需要在 POM 中添加 for spring-boot-maven-plugin:

<executions>
    <execution>
        <goals>
            <goal>build-info</goal>
        </goals>
    </execution>
</executions> 

或添加 build.gradle:

springBoot {
    buildInfo()
}

作为替代方法,可以显式添加 Bean,格式比 显示的更短:

@Bean @ConditionalOnMissingBean(BuildProperties.class)
BuildProperties buildProperties() {
    return new BuildProperties(new Properties());
}

在配置文件中。

注意:如果你更新了POMbuild.gradle,但仍然出现错误,请尝试使用(例如Maven)生命周期命令 clean,然后是 install 和 运行 项目。

如果使用 Intelli 和 maven,这里是关于如何更改 运行ner 和使用 maven 运行ner 的屏幕截图。理想情况下,这是最好的建议,因为我们可以 运行 确切地了解 ide 中的 maven 运行s。

对于 Gradle 项目:将其添加到 build.gradle 文件

    springBoot {
        buildInfo {
            properties {
                name = PROJECT_NAME
                additional = [
                        'yourCustomInfo': 'someInfo'
                ]
            }
        }
    }

问题是 STS 和 Intellij 没有 运行 buildinfo 任务,同时 运行将应用程序作为 Spring 启动应用程序。由于大多数开发人员 运行 通过此机制而不是使用 gradle / maven 构建的应用程序需要有一个适用于两者的解决方案。我遵循了 Sergey K 的回答,它适用于基于 IDE 的 Spring 启动 运行。但是对于 gradle 运行 它失败了,因为 BuildProperties 是从配置文件而不是生成的构建-info.properties 文件自动装配的。

我能够通过使用以下组件并自动装配它来克服这个问题

@Component
public BuildValues{
   @Autowired(required = false)
   private buildProperties;
   
   public getBuildProperties(){
       if(buildProperties==null) {
          ResourceLoader resourceLoader = new DefaultResourceLoader();
          Resource r = resourceLoader.getResource("classpath:META-INF/build- 
        info.properties");
    if (r.exists()) {
        // build-info.properties exists in my jar
        Properties prop = PropertiesLoaderUtils.loadProperties(r);
        buildProperties =  new BuildProperties(prop);
    }else {
    // No, we running via IDE. So let's build a stub:
        Properties properties = new Properties();
        properties.put("buildTime", "2022-01-13T14:38:06.567Z");
        buildProperties = new BuildProperties(properties);
    }
    }
    return buildProperties;
   }

}