Fix Maven-built native images with Docker Compose dependency

Previously, we tried to prevent spring-boot-docker-compose from
causing problems with AOT and native images by excluding it from
the AOT processing classpath. This allowed AOT processing to
succeed. We cannot apply the same exclusion to the native image
classpath so spring-boot-docker-compose was still included in the
native image. This results in a failure at runtime due to missing
reflection hints.

This commit reverts that changes that excluded
spring-boot-docker-compose from the AOT processing classpath. This
allows AOT processing to generate reflection hints but reintroduces
the failure caused by the connection details bean definitions using
an instance supplier callback. To overcome this problem we disable
DockerComposeLifecycleManager during AOT processing and in a native
image. This ensures that no attempt is made to call docker compose up
and no connection details beans are defined.

Fixes gh-35676
This commit is contained in:
Andy Wilkinson 2023-06-21 11:33:15 +01:00
parent 06604ef5ed
commit 660dbb9afd
6 changed files with 47 additions and 96 deletions

View File

@ -24,6 +24,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.AotDetector;
import org.springframework.boot.SpringApplicationShutdownHandlers;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.docker.compose.core.DockerCompose;
@ -92,6 +93,10 @@ class DockerComposeLifecycleManager {
}
void start() {
if (Boolean.getBoolean("spring.aot.processing") || AotDetector.useGeneratedArtifacts()) {
logger.trace("Docker Compose support disabled with AOT and native images");
return;
}
if (!this.properties.isEnabled()) {
logger.trace("Docker Compose support not enabled");
return;

View File

@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.aot.AotDetector;
import org.springframework.boot.SpringApplicationShutdownHandlers;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.docker.compose.core.DockerCompose;
@ -116,6 +117,30 @@ class DockerComposeLifecycleManagerTests {
then(this.dockerCompose).should(never()).hasDefinedServices();
}
@Test
void startWhenAotProcessingDoesNotStart() {
withSystemProperty("spring.aot.processing", "true", () -> {
EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener);
setUpRunningServices();
this.lifecycleManager.start();
assertThat(listener.getEvent()).isNull();
then(this.dockerCompose).should(never()).hasDefinedServices();
});
}
@Test
void startWhenUsingAotArtifactsDoesNotStart() {
withSystemProperty(AotDetector.AOT_ENABLED, "true", () -> {
EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener);
setUpRunningServices();
this.lifecycleManager.start();
assertThat(listener.getEvent()).isNull();
then(this.dockerCompose).should(never()).hasDefinedServices();
});
}
@Test
void startWhenComposeFileNotFoundThrowsException() {
DockerComposeLifecycleManager manager = new DockerComposeLifecycleManager(new File("."),
@ -362,6 +387,22 @@ class DockerComposeLifecycleManagerTests {
}
}
private void withSystemProperty(String key, String value, Runnable action) {
String previous = System.getProperty(key);
try {
System.setProperty(key, value);
action.run();
}
finally {
if (previous == null) {
System.clearProperty(key);
}
else {
System.setProperty(key, previous);
}
}
}
/**
* Testable {@link SpringApplicationShutdownHandlers}.
*/

View File

@ -176,15 +176,6 @@ class AotTests {
});
}
@TestTemplate
void whenAotWithDevelopmentOnlyExclusions(MavenBuild mavenBuild) {
mavenBuild.project("aot-development-only-exclusions").goals("package").execute((project) -> {
Path aotDirectory = project.toPath().resolve("target/spring-aot/main");
assertThat(collectRelativePaths(aotDirectory.resolve("sources")))
.contains(Path.of("org", "test", "SampleApplication__ApplicationContextInitializer.java"));
});
}
List<Path> collectRelativePaths(Path sourceDirectory) {
try (Stream<Path> pathStream = Files.walk(sourceDirectory)) {
return pathStream.filter(Files::isRegularFile)

View File

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>aot-development-only-exclusions</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>@project.version@</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>@jakarta-servlet.version@</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>@project.version@</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<version>@project.version@</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -1,32 +0,0 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.test;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@Configuration(proxyBeanMethods = false)
public class SampleApplication {
public static void main(String[] args) {
Assert.state(!ClassUtils.isPresent("org.springframework.boot.docker.compose.core.DockerCompose", null), "Should not have docker-compose");
SpringApplication.run(SampleApplication.class, args);
}
}

View File

@ -111,7 +111,7 @@ public class ProcessAotMojo extends AbstractAotMojo {
private URL[] getClassPath() throws Exception {
File[] directories = new File[] { this.classesDirectory, this.generatedClasses };
return getClassPath(directories, new ExcludeTestScopeArtifactFilter(), DOCKER_COMPOSE_EXCLUDE_FILTER);
return getClassPath(directories, new ExcludeTestScopeArtifactFilter());
}
private RunArguments resolveArguments() {