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
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# The auto-configuration with no properties and no user beans
|
||||
|
||||
Masker beans : [masker]
|
||||
Masker implementation : TailMasker
|
||||
defaults bound : enabled=true replacement='*' visibleTail=4
|
||||
mask("4111111111111111") = ************1111
|
||||
@@ -0,0 +1,9 @@
|
||||
# masker.* properties, and masker.enabled=false
|
||||
|
||||
|
||||
--- masker.replacement=# masker.visible-tail=2 ---
|
||||
mask("4111111111111111") = ##############11
|
||||
|
||||
--- masker.enabled=false ---
|
||||
Masker beans : []
|
||||
MaskerProperties beans : []
|
||||
@@ -0,0 +1,13 @@
|
||||
# @ConditionalOnMissingBean: backing off, and the order that decides it
|
||||
|
||||
|
||||
--- user configuration + auto-configuration (the way Boot loads them) ---
|
||||
Masker beans : [customMasker]
|
||||
getBean(Masker.class) : mask -> <redacted>
|
||||
|
||||
--- the same two classes registered as ordinary configuration, starter first ---
|
||||
Masker beans : [masker, customMasker]
|
||||
getBean(Masker.class) : NoUniqueBeanDefinitionException
|
||||
|
||||
--- the same two classes, user configuration first ---
|
||||
Masker beans : [customMasker]
|
||||
@@ -0,0 +1,11 @@
|
||||
# Jackson on the classpath: the module is registered and Sensitive is written masked
|
||||
|
||||
module beans : [maskerJacksonModule]
|
||||
JSON : {"card":"************1111"}
|
||||
|
||||
--- what Boot recorded for the Jackson auto-configuration ---
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration [matched]
|
||||
matched: @ConditionalOnClass found required class 'tools.jackson.databind.json.JsonMapper'
|
||||
matched: @ConditionalOnBean (types: com.ankurm.masker.Masker; SearchStrategy: all) found bean 'masker'
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration#maskerJacksonModule [matched]
|
||||
matched: @ConditionalOnMissingBean (types: com.ankurm.masker.MaskerJacksonModule; SearchStrategy: all) did not find any beans
|
||||
@@ -0,0 +1,9 @@
|
||||
# Jackson hidden with FilteredClassLoader: nothing fails, the integration just is not there
|
||||
|
||||
context failed : false
|
||||
Masker beans : [masker]
|
||||
module beans : []
|
||||
|
||||
--- what Boot recorded for the Jackson auto-configuration ---
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration [not matched]
|
||||
did not match: @ConditionalOnClass did not find required class 'tools.jackson.databind.json.JsonMapper'
|
||||
@@ -0,0 +1,17 @@
|
||||
# @ConditionalOnBean(JsonMapper) with and without an ordering constraint
|
||||
|
||||
|
||||
--- the order Boot evaluates them in ---
|
||||
com.ankurm.masker.MaskerAutoConfiguration
|
||||
com.ankurm.traps.EarlyModuleAutoConfiguration
|
||||
org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration
|
||||
com.ankurm.traps.OrderedModuleAutoConfiguration
|
||||
|
||||
--- the beans each one produced ---
|
||||
Marker bean created by OrderedModuleAutoConfiguration
|
||||
|
||||
--- what Boot recorded ---
|
||||
com.ankurm.traps.EarlyModuleAutoConfiguration [not matched]
|
||||
did not match: @ConditionalOnBean (types: tools.jackson.databind.json.JsonMapper; SearchStrategy: all) did not find any beans of type tools.jackson.databind.json.JsonMapper
|
||||
com.ankurm.traps.OrderedModuleAutoConfiguration [matched]
|
||||
matched: @ConditionalOnBean (types: tools.jackson.databind.json.JsonMapper; SearchStrategy: all) found bean 'jacksonJsonMapper'
|
||||
@@ -0,0 +1,12 @@
|
||||
# @ConditionalOnClass(JsonMapper.class) on a @Bean method, versus on a nested class, with and without Jackson
|
||||
|
||||
--- condition on the @Bean method, classpath without any Jackson jar ---
|
||||
context failed
|
||||
exception chain: BeanDefinitionStoreException -> IllegalStateException -> NoClassDefFoundError -> ClassNotFoundException
|
||||
root cause : java.lang.ClassNotFoundException: tools.jackson.databind.module.SimpleModule
|
||||
|
||||
--- condition on a nested class, classpath without any Jackson jar ---
|
||||
context started; nestedModule bean present: false
|
||||
|
||||
--- condition on the @Bean method, classpath with Jackson ---
|
||||
context started; methodLevelModule bean present: true
|
||||
@@ -0,0 +1,14 @@
|
||||
# An auto-configuration that is not listed in AutoConfiguration.imports (it is listed in spring.factories, which Boot 4 ignores for this)
|
||||
|
||||
|
||||
--- ApplicationContextRunner: the test names the class itself ---
|
||||
forgottenMarker bean : true
|
||||
|
||||
--- the starter author's application: @SpringBootApplication scanning the package that holds both classes ---
|
||||
forgottenMarker (@AutoConfiguration) : false
|
||||
forgottenPlainMarker (@Configuration) : true
|
||||
|
||||
--- a consumer application: its own package, the starter on the classpath ---
|
||||
forgottenMarker (@AutoConfiguration) : false
|
||||
forgottenPlainMarker (@Configuration) : false
|
||||
Masker beans : [masker]
|
||||
@@ -0,0 +1,6 @@
|
||||
# ImportCandidates: what Boot itself reads from META-INF/spring/...AutoConfiguration.imports
|
||||
|
||||
entries under com.ankurm.masker: 2
|
||||
com.ankurm.masker.MaskerAutoConfiguration
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration
|
||||
ForgottenAutoConfiguration listed: false
|
||||
@@ -0,0 +1,17 @@
|
||||
# An application that adds masker-spring-boot-starter and nothing else
|
||||
|
||||
Masker beans : [masker]
|
||||
MaskerProperties beans : [masker-com.ankurm.masker.MaskerProperties]
|
||||
Jackson module beans : [maskerJacksonModule]
|
||||
JSON of a Customer : {"name":"Asha","card":"************1111"}
|
||||
|
||||
--- what Boot recorded (the same lines --debug prints) ---
|
||||
com.ankurm.masker.MaskerAutoConfiguration [matched]
|
||||
matched: @ConditionalOnProperty (masker.enabled) matched
|
||||
com.ankurm.masker.MaskerAutoConfiguration#masker [matched]
|
||||
matched: @ConditionalOnMissingBean (types: com.ankurm.masker.Masker; SearchStrategy: all) did not find any beans
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration [matched]
|
||||
matched: @ConditionalOnClass found required class 'tools.jackson.databind.json.JsonMapper'
|
||||
matched: @ConditionalOnBean (types: com.ankurm.masker.Masker; SearchStrategy: all) found bean 'masker'
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration#maskerJacksonModule [matched]
|
||||
matched: @ConditionalOnMissingBean (types: com.ankurm.masker.MaskerJacksonModule; SearchStrategy: all) did not find any beans
|
||||
@@ -0,0 +1,16 @@
|
||||
# The same application configured with masker.* properties, then switched off
|
||||
|
||||
|
||||
--- --masker.replacement=# --masker.visible-tail=6 ---
|
||||
JSON of a Customer : {"name":"Asha","card":"##########111111"}
|
||||
|
||||
--- --masker.enabled=false ---
|
||||
Masker beans : []
|
||||
Jackson module beans : []
|
||||
|
||||
--- what Boot recorded ---
|
||||
com.ankurm.masker.MaskerAutoConfiguration [not matched]
|
||||
did not match: @ConditionalOnProperty (masker.enabled) found different value in property 'enabled'
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration [not matched]
|
||||
matched: @ConditionalOnClass found required class 'tools.jackson.databind.json.JsonMapper'
|
||||
did not match: @ConditionalOnBean (types: com.ankurm.masker.Masker; SearchStrategy: all) did not find any beans of type com.ankurm.masker.Masker
|
||||
@@ -0,0 +1,74 @@
|
||||
# What is inside the two jars, and what the starter drags in
|
||||
|
||||
--- masker-spring-boot-starter-1.0.0.jar ---
|
||||
META-INF/MANIFEST.MF
|
||||
META-INF/maven/com.ankurm.masker/masker-spring-boot-starter/pom.xml
|
||||
META-INF/maven/com.ankurm.masker/masker-spring-boot-starter/pom.properties
|
||||
|
||||
--- masker-spring-boot-autoconfigure-1.0.0.jar (META-INF and the classes) ---
|
||||
META-INF/spring-autoconfigure-metadata.properties
|
||||
META-INF/additional-spring-configuration-metadata.json
|
||||
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
|
||||
META-INF/spring-configuration-metadata.json
|
||||
com/ankurm/masker/MaskerJacksonAutoConfiguration.class
|
||||
com/ankurm/masker/Sensitive.class
|
||||
com/ankurm/masker/Masker.class
|
||||
com/ankurm/masker/MaskerAutoConfiguration.class
|
||||
com/ankurm/masker/TailMasker.class
|
||||
com/ankurm/masker/MaskerJacksonModule.class
|
||||
com/ankurm/masker/MaskerProperties.class
|
||||
com/ankurm/masker/MaskerJacksonModule$1.class
|
||||
|
||||
--- META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ---
|
||||
com.ankurm.masker.MaskerAutoConfiguration
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration
|
||||
|
||||
--- dependency tree of masker-spring-boot-starter (Jackson is not in it) ---
|
||||
com.ankurm.masker:masker-spring-boot-starter:jar:1.0.0
|
||||
+- org.springframework.boot:spring-boot-starter:jar:4.1.1:compile
|
||||
| +- org.springframework.boot:spring-boot-starter-logging:jar:4.1.1:compile
|
||||
| | +- ch.qos.logback:logback-classic:jar:1.5.38:compile
|
||||
| | | +- ch.qos.logback:logback-core:jar:1.5.38:compile
|
||||
| | | \- org.slf4j:slf4j-api:jar:2.0.18:compile
|
||||
| | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.25.5:compile
|
||||
| | | \- org.apache.logging.log4j:log4j-api:jar:2.25.5:compile
|
||||
| | \- org.slf4j:jul-to-slf4j:jar:2.0.18:compile
|
||||
| +- org.springframework.boot:spring-boot-autoconfigure:jar:4.1.1:compile
|
||||
| | \- org.springframework.boot:spring-boot:jar:4.1.1:compile
|
||||
| | +- org.springframework:spring-core:jar:7.0.9:compile
|
||||
| | | +- commons-logging:commons-logging:jar:1.3.6:compile
|
||||
| | | \- org.jspecify:jspecify:jar:1.0.1:compile
|
||||
| | \- org.springframework:spring-context:jar:7.0.9:compile
|
||||
| | +- org.springframework:spring-aop:jar:7.0.9:compile
|
||||
| | +- org.springframework:spring-beans:jar:7.0.9:compile
|
||||
| | +- org.springframework:spring-expression:jar:7.0.9:compile
|
||||
| | \- io.micrometer:micrometer-observation:jar:1.17.1:compile
|
||||
| | \- io.micrometer:micrometer-commons:jar:1.17.1:compile
|
||||
| +- jakarta.annotation:jakarta.annotation-api:jar:3.0.0:compile
|
||||
| \- org.yaml:snakeyaml:jar:2.6:compile
|
||||
\- com.ankurm.masker:masker-spring-boot-autoconfigure:jar:1.0.0:compile
|
||||
|
||||
--- dependency tree of masker-spring-boot-autoconfigure (Jackson is there, marked optional in the pom) ---
|
||||
com.ankurm.masker:masker-spring-boot-autoconfigure:jar:1.0.0
|
||||
+- org.springframework.boot:spring-boot-autoconfigure:jar:4.1.1:compile
|
||||
| \- org.springframework.boot:spring-boot:jar:4.1.1:compile
|
||||
| \- org.springframework:spring-context:jar:7.0.9:compile
|
||||
| +- org.springframework:spring-aop:jar:7.0.9:compile
|
||||
| +- org.springframework:spring-beans:jar:7.0.9:compile
|
||||
| +- org.springframework:spring-expression:jar:7.0.9:compile
|
||||
| \- io.micrometer:micrometer-observation:jar:1.17.1:compile
|
||||
| \- io.micrometer:micrometer-commons:jar:1.17.1:compile
|
||||
\- org.springframework.boot:spring-boot-starter-jackson:jar:4.1.1:compile (optional)
|
||||
+- org.springframework.boot:spring-boot-starter:jar:4.1.1:compile
|
||||
| +- org.springframework.boot:spring-boot-starter-logging:jar:4.1.1:compile
|
||||
| | +- ch.qos.logback:logback-classic:jar:1.5.38:compile
|
||||
| | | \- ch.qos.logback:logback-core:jar:1.5.38:compile
|
||||
| | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.25.5:compile
|
||||
| | | \- org.apache.logging.log4j:log4j-api:jar:2.25.5:compile
|
||||
| | \- org.slf4j:jul-to-slf4j:jar:2.0.18:compile
|
||||
| +- jakarta.annotation:jakarta.annotation-api:jar:3.0.0:compile
|
||||
| \- org.yaml:snakeyaml:jar:2.6:compile
|
||||
\- org.springframework.boot:spring-boot-jackson:jar:4.1.1:compile (optional)
|
||||
\- tools.jackson.core:jackson-databind:jar:3.1.5:compile (optional)
|
||||
+- com.fasterxml.jackson.core:jackson-annotations:jar:2.21:compile (optional)
|
||||
\- tools.jackson.core:jackson-core:jar:3.1.5:compile (optional)
|
||||
@@ -0,0 +1,80 @@
|
||||
# Metadata the two annotation processors write into the auto-configuration jar
|
||||
|
||||
--- META-INF/spring-configuration-metadata.json, written by spring-boot-configuration-processor ---
|
||||
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "masker",
|
||||
"type": "com.ankurm.masker.MaskerProperties",
|
||||
"sourceType": "com.ankurm.masker.MaskerProperties"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "masker.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "whether the starter configures anything at all",
|
||||
"sourceType": "com.ankurm.masker.MaskerProperties",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "masker.replacement",
|
||||
"type": "java.lang.String",
|
||||
"description": "the text that stands in for each hidden character",
|
||||
"sourceType": "com.ankurm.masker.MaskerProperties",
|
||||
"defaultValue": "*"
|
||||
},
|
||||
{
|
||||
"name": "masker.visible-tail",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "how many characters at the end of the value stay readable",
|
||||
"sourceType": "com.ankurm.masker.MaskerProperties",
|
||||
"defaultValue": 4
|
||||
}
|
||||
],
|
||||
"hints": [
|
||||
{
|
||||
"name": "masker.replacement",
|
||||
"values": [
|
||||
{
|
||||
"value": "*",
|
||||
"description": "Asterisk (the default)."
|
||||
},
|
||||
{
|
||||
"value": "#",
|
||||
"description": "Hash sign."
|
||||
},
|
||||
{
|
||||
"value": "•",
|
||||
"description": "Bullet."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ignored": {
|
||||
"properties": []
|
||||
}
|
||||
}
|
||||
|
||||
--- META-INF/additional-spring-configuration-metadata.json, hand-written and kept as is ---
|
||||
{
|
||||
"hints": [
|
||||
{
|
||||
"name": "masker.replacement",
|
||||
"values": [
|
||||
{ "value": "*", "description": "Asterisk (the default)." },
|
||||
{ "value": "#", "description": "Hash sign." },
|
||||
{ "value": "•", "description": "Bullet." }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
--- META-INF/spring-autoconfigure-metadata.properties, written by spring-boot-autoconfigure-processor ---
|
||||
com.ankurm.masker.MaskerAutoConfiguration=
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration=
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration.AutoConfigureAfter=com.ankurm.masker.MaskerAutoConfiguration
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration.ConditionalOnBean=com.ankurm.masker.Masker
|
||||
com.ankurm.masker.MaskerJacksonAutoConfiguration.ConditionalOnClass=tools.jackson.databind.json.JsonMapper
|
||||
@@ -0,0 +1,30 @@
|
||||
# Boot 4.1.1: where the auto-configuration classes a starter refers to now live
|
||||
|
||||
--- entries in spring-boot-autoconfigure's own AutoConfiguration.imports ---
|
||||
12
|
||||
|
||||
--- JacksonAutoConfiguration: which jar, which package ---
|
||||
spring-boot-autoconfigure-4.1.1.jar, classes named JacksonAutoConfiguration: 0
|
||||
spring-boot-jackson-4.1.1.jar: org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.class
|
||||
|
||||
--- what @AutoConfiguration is made of (javap -v, class-level annotations) ---
|
||||
org.springframework.context.annotation.Configuration(
|
||||
proxyBeanMethods=false
|
||||
org.springframework.boot.autoconfigure.AutoConfigureBefore
|
||||
org.springframework.boot.autoconfigure.AutoConfigureAfter
|
||||
|
||||
--- AutoConfigurationExcludeFilter, the filter @SpringBootApplication's scan applies: the classes it tests for (javap -c) ---
|
||||
ldc #26 // class org/springframework/context/annotation/Configuration
|
||||
ldc #40 // class org/springframework/boot/autoconfigure/AutoConfiguration
|
||||
ldc #40 // class org/springframework/boot/autoconfigure/AutoConfiguration
|
||||
invokestatic #65 // Method org/springframework/boot/context/annotation/ImportCandidates.load:(Ljava/lang/Class;Ljava/lang/ClassLoader;)Lorg/springframework/boot/context/annotation/ImportCandidates;
|
||||
|
||||
--- the Boot 3 import, compiled against the 4.1.1 jars ---
|
||||
masker-spring-boot-autoconfigure/src/broken/OldJacksonImport.java:4: error: package org.springframework.boot.autoconfigure.jackson does not exist
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
^
|
||||
masker-spring-boot-autoconfigure/src/broken/OldJacksonImport.java:7: error: cannot find symbol
|
||||
@AutoConfiguration(after = JacksonAutoConfiguration.class)
|
||||
^
|
||||
symbol: class JacksonAutoConfiguration
|
||||
2 errors
|
||||
@@ -0,0 +1,3 @@
|
||||
# The processor path with <version>${project.parent.version}</version>, when the module's parent is not Spring Boot's
|
||||
|
||||
Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.15.0:compile (default-compile) on project masker-spring-boot-autoconfigure: Resolution of annotationProcessorPath dependencies failed: The following artifacts could not be resolved: org.springframework.boot:spring-boot-configuration-processor:jar:1.0.0 (absent): Could not find artifact org.springframework.boot:spring-boot-configuration-processor:jar:1.0.0 in central (https://repo.maven.apache.org/maven2)
|
||||
@@ -0,0 +1,10 @@
|
||||
# @ConditionalOnProperty(prefix = "traps", name = "enabled") with no matchIfMissing
|
||||
|
||||
|
||||
--- no property set ---
|
||||
Marker beans : 0
|
||||
com.ankurm.traps.DefaultOffAutoConfiguration [not matched]
|
||||
did not match: @ConditionalOnProperty (traps.enabled) did not find property 'enabled'
|
||||
|
||||
--- traps.enabled=true ---
|
||||
Marker beans : 1
|
||||
@@ -0,0 +1,15 @@
|
||||
# Which 4.1.1 jar holds each Boot class this starter and its tests import (searched in every Boot jar the build resolved)
|
||||
|
||||
spring-boot-autoconfigure-4.1.1.jar org/springframework/boot/autoconfigure/AutoConfiguration.class
|
||||
spring-boot-autoconfigure-4.1.1.jar org/springframework/boot/autoconfigure/AutoConfigureAfter.class
|
||||
spring-boot-autoconfigure-4.1.1.jar org/springframework/boot/autoconfigure/AutoConfigurations.class
|
||||
spring-boot-autoconfigure-4.1.1.jar org/springframework/boot/autoconfigure/condition/ConditionalOnClass.class
|
||||
spring-boot-autoconfigure-4.1.1.jar org/springframework/boot/autoconfigure/condition/ConditionalOnBean.class
|
||||
spring-boot-autoconfigure-4.1.1.jar org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.class
|
||||
spring-boot-autoconfigure-4.1.1.jar org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.class
|
||||
spring-boot-autoconfigure-4.1.1.jar org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLoggingListener.class
|
||||
spring-boot-4.1.1.jar org/springframework/boot/context/annotation/ImportCandidates.class
|
||||
spring-boot-4.1.1.jar org/springframework/boot/context/annotation/Configurations.class
|
||||
spring-boot-jackson-4.1.1.jar org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.class
|
||||
spring-boot-test-4.1.1.jar org/springframework/boot/test/context/runner/ApplicationContextRunner.class
|
||||
spring-boot-test-4.1.1.jar org/springframework/boot/test/context/FilteredClassLoader.class
|
||||
Reference in New Issue
Block a user