Skip to content

Commit 926a657

Browse files
Mistborn94Renette Ros
authored andcommitted
Support Spring Boot 4
Fixes #4345
1 parent 51204fc commit 926a657

36 files changed

Lines changed: 988 additions & 6 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ jobs:
320320
test-java-distribution: ${{ matrix.distribution }}
321321
command: ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true
322322
- name: Run tests for ${{ matrix.version }}:${{ matrix.distribution }}
323-
run: ./mvnw test -Delastic.jdkCompatibilityTest=true -Dtest_java_binary=${{ env.TEST_JAVA_BINARY }}
323+
run: ./mvnw test -Delastic.jdkCompatibilityTest=true -Dtest_java_binary=${{ env.TEST_JAVA_BINARY }} -Dtest_java_version=${{ matrix.version }}
324324
- name: Store test results
325325
if: success() || failure()
326326
uses: actions/upload-artifact@v6

CHANGELOG.next-release.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ This file contains all changes which are not released yet.
1515
<!--FIXES-END-->
1616
# Features and enhancements
1717
<!--ENHANCEMENTS-START-->
18-
18+
* Support Spring Boot 4
1919
<!--ENHANCEMENTS-END-->
2020
# Deprecations
2121
<!--DEPRECATIONS-START-->

apm-agent-plugins/apm-spring-resttemplate/apm-spring-restclient-test/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,19 @@
5454
<scope>test</scope>
5555
</dependency>
5656
</dependencies>
57+
58+
<build>
59+
<plugins>
60+
<plugin>
61+
<artifactId>maven-jar-plugin</artifactId>
62+
<executions>
63+
<execution>
64+
<goals>
65+
<goal>test-jar</goal>
66+
</goals>
67+
</execution>
68+
</executions>
69+
</plugin>
70+
</plugins>
71+
</build>
5772
</project>

apm-agent-plugins/apm-spring-resttemplate/apm-spring-restclient-test/src/test/java/co/elastic/apm/agent/restclient/SpringRestClientInstrumentationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ protected boolean isRedirectFollowingSupported() {
6666
* The code is compiled with java 17 but potentially run with java 11.
6767
* JUnit will inspect the test class, therefore it must not contain any references to java 17 code.
6868
*/
69-
private static class Java17Code {
69+
public static class Java17Code {
7070
public static void performGet(Object restClient, String path) {
7171
((RestClient) restClient).get().uri(path).retrieve().body(String.class);
7272
}

apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/src/main/java/co/elastic/apm/agent/resttemplate/SpringRestRequestHeaderSetter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public class SpringRestRequestHeaderSetter implements TextHeaderSetter<HttpReque
2727

2828
@Override
2929
public void setHeader(String headerName, String headerValue, HttpRequest request) {
30-
if (!request.getHeaders().containsKey(headerName)) {
30+
// In Spring Framework 7, containsKey no longer exists
31+
if (request.getHeaders().getFirst(headerName) == null) {
3132
// the org.springframework.http.HttpRequest has only be introduced in 3.1.0
3233
request.getHeaders().add(headerName, headerValue);
3334
}

apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-test/pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717

1818
<dependencies>
1919

20+
<dependency>
21+
<groupId>${project.groupId}</groupId>
22+
<artifactId>apm-httpclient-core</artifactId>
23+
<version>${project.version}</version>
24+
<type>test-jar</type>
25+
<scope>test</scope>
26+
</dependency>
2027
<dependency>
2128
<groupId>${project.groupId}</groupId>
2229
<artifactId>apm-spring-resttemplate-plugin</artifactId>
@@ -31,6 +38,13 @@
3138
<type>test-jar</type>
3239
<scope>test</scope>
3340
</dependency>
41+
<dependency>
42+
<groupId>${project.groupId}</groupId>
43+
<artifactId>apm-spring-restclient-test</artifactId>
44+
<version>${project.version}</version>
45+
<type>test-jar</type>
46+
<scope>test</scope>
47+
</dependency>
3448

3549
<dependency>
3650
<groupId>org.apache.ivy</groupId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.resttemplate;
20+
21+
import co.elastic.apm.agent.restclient.SpringRestClientInstrumentationTest;
22+
import co.elastic.apm.agent.testutils.TestClassWithDependencyRunner;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.condition.EnabledForJreRange;
25+
import org.junit.jupiter.api.condition.JRE;
26+
27+
import java.util.List;
28+
import java.util.stream.Collectors;
29+
import java.util.stream.Stream;
30+
31+
@EnabledForJreRange(min = JRE.JAVA_17, disabledReason = "Spring 6 and up require JDK 17")
32+
public class SpringRestClientVersionsIT {
33+
34+
35+
@Test
36+
void version6() throws Exception {
37+
testVersionImpl("6.1.0");
38+
}
39+
40+
@Test
41+
void version7() throws Exception {
42+
testVersionImpl("7.0.0");
43+
}
44+
45+
void testVersionImpl(String version) throws Exception {
46+
var springDependencies = Stream.of(
47+
"spring-webmvc",
48+
"spring-aop",
49+
"spring-beans",
50+
"spring-context",
51+
"spring-core",
52+
"spring-expression",
53+
"spring-web")
54+
.map(s -> String.format("org.springframework:%s:%s", s, version));
55+
56+
var additionalDependencies = Stream.of(
57+
"io.micrometer:micrometer-observation:1.10.2",
58+
"io.micrometer:micrometer-commons:1.10.2",
59+
"commons-logging:commons-logging:1.3.0",
60+
"org.apache.logging.log4j:log4j-api:2.25.3"
61+
);
62+
63+
List<String> dependencies = Stream.concat(springDependencies, additionalDependencies).collect(Collectors.toList());
64+
65+
new TestClassWithDependencyRunner(dependencies, SpringRestClientInstrumentationTest.class.getName(), SpringRestClientInstrumentationTest.Java17Code.class.getName()).run();
66+
}
67+
}

apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-test/src/test/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateVersionsIT.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ void testVersion6(String version) throws Exception {
9292
));
9393
}
9494

95+
@ParameterizedTest
96+
@ValueSource(strings = {
97+
"7.0.0"
98+
})
99+
@EnabledForJreRange(min = JRE.JAVA_17, disabledReason = "Spring 7 requires JDK 17")
100+
void testVersion7(String version) throws Exception {
101+
testVersionImpl(version, true, List.of(
102+
"commons-logging:commons-logging:1.3.0",
103+
"io.micrometer:micrometer-observation:1.10.2",
104+
"io.micrometer:micrometer-commons:1.10.2",
105+
"org.apache.logging.log4j:log4j-api:2.25.3"
106+
));
107+
}
108+
95109
void testVersionImpl(String version, boolean isSupported, List<String> additionalDependencies) throws Exception {
96110
List<String> dependencies = Stream.of(
97111
"spring-webmvc",
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<artifactId>apm-spring-webmvc</artifactId>
8+
<groupId>co.elastic.apm</groupId>
9+
<version>1.55.5-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>apm-spring-webmvc-spring7-test</artifactId>
13+
<name>${project.groupId}:${project.artifactId}</name>
14+
15+
<properties>
16+
<animal.sniffer.skip>true</animal.sniffer.skip>
17+
<apm-agent-parent.base.dir>${project.basedir}/../../..</apm-agent-parent.base.dir>
18+
</properties>
19+
20+
<dependencyManagement>
21+
<dependencies>
22+
<dependency>
23+
<!-- Import dependency management from Spring Boot -->
24+
<groupId>org.springframework.boot</groupId>
25+
<artifactId>spring-boot-dependencies</artifactId>
26+
<version>4.0.0</version> <!-- must be 4.X to correspond to spring 7 -->
27+
<type>pom</type>
28+
<scope>import</scope>
29+
</dependency>
30+
</dependencies>
31+
</dependencyManagement>
32+
33+
<dependencies>
34+
<dependency>
35+
<groupId>${project.groupId}</groupId>
36+
<artifactId>apm-spring-webmvc-plugin</artifactId>
37+
<version>${project.version}</version>
38+
</dependency>
39+
40+
<dependency>
41+
<groupId>jakarta.servlet</groupId>
42+
<artifactId>jakarta.servlet-api</artifactId>
43+
<scope>test</scope>
44+
</dependency>
45+
<dependency>
46+
<groupId>${project.groupId}</groupId>
47+
<artifactId>apm-spring-webmvc-spring5</artifactId>
48+
<type>test-jar</type>
49+
<version>${project.version}</version>
50+
<scope>test</scope>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.thymeleaf</groupId>
54+
<artifactId>thymeleaf-spring6</artifactId>
55+
<scope>test</scope>
56+
</dependency>
57+
</dependencies>
58+
59+
60+
61+
<profiles>
62+
<profile>
63+
<!-- Spring Boot 4 / Spring Framework 7 requires junit 6 which requires java 17 -->
64+
<id>testing-jdk-11</id>
65+
<activation>
66+
<property>
67+
<name>test_java_version</name>
68+
<value>11</value>
69+
</property>
70+
</activation>
71+
<properties>
72+
<skipTests>true</skipTests>
73+
</properties>
74+
</profile>
75+
</profiles>
76+
77+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.springwebmvc;
20+
21+
import jakarta.servlet.ServletException;
22+
import jakarta.servlet.http.HttpServlet;
23+
import jakarta.servlet.http.HttpServletRequest;
24+
import jakarta.servlet.http.HttpServletResponse;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.web.servlet.ModelAndView;
27+
import org.springframework.web.servlet.mvc.ServletWrappingController;
28+
29+
import java.io.IOException;
30+
import java.io.PrintWriter;
31+
import java.util.function.Function;
32+
33+
public class Spring7TransactionNameInstrumentationTest extends AbstractSpringTransactionNameInstrumentationTest {
34+
public static class TestServlet extends HttpServlet {
35+
@Override
36+
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
37+
resp.getWriter().print("TestServlet");
38+
}
39+
}
40+
41+
@Configuration
42+
public static class TestConfiguration extends AbstractTestConfiguration {
43+
44+
@Override
45+
protected Class<?> getTestServletClass() {
46+
return TestServlet.class;
47+
}
48+
49+
@Override
50+
protected void configureTestServletOnController(ServletWrappingController controller) {
51+
controller.setServletClass(TestServlet.class);
52+
}
53+
54+
@Override
55+
protected ServletWrappingController createMyCustomController(Function<PrintWriter, ModelAndView> handler) {
56+
return new MyCustomController(handler);
57+
}
58+
59+
@Override
60+
protected ServletWrappingController createAnonymousController(Function<PrintWriter, ModelAndView> handler) {
61+
return new ServletWrappingController() {
62+
@Override
63+
public ModelAndView handleRequest(HttpServletRequest request,
64+
HttpServletResponse response) throws Exception {
65+
return handler.apply(response.getWriter());
66+
}
67+
};
68+
}
69+
}
70+
71+
72+
private static class MyCustomController extends ServletWrappingController {
73+
74+
private final Function<PrintWriter, ModelAndView> handler;
75+
76+
public MyCustomController(Function<PrintWriter, ModelAndView> handler) {
77+
this.handler = handler;
78+
}
79+
80+
@Override
81+
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
82+
return handler.apply(response.getWriter());
83+
}
84+
}
85+
86+
}

0 commit comments

Comments
 (0)