Files
Claude c418589251 Add custom-starter module: a Boot 4 auto-configuration starter with conditions, metadata and runner tests
Two-jar starter (autoconfigure + starter), a demo app, and 17 captured transcripts
covering AutoConfiguration.imports, @ConditionalOn*, ordering, optional Jackson 3
integration, configuration metadata and the Boot 4 module split.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Uu7q8vPeREyT4218EJPzz1
2026-09-24 06:53:07 +00:00

137 lines
7.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Facts that a test cannot show, read from the built jars and from deliberately failing builds:
# 07 @ConditionalOnClass on a @Bean method versus on a nested class, on classpaths with and without Jackson
# 12 what is in the starter jar and the auto-configuration jar, and what the starter pulls in
# 13 the generated configuration metadata and auto-configuration metadata, as they sit in the jar
# 14 Boot 4's split of spring-boot-autoconfigure, and the Boot 3 import that no longer compiles
# 15 the annotation-processor version trap when the module has its own parent pom
# 17 which Boot 4.1.1 jar holds each class the starter imports
set -euo pipefail
cd "$(dirname "$0")/.."
mkdir -p output
M2=~/.m2/repository/org/springframework/boot
AC=masker-spring-boot-autoconfigure
JP() { "$@" 2>&1 | grep -v -E '^(Picked up|WARNING)'; }
ROOT=$(pwd)
clean() { sed "s#$ROOT#<custom-starter>#g; s#$HOME#~#g"; }
echo "== build the jars"
mvn -B -q package -DskipTests dependency:tree -Dscope=compile -DoutputFile=target/tree.txt >/dev/null 2>&1 || mvn -B package -DskipTests dependency:tree -Dscope=compile -DoutputFile=target/tree.txt
echo "== 07"
mvn -B -q -pl "$AC" test-compile dependency:build-classpath -Dmdep.outputFile=/tmp/masker-cp.txt -Dmdep.includeScope=test >/dev/null 2>&1
FULL=$(cat /tmp/masker-cp.txt)
NOJACKSON=$(tr ':' '\n' < /tmp/masker-cp.txt | grep -v jackson | paste -sd:)
{
echo "# @ConditionalOnClass(JsonMapper.class) on a @Bean method, versus on a nested class, with and without Jackson"
echo
echo "--- condition on the @Bean method, classpath without any Jackson jar ---"
JP java -cp "$AC/target/test-classes:$AC/target/classes:$NOJACKSON" com.ankurm.traps.MethodLevelMain method | grep -v -E '^[0-9:.]+ \[main\]'
echo
echo "--- condition on a nested class, classpath without any Jackson jar ---"
JP java -cp "$AC/target/test-classes:$AC/target/classes:$NOJACKSON" com.ankurm.traps.MethodLevelMain nested | grep -v -E '^[0-9:.]+ \[main\]'
echo
echo "--- condition on the @Bean method, classpath with Jackson ---"
JP java -cp "$AC/target/test-classes:$AC/target/classes:$FULL" com.ankurm.traps.MethodLevelMain method | grep -v -E '^[0-9:.]+ \[main\]'
} | clean > output/07-conditional-on-class-on-a-bean-method.txt
echo "== 12"
{
echo "# What is inside the two jars, and what the starter drags in"
echo
echo "--- masker-spring-boot-starter-1.0.0.jar ---"
unzip -l masker-spring-boot-starter/target/masker-spring-boot-starter-1.0.0.jar | awk 'NR>3 && $4!="" {print $4}' | grep -v '/$'
echo
echo "--- masker-spring-boot-autoconfigure-1.0.0.jar (META-INF and the classes) ---"
unzip -l $AC/target/$AC-1.0.0.jar | awk 'NR>3 && $4!="" {print $4}' | grep -v '/$' | grep -v -E 'pom\.(xml|properties)|MANIFEST'
echo
echo "--- META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ---"
unzip -p $AC/target/$AC-1.0.0.jar META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
echo
echo "--- dependency tree of masker-spring-boot-starter (Jackson is not in it) ---"
cat masker-spring-boot-starter/target/tree.txt
echo
echo "--- dependency tree of masker-spring-boot-autoconfigure (Jackson is there, marked optional in the pom) ---"
cat $AC/target/tree.txt
} | clean > output/12-jar-contents-and-dependency-trees.txt
echo "== 13"
{
echo "# Metadata the two annotation processors write into the auto-configuration jar"
echo
echo "--- META-INF/spring-configuration-metadata.json, written by spring-boot-configuration-processor ---"
echo
unzip -p $AC/target/$AC-1.0.0.jar META-INF/spring-configuration-metadata.json
echo
echo
echo "--- META-INF/additional-spring-configuration-metadata.json, hand-written and kept as is ---"
unzip -p $AC/target/$AC-1.0.0.jar META-INF/additional-spring-configuration-metadata.json
echo
echo
echo "--- META-INF/spring-autoconfigure-metadata.properties, written by spring-boot-autoconfigure-processor ---"
unzip -p $AC/target/$AC-1.0.0.jar META-INF/spring-autoconfigure-metadata.properties
} > output/13-configuration-metadata-in-the-jar.txt
echo "== 14"
BOOT_AC=$M2/spring-boot-autoconfigure/4.1.1/spring-boot-autoconfigure-4.1.1.jar
BOOT_JACKSON=$M2/spring-boot-jackson/4.1.1/spring-boot-jackson-4.1.1.jar
{
echo "# Boot 4.1.1: where the auto-configuration classes a starter refers to now live"
echo
echo "--- entries in spring-boot-autoconfigure's own AutoConfiguration.imports ---"
unzip -p "$BOOT_AC" META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | grep -c .
echo
echo "--- JacksonAutoConfiguration: which jar, which package ---"
echo "spring-boot-autoconfigure-4.1.1.jar, classes named JacksonAutoConfiguration: $(unzip -l "$BOOT_AC" | grep -c 'JacksonAutoConfiguration' || true)"
unzip -l "$BOOT_JACKSON" | awk '/JacksonAutoConfiguration.class/ {print "spring-boot-jackson-4.1.1.jar: " $4}'
echo
X=$(mktemp -d)
unzip -q -o "$BOOT_AC" 'org/springframework/boot/autoconfigure/AutoConfiguration.class' \
'org/springframework/boot/autoconfigure/AutoConfigurationExcludeFilter.class' -d "$X"
echo "--- what @AutoConfiguration is made of (javap -v, class-level annotations) ---"
JP javap -v -cp "$X" org.springframework.boot.autoconfigure.AutoConfiguration \
| sed -n '/^RuntimeVisibleAnnotations/,$p' | grep -E '^ (org\.springframework)|proxyBeanMethods' | sed 's/^ *//'
echo
echo "--- AutoConfigurationExcludeFilter, the filter @SpringBootApplication's scan applies: the classes it tests for (javap -c) ---"
JP javap -p -c -cp "$X" org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter \
| grep -E 'ldc|ImportCandidates.load' | sed -E 's/^ *[0-9]+: //; s/ +/ /g'
rm -rf "$X"
echo
echo "--- the Boot 3 import, compiled against the 4.1.1 jars ---"
mkdir -p /tmp/masker-broken
JP javac -d /tmp/masker-broken -cp "$BOOT_AC:$BOOT_JACKSON" $AC/src/broken/OldJacksonImport.java | clean || true
} > output/14-boot4-autoconfiguration-locations.txt
echo "== 17"
{
echo "# Which 4.1.1 jar holds each Boot class this starter and its tests import (searched in every Boot jar the build resolved)"
echo
for c in AutoConfiguration AutoConfigureAfter AutoConfigurations ConditionalOnClass ConditionalOnBean ConditionalOnMissingBean ConditionalOnProperty \
ConditionEvaluationReportLoggingListener ImportCandidates Configurations JacksonAutoConfiguration ApplicationContextRunner FilteredClassLoader; do
for j in "$M2"/spring-boot*/4.1.1/spring-boot*-4.1.1.jar; do
unzip -l "$j" | awk -v c="/$c.class" -v j="$(basename "$j")" 'index($4, c) && $4 !~ /\$/ {printf "%-46s %s\n", j, $4}'
done
done
} > output/17-where-the-classes-live.txt
echo "== 15"
TMP=$(mktemp -d)
cp -r pom.xml "$AC" "$TMP/"
mkdir -p "$TMP/masker-spring-boot-starter" "$TMP/masker-demo"
cp masker-spring-boot-starter/pom.xml "$TMP/masker-spring-boot-starter/"; cp masker-demo/pom.xml "$TMP/masker-demo/"
rm -rf "$TMP/$AC/target"
python3 - "$TMP/$AC/pom.xml" <<'PY'
import sys
p=sys.argv[1]
s=open(p).read()
s=s.replace("<artifactId>spring-boot-configuration-processor</artifactId>","<artifactId>spring-boot-configuration-processor</artifactId>\n <version>${project.parent.version}</version>")
open(p,'w').write(s)
PY
{
echo "# The processor path with <version>\${project.parent.version}</version>, when the module's parent is not Spring Boot's"
echo
(cd "$TMP" && mvn -B -U -pl $AC compile 2>&1 | grep -E 'ERROR.*(annotationProcessorPath|Could not find artifact)' | head -1 | sed -E 's/^\[ERROR\] //; s/ -> \[Help 1\]//')
} > output/15-annotation-processor-version-trap.txt
rm -rf "$TMP"
echo "done"