Add boot42-preview module: Spring Boot 4.2.0-M2 vs 4.1.1 forwarded headers, AMQP starters, buildpack cache, RestTemplate removal warnings
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Gth99spB1CzQmBFofXmU4Z
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
# boot42-preview
|
||||||
|
|
||||||
|
Companion module for the ankurm.com article *Spring Boot 4.2 Preview: AMQP 1.0, Buildpack Image Cache and What to Test Now*.
|
||||||
|
Everything runs against **4.2.0-M2** (a milestone, not GA) and, where a comparison matters, against **4.1.1** (GA).
|
||||||
|
|
||||||
|
| Path | What it is |
|
||||||
|
|---|---|
|
||||||
|
| `pom.xml` / `pom-41.xml` | the same project on 4.2.0-M2 and on 4.1.1 (a `${revision}` parent does not resolve from a repository, hence two files) |
|
||||||
|
| `src/test/.../ForwardedHeadersTest` | what the container believes under each forwarded-header strategy |
|
||||||
|
| `src/test/.../BindingOrderTest` | Set and Map iteration order from a properties file |
|
||||||
|
| `src/broken/UsesRestTemplate.java` | not built; compiled by the facts script to capture the deprecation warnings |
|
||||||
|
| `src/main/.../RestClientReplacement` | the replacement, compiled clean |
|
||||||
|
| `examples/build-image-cache-fragment.xml` | **not executed** (needs Docker and a registry) |
|
||||||
|
| `output/` | every transcript quoted in the article; `./scripts/run-all.sh` regenerates them |
|
||||||
|
|
||||||
|
There is deliberately no `docs/` folder: the explanations live in the article.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<!-- NOT EXECUTED: needs a Docker daemon and a registry. Element names come from the setters javap shows in
|
||||||
|
output/04-buildpack-cache-classes.txt (CacheInfo.setImage, ImageCacheInfo.setName). -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<image>
|
||||||
|
<buildCache>
|
||||||
|
<image>
|
||||||
|
<name>registry.example.com/team/demo-build-cache:latest</name>
|
||||||
|
</image>
|
||||||
|
</buildCache>
|
||||||
|
</image>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# 01-forwarded-headers (Spring Boot 4.1.1, JDK 25)
|
||||||
|
none/x-forwarded scheme=http secure=false host=localhost port=<random>
|
||||||
|
none/rfc7239 scheme=http secure=false host=localhost port=<random>
|
||||||
|
native/x-forwarded scheme=https secure=true host=shop.example.com port=443
|
||||||
|
native/rfc7239 scheme=http secure=false host=localhost port=<random>
|
||||||
|
framework/x-forwarded scheme=https secure=true host=shop.example.com port=443
|
||||||
|
framework/rfc7239 scheme=https secure=true host=shop.example.com port=443
|
||||||
|
framework+standard/x-forwarded scheme=https secure=true host=shop.example.com port=443
|
||||||
|
framework+standard/rfc7239 scheme=https secure=true host=shop.example.com port=443
|
||||||
|
header-format=forwarded -> started (property ignored)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# 01-forwarded-headers (Spring Boot 4.2.0-M2, JDK 25)
|
||||||
|
none/x-forwarded scheme=http secure=false host=localhost port=<random>
|
||||||
|
none/rfc7239 scheme=http secure=false host=localhost port=<random>
|
||||||
|
native/x-forwarded scheme=https secure=true host=shop.example.com port=443
|
||||||
|
native/rfc7239 scheme=http secure=false host=localhost port=<random>
|
||||||
|
framework/x-forwarded scheme=https secure=true host=shop.example.com port=443
|
||||||
|
framework/rfc7239 scheme=http secure=false host=localhost port=<random>
|
||||||
|
framework+standard/x-forwarded scheme=http secure=false host=localhost port=<random>
|
||||||
|
framework+standard/rfc7239 scheme=https secure=true host=shop.example.com port=443
|
||||||
|
header-format=forwarded -> IllegalArgumentException: No enum constant org.springframework.boot.webmvc.autoconfigure.WebMvcProperties.HeaderFormat.forwarded
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# 02-set-and-map-binding-order (Spring Boot 4.1.1, JDK 25)
|
||||||
|
file order : zulu, alpha, mike, bravo, yankee, charlie
|
||||||
|
Set class : java.util.LinkedHashSet
|
||||||
|
Set order : [zulu, alpha, mike, bravo, yankee, charlie]
|
||||||
|
Map class : java.util.LinkedHashMap
|
||||||
|
Map key order : [zulu, alpha, mike, bravo, yankee, charlie]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# 02-set-and-map-binding-order (Spring Boot 4.2.0-M2, JDK 25)
|
||||||
|
file order : zulu, alpha, mike, bravo, yankee, charlie
|
||||||
|
Set class : java.util.LinkedHashSet
|
||||||
|
Set order : [zulu, alpha, mike, bravo, yankee, charlie]
|
||||||
|
Map class : java.util.LinkedHashMap
|
||||||
|
Map key order : [zulu, alpha, mike, bravo, yankee, charlie]
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# 03-amqp-starters (POM descriptions and contents straight from Maven Central)
|
||||||
|
spring-boot-starter-amqp 4.1.1 HTTP 200 "Starter for using Spring AMQP and Rabbit MQ" -> spring-boot-amqp
|
||||||
|
spring-boot-starter-amqp 4.2.0-M2 HTTP 200 "Starter for generic AMQP 1.0 support" -> spring-boot-amqp
|
||||||
|
spring-boot-starter-rabbitmq 4.1.1 HTTP 404 (does not exist)
|
||||||
|
spring-boot-starter-rabbitmq 4.2.0-M2 HTTP 200 "Starter for using RabbitMQ messaging and streaming broker" -> spring-boot-rabbitmq
|
||||||
|
spring-boot-starter-amqp-rabbitmq 4.1.1 HTTP 404 (does not exist)
|
||||||
|
spring-boot-starter-amqp-rabbitmq 4.2.0-M2 HTTP 200 "Starter for using Spring AMQP with Rabbit MQ over AMQP 1.0 protocol" -> spring-boot-amqp-rabbitmq
|
||||||
|
|
||||||
|
# auto-configuration each module registers (AutoConfiguration.imports)
|
||||||
|
--- spring-boot-amqp 4.1.1
|
||||||
|
org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration
|
||||||
|
org.springframework.boot.amqp.autoconfigure.health.RabbitHealthContributorAutoConfiguration
|
||||||
|
org.springframework.boot.amqp.autoconfigure.metrics.RabbitMetricsAutoConfiguration
|
||||||
|
--- spring-boot-amqp 4.2.0-M2
|
||||||
|
org.springframework.boot.amqp.autoconfigure.AmqpAutoConfiguration
|
||||||
|
|
||||||
|
--- spring-boot-rabbitmq 4.2.0-M2
|
||||||
|
org.springframework.boot.rabbitmq.autoconfigure.RabbitAutoConfiguration
|
||||||
|
org.springframework.boot.rabbitmq.autoconfigure.health.RabbitHealthContributorAutoConfiguration
|
||||||
|
org.springframework.boot.rabbitmq.autoconfigure.metrics.RabbitMetricsAutoConfiguration
|
||||||
|
--- spring-boot-amqp-rabbitmq 4.2.0-M2
|
||||||
|
org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitAutoConfiguration
|
||||||
|
org.springframework.boot.amqp.rabbitmq.autoconfigure.health.AmqpRabbitHealthContributorAutoConfiguration
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# 04-buildpack-cache-classes (javap, spring-boot-buildpack-platform and spring-boot-maven-plugin)
|
||||||
|
--- buildpack-platform 4.1.1: classes matching Cache
|
||||||
|
Cache$Bind.class
|
||||||
|
Cache$Format.class
|
||||||
|
Cache$Volume.class
|
||||||
|
Cache.class
|
||||||
|
--- buildpack-platform 4.2.0-M2: classes matching Cache
|
||||||
|
Cache.class
|
||||||
|
ImageCache.class
|
||||||
|
LocalCache$Bind.class
|
||||||
|
LocalCache$Volume.class
|
||||||
|
LocalCache.class
|
||||||
|
--- javap Cache (4.2.0-M2)
|
||||||
|
Compiled from "Cache.java"
|
||||||
|
public interface Cache {
|
||||||
|
public default LocalCache$Volume getVolume();
|
||||||
|
public default LocalCache$Bind getBind();
|
||||||
|
public default ImageCache getImage();
|
||||||
|
public static LocalCache volume(java.lang.String);
|
||||||
|
public static LocalCache volume(VolumeName);
|
||||||
|
public static LocalCache bind(java.lang.String);
|
||||||
|
public static ImageCache image(java.lang.String);
|
||||||
|
}
|
||||||
|
--- javap org.springframework.boot.maven.CacheInfo (4.2.0-M2)
|
||||||
|
Compiled from "CacheInfo.java"
|
||||||
|
public class CacheInfo {
|
||||||
|
public CacheInfo();
|
||||||
|
public void setVolume(LocalCacheInfo$VolumeCacheInfo);
|
||||||
|
public void setBind(LocalCacheInfo$BindCacheInfo);
|
||||||
|
public void setImage(CacheInfo$ImageCacheInfo);
|
||||||
|
Cache asCache();
|
||||||
|
static CacheInfo fromVolume(LocalCacheInfo$VolumeCacheInfo);
|
||||||
|
static CacheInfo fromBind(LocalCacheInfo$BindCacheInfo);
|
||||||
|
static CacheInfo fromImage(CacheInfo$ImageCacheInfo);
|
||||||
|
}
|
||||||
|
--- javap org.springframework.boot.maven.LocalCacheInfo (4.2.0-M2)
|
||||||
|
Compiled from "LocalCacheInfo.java"
|
||||||
|
public class LocalCacheInfo {
|
||||||
|
public LocalCacheInfo();
|
||||||
|
LocalCacheInfo(LocalCache);
|
||||||
|
public void setVolume(LocalCacheInfo$VolumeCacheInfo);
|
||||||
|
public void setBind(LocalCacheInfo$BindCacheInfo);
|
||||||
|
LocalCache asCache();
|
||||||
|
static LocalCacheInfo fromVolume(LocalCacheInfo$VolumeCacheInfo);
|
||||||
|
static LocalCacheInfo fromBind(LocalCacheInfo$BindCacheInfo);
|
||||||
|
}
|
||||||
|
--- javap -p Image, fields mentioning cache/workspace (4.2.0-M2)
|
||||||
|
LocalCacheInfo buildWorkspace;
|
||||||
|
CacheInfo buildCache;
|
||||||
|
LocalCacheInfo launchCache;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 05-resttemplate-deprecations (javac -Xlint:deprecation on src/broken/UsesRestTemplate.java)
|
||||||
|
--- Spring Boot 4.1.1
|
||||||
|
--- Spring Boot 4.2.0-M2
|
||||||
|
UsesRestTemplate.java:8: warning: [removal] RestTemplate in org.springframework.web.client has been deprecated and marked for removal
|
||||||
|
RestTemplate viaBuilder(RestTemplateBuilder builder) {
|
||||||
|
^
|
||||||
|
UsesRestTemplate.java:8: warning: [removal] RestTemplateBuilder in org.springframework.boot.restclient has been deprecated and marked for removal
|
||||||
|
RestTemplate viaBuilder(RestTemplateBuilder builder) {
|
||||||
|
^
|
||||||
|
UsesRestTemplate.java:12: warning: [removal] TestRestTemplate in org.springframework.boot.resttestclient has been deprecated and marked for removal
|
||||||
|
TestRestTemplate viaTest(TestRestTemplate t) {
|
||||||
|
^
|
||||||
|
UsesRestTemplate.java:12: warning: [removal] TestRestTemplate in org.springframework.boot.resttestclient has been deprecated and marked for removal
|
||||||
|
TestRestTemplate viaTest(TestRestTemplate t) {
|
||||||
|
^
|
||||||
|
UsesRestTemplate.java:16: warning: [removal] RestTemplate in org.springframework.web.client has been deprecated and marked for removal
|
||||||
|
RestTemplate plain() {
|
||||||
|
^
|
||||||
|
UsesRestTemplate.java:17: warning: [removal] RestTemplate in org.springframework.web.client has been deprecated and marked for removal
|
||||||
|
return new RestTemplate();
|
||||||
|
^
|
||||||
|
6 warnings
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# 06-restclient-replacement (javac -Xlint:deprecation on the RestClient version, Spring Boot 4.2.0-M2)
|
||||||
|
javac finished; lines above this one are warnings (none means a clean compile)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>4.1.1</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.ankurm</groupId>
|
||||||
|
<artifactId>boot42-preview-on-41</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<name>boot42-preview</name>
|
||||||
|
<description>Spring Boot 4.2 milestone preview: what to test now, run against 4.2.0-M2 and 4.1.1</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>25</java.version>
|
||||||
|
<boot.label>4.1.1</boot.label>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webmvc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-restclient</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webmvc-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<boot.label>${boot.label}</boot.label>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>4.2.0-M2</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.ankurm</groupId>
|
||||||
|
<artifactId>boot42-preview</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<name>boot42-preview</name>
|
||||||
|
<description>Spring Boot 4.2 milestone preview: what to test now, run against 4.2.0-M2 and 4.1.1</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>25</java.version>
|
||||||
|
<boot.label>4.2.0-M2</boot.label>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webmvc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-restclient</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webmvc-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<boot.label>${boot.label}</boot.label>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
Executable
+78
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Facts read from jars and POMs on Maven Central: nothing here needs a running application.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
M="$HOME/.m2/repository/org/springframework/boot"
|
||||||
|
CEN=https://repo1.maven.org/maven2/org/springframework/boot
|
||||||
|
W=$(mktemp -d)
|
||||||
|
for v in 4.1.1 4.2.0-M2; do for a in spring-boot-maven-plugin spring-boot-buildpack-platform spring-boot-amqp spring-boot-amqp-rabbitmq spring-boot-rabbitmq; do
|
||||||
|
mvn -B -q dependency:get -Dartifact=org.springframework.boot:$a:$v >/dev/null 2>&1 || true
|
||||||
|
done; done
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# 03-amqp-starters (POM descriptions and contents straight from Maven Central)"
|
||||||
|
for a in spring-boot-starter-amqp spring-boot-starter-rabbitmq spring-boot-starter-amqp-rabbitmq; do
|
||||||
|
for v in 4.1.1 4.2.0-M2; do
|
||||||
|
for try in 1 2 3 4 5; do
|
||||||
|
code=$(curl -s -o "$W/p.xml" -w '%{http_code}' "$CEN/$a/$v/$a-$v.pom")
|
||||||
|
[ "$code" = 429 ] || break
|
||||||
|
sleep $((try * 3))
|
||||||
|
done
|
||||||
|
if [ "$code" = 200 ]; then
|
||||||
|
d=$(grep -m1 '<description>' "$W/p.xml" | sed 's/ *<[^>]*>//g')
|
||||||
|
dep=$(grep -E '<artifactId>spring-boot-(amqp|rabbitmq|amqp-rabbitmq)</artifactId>' "$W/p.xml" | sed 's/ *<[^>]*>//g' | paste -sd, -)
|
||||||
|
printf '%-34s %-10s HTTP %s "%s" -> %s\n' "$a" "$v" "$code" "$d" "$dep"
|
||||||
|
else
|
||||||
|
printf '%-34s %-10s HTTP %s (does not exist)\n' "$a" "$v" "$code"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo "# auto-configuration each module registers (AutoConfiguration.imports)"
|
||||||
|
for a in spring-boot-amqp spring-boot-rabbitmq spring-boot-amqp-rabbitmq; do
|
||||||
|
for v in 4.1.1 4.2.0-M2; do
|
||||||
|
j="$M/$a/$v/$a-$v.jar"
|
||||||
|
if [ -f "$j" ]; then
|
||||||
|
echo "--- $a $v"; unzip -p "$j" META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 2>/dev/null | sed 's/^/ /' || true; echo
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
} > output/03-amqp-starters.txt
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# 04-buildpack-cache-classes (javap, spring-boot-buildpack-platform and spring-boot-maven-plugin)"
|
||||||
|
for v in 4.1.1 4.2.0-M2; do
|
||||||
|
echo "--- buildpack-platform $v: classes matching Cache"
|
||||||
|
unzip -l "$M/spring-boot-buildpack-platform/$v/spring-boot-buildpack-platform-$v.jar" | awk '{print $4}' | grep -E 'build/.*Cache' | sed 's#.*/build/# #'
|
||||||
|
done
|
||||||
|
mkdir -p "$W/bp"
|
||||||
|
unzip -q -o "$M/spring-boot-buildpack-platform/4.2.0-M2/spring-boot-buildpack-platform-4.2.0-M2.jar" 'org/springframework/boot/buildpack/platform/build/*Cache*' -d "$W/bp"
|
||||||
|
unzip -q -o "$M/spring-boot-maven-plugin/4.2.0-M2/spring-boot-maven-plugin-4.2.0-M2.jar" 'org/springframework/boot/maven/*' -d "$W/bp"
|
||||||
|
echo "--- javap Cache (4.2.0-M2)"
|
||||||
|
javap -cp "$W/bp" org.springframework.boot.buildpack.platform.build.Cache 2>/dev/null | sed 's/org.springframework.boot.buildpack.platform.build.//g; s/org.springframework.boot.buildpack.platform.docker.type.//g'
|
||||||
|
for c in 'org.springframework.boot.maven.CacheInfo' 'org.springframework.boot.maven.LocalCacheInfo'; do
|
||||||
|
echo "--- javap $c (4.2.0-M2)"
|
||||||
|
javap -cp "$W/bp" "$c" 2>/dev/null | sed 's/org.springframework.boot.maven.//g; s/org.springframework.boot.buildpack.platform.build.//g'
|
||||||
|
done
|
||||||
|
echo "--- javap -p Image, fields mentioning cache/workspace (4.2.0-M2)"
|
||||||
|
javap -p -cp "$W/bp" org.springframework.boot.maven.Image 2>/dev/null | grep -E '(Cache|buildWorkspace)[a-zA-Z]* [a-zA-Z]+;$' | sed 's/org.springframework.boot.maven.//g'
|
||||||
|
} > output/04-buildpack-cache-classes.txt
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# 05-resttemplate-deprecations (javac -Xlint:deprecation on src/broken/UsesRestTemplate.java)"
|
||||||
|
for pom in pom-41.xml pom.xml; do
|
||||||
|
mvn -B -q -f $pom dependency:build-classpath -Dmdep.outputFile="$W/cp.txt" >/dev/null 2>&1
|
||||||
|
label=$([ $pom = pom.xml ] && echo 4.2.0-M2 || echo 4.1.1)
|
||||||
|
echo "--- Spring Boot $label"
|
||||||
|
mkdir -p "$W/out-$label"
|
||||||
|
javac -Xlint:deprecation -d "$W/out-$label" -cp "$(cat "$W/cp.txt")" src/broken/UsesRestTemplate.java 2>&1 | grep -v JAVA_TOOL | sed 's#^src/broken/##' || true
|
||||||
|
done
|
||||||
|
} > output/05-resttemplate-deprecations.txt
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# 06-restclient-replacement (javac -Xlint:deprecation on the RestClient version, Spring Boot 4.2.0-M2)"
|
||||||
|
mvn -B -q dependency:build-classpath -Dmdep.outputFile="$W/cp.txt" >/dev/null 2>&1
|
||||||
|
mkdir -p "$W/out-rc"
|
||||||
|
javac -Xlint:deprecation -Xlint:removal -d "$W/out-rc" -cp "$(cat "$W/cp.txt")" src/main/java/com/ankurm/boot42/RestClientReplacement.java 2>&1 | grep -v JAVA_TOOL | sed 's#^src/main/java/com/ankurm/boot42/##' || true
|
||||||
|
echo "javac finished; lines above this one are warnings (none means a clean compile)"
|
||||||
|
} > output/06-restclient-replacement.txt
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regenerates every file under output/. Needs JDK 25 and Maven 3.9 with network access to Maven Central.
|
||||||
|
# ./scripts/run-all.sh
|
||||||
|
# 01-02 come from the test suite (run on 4.2.0-M2, then on 4.1.1 via pom-41.xml, which writes the *-on-4.1.1 files).
|
||||||
|
# 03-06 come from jars, POMs and javac. Port numbers are random, so the transcripts print "<random>".
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
mvn -B test
|
||||||
|
mvn -B -f pom-41.xml test
|
||||||
|
./scripts/capture-facts.sh
|
||||||
|
ls -1 output
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import org.springframework.boot.restclient.RestTemplateBuilder;
|
||||||
|
import org.springframework.boot.resttestclient.TestRestTemplate;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
/** Not part of the build: compiled by scripts/capture-facts.sh so the compiler's own words are committed. */
|
||||||
|
class UsesRestTemplate {
|
||||||
|
|
||||||
|
RestTemplate viaBuilder(RestTemplateBuilder builder) {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
TestRestTemplate viaTest(TestRestTemplate t) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
RestTemplate plain() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.ankurm.boot42;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/** Minimal application. Every experiment in this module lives in src/test. */
|
||||||
|
@SpringBootApplication
|
||||||
|
@org.springframework.boot.context.properties.EnableConfigurationProperties(OrderProperties.class)
|
||||||
|
public class Boot42PreviewApplication {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.ankurm.boot42;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
/** A Set and a Map bound from a file where the keys are deliberately not alphabetical. */
|
||||||
|
@ConfigurationProperties("app")
|
||||||
|
public record OrderProperties(Set<String> regions, Map<String, Integer> limits) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ankurm.boot42;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
/** What the deprecated RestTemplateBuilder code turns into: an injected RestClient.Builder. */
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
class RestClientReplacement {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RestClient inventoryClient(RestClient.Builder builder) {
|
||||||
|
return builder.baseUrl("http://inventory.internal").build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ankurm.boot42;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/** Echoes what the servlet container believes about the request, after any forwarded-header handling. */
|
||||||
|
@RestController
|
||||||
|
class WhoAmIController {
|
||||||
|
|
||||||
|
@GetMapping("/whoami")
|
||||||
|
Map<String, Object> whoAmI(HttpServletRequest request) {
|
||||||
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
|
m.put("scheme", request.getScheme());
|
||||||
|
m.put("secure", request.isSecure());
|
||||||
|
m.put("serverName", request.getServerName());
|
||||||
|
m.put("serverPort", request.getServerPort());
|
||||||
|
m.put("remoteAddr", request.getRemoteAddr());
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
app.regions=zulu,alpha,mike,bravo,yankee,charlie
|
||||||
|
app.limits.zulu=1
|
||||||
|
app.limits.alpha=2
|
||||||
|
app.limits.mike=3
|
||||||
|
app.limits.bravo=4
|
||||||
|
app.limits.yankee=5
|
||||||
|
app.limits.charlie=6
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.ankurm.boot42;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
|
||||||
|
/** 4.2.0-M1: Sets and Maps bind consistently and keep the order of the configuration file. */
|
||||||
|
class BindingOrderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void recordsIterationOrder() {
|
||||||
|
Transcript t = new Transcript("02-set-and-map-binding-order");
|
||||||
|
try (ConfigurableApplicationContext ctx = BootRun.start("spring.config.name=order")) {
|
||||||
|
OrderProperties p = ctx.getBean(OrderProperties.class);
|
||||||
|
t.line("file order : zulu, alpha, mike, bravo, yankee, charlie");
|
||||||
|
t.line("Set class : " + p.regions().getClass().getName());
|
||||||
|
t.line("Set order : " + new ArrayList<>(p.regions()));
|
||||||
|
t.line("Map class : " + p.limits().getClass().getName());
|
||||||
|
t.line("Map key order : " + new ArrayList<>(p.limits().keySet()));
|
||||||
|
t.write();
|
||||||
|
assertThat(p.regions()).hasSize(6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.ankurm.boot42;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import org.springframework.boot.WebApplicationType;
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
/** Starts the real application with the given properties on a random port. */
|
||||||
|
final class BootRun {
|
||||||
|
|
||||||
|
private BootRun() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static ConfigurableApplicationContext start(String... props) {
|
||||||
|
return new SpringApplicationBuilder(Boot42PreviewApplication.class)
|
||||||
|
.web(WebApplicationType.SERVLET)
|
||||||
|
.properties(props)
|
||||||
|
.properties("server.port=0", "spring.main.banner-mode=off", "logging.level.root=WARN")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
static RestClient client(ConfigurableApplicationContext ctx) {
|
||||||
|
Environment env = ctx.getEnvironment();
|
||||||
|
return RestClient.create("http://localhost:" + env.getProperty("local.server.port"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<?, ?> whoAmI(ConfigurableApplicationContext ctx, Map<String, String> headers) {
|
||||||
|
return client(ctx).get().uri("/whoami").headers(h -> headers.forEach(h::add)).retrieve().body(Map.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.ankurm.boot42;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4.2.0-M1: "When server.forward-headers-strategy=FRAMEWORK is selected, X-Forwarded-* headers are used by
|
||||||
|
* default." Sends both header styles under each strategy and records what the container believes.
|
||||||
|
*/
|
||||||
|
class ForwardedHeadersTest {
|
||||||
|
|
||||||
|
private static final Map<String, String> X_FORWARDED = Map.of("X-Forwarded-Proto", "https",
|
||||||
|
"X-Forwarded-Host", "shop.example.com", "X-Forwarded-Port", "443");
|
||||||
|
private static final Map<String, String> RFC_7239 = Map.of("Forwarded", "proto=https;host=shop.example.com");
|
||||||
|
|
||||||
|
private static final Map<String, Map<String, String>> HEADERS = new LinkedHashMap<>();
|
||||||
|
static {
|
||||||
|
HEADERS.put("x-forwarded", X_FORWARDED);
|
||||||
|
HEADERS.put("rfc7239", RFC_7239);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void recordsBehaviourPerStrategy() {
|
||||||
|
Transcript t = new Transcript("01-forwarded-headers");
|
||||||
|
Map<String, Map<?, ?>> results = new LinkedHashMap<>();
|
||||||
|
for (String strategy : new String[] { "none", "native", "framework" }) {
|
||||||
|
for (var e : HEADERS.entrySet()) {
|
||||||
|
try (ConfigurableApplicationContext ctx = BootRun.start("server.forward-headers-strategy=" + strategy)) {
|
||||||
|
Map<?, ?> seen = BootRun.whoAmI(ctx, e.getValue());
|
||||||
|
String key = strategy + "/" + e.getKey();
|
||||||
|
results.put(key, seen);
|
||||||
|
t.line(String.format("%-22s scheme=%s secure=%s host=%s port=%s", key, seen.get("scheme"),
|
||||||
|
seen.get("secure"), seen.get("serverName"), seen.get("serverPort").toString().equals("443") ? "443" : "<random>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the new 4.2 knob: which header family the FRAMEWORK strategy reads
|
||||||
|
for (var e : HEADERS.entrySet()) {
|
||||||
|
try (ConfigurableApplicationContext ctx = BootRun.start("server.forward-headers-strategy=framework",
|
||||||
|
"spring.mvc.forwarded-headers.header-format=standard")) {
|
||||||
|
Map<?, ?> seen = BootRun.whoAmI(ctx, e.getValue());
|
||||||
|
t.line(String.format("%-22s scheme=%s secure=%s host=%s port=%s", "framework+standard/" + e.getKey(),
|
||||||
|
seen.get("scheme"), seen.get("secure"), seen.get("serverName"), seen.get("serverPort").toString().equals("443") ? "443" : "<random>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// my first guess at the value was "forwarded"; record what the binder says about it
|
||||||
|
try (ConfigurableApplicationContext ctx = BootRun.start("server.forward-headers-strategy=framework",
|
||||||
|
"spring.mvc.forwarded-headers.header-format=forwarded")) {
|
||||||
|
t.line("header-format=forwarded -> started (property ignored)");
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
Throwable root = ex;
|
||||||
|
while (root.getCause() != null) {
|
||||||
|
root = root.getCause();
|
||||||
|
}
|
||||||
|
t.line("header-format=forwarded -> " + root.getClass().getSimpleName() + ": " + root.getMessage());
|
||||||
|
}
|
||||||
|
t.write();
|
||||||
|
// a strategy of "none" must never trust either header
|
||||||
|
assertThat(results.get("none/x-forwarded").get("scheme")).isEqualTo("http");
|
||||||
|
assertThat(results.get("none/rfc7239").get("scheme")).isEqualTo("http");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.ankurm.boot42;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Collects lines and writes output/NN-name.txt. The test asserts the same values it writes. */
|
||||||
|
final class Transcript {
|
||||||
|
|
||||||
|
private final List<String> lines = new ArrayList<>();
|
||||||
|
private final Path file;
|
||||||
|
|
||||||
|
Transcript(String name) {
|
||||||
|
String v = System.getProperty("boot.label", "4.2.0-M2");
|
||||||
|
String suffix = v.equals("4.2.0-M2") ? "" : "-on-" + v;
|
||||||
|
this.file = Path.of("output", name + suffix + ".txt");
|
||||||
|
line("# " + name + " (Spring Boot " + v + ", JDK " + Runtime.version().feature() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
Transcript line(String s) {
|
||||||
|
lines.add(s);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write() {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(file.getParent());
|
||||||
|
Files.write(file, lines);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user