Skip to content

Commit 8686b02

Browse files
committed
Upgraded to spring-boot 4.0.1 and fixed BeanMapperAutoConfig for that
1 parent 4fff6f2 commit 8686b02

File tree

3 files changed

+142
-8
lines changed

3 files changed

+142
-8
lines changed

CLAUDE.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
BeanMapper Spring Boot Starter provides Spring Boot autoconfiguration for the BeanMapper library (http://beanmapper.io). This starter automatically configures a BeanMapper instance, scans for custom converters, and integrates with Spring Data JPA, Spring Security, and Spring MVC when available on the classpath.
8+
9+
**Core Dependency**: beanmapper-spring (version 7.0.0)
10+
**Java Version**: 21
11+
**Spring Boot Version**: 4.0.1
12+
13+
## Build and Test Commands
14+
15+
```bash
16+
# Build the project
17+
mvn clean install
18+
19+
# Run tests
20+
mvn test
21+
22+
# Run tests with coverage (generates Jacoco report)
23+
mvn clean test
24+
25+
# Run a single test class
26+
mvn test -Dtest=BeanMapperAutoConfigTest
27+
28+
# Run a single test method
29+
mvn test -Dtest=BeanMapperAutoConfigTest#autoconfig_shouldCreateBeanMapper_ifNotExists
30+
31+
# Security vulnerability check
32+
mvn dependency-check:check
33+
34+
# Generate Javadoc
35+
mvn javadoc:javadoc
36+
```
37+
38+
## Release Process
39+
40+
The project uses Maven Central publishing with the `release` profile:
41+
42+
```bash
43+
mvn clean deploy -P release
44+
```
45+
46+
This profile activates:
47+
- Javadoc generation
48+
- Source attachment
49+
- GPG signing
50+
- Central publishing plugin
51+
52+
## Architecture
53+
54+
### Autoconfiguration Flow
55+
56+
The autoconfiguration is triggered by Spring Boot's autoconfiguration mechanism via `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`, which references `BeanMapperAutoConfig`.
57+
58+
**BeanMapperAutoConfig** (src/main/java/io/beanmapper/autoconfigure/BeanMapperAutoConfig.java:54) is the central configuration class that:
59+
60+
1. **Scans for package prefix**: Uses `ApplicationScanner` to find the `@SpringBootApplication` annotated class package, unless `beanmapper.package-prefix` property is set
61+
2. **Scans for custom components**: Finds and instantiates `BeanConverter`, `CollectionHandler`, and `LogicSecuredCheck` implementations within the package prefix
62+
3. **Conditionally configures integrations** based on classpath detection:
63+
- Spring Data JPA: Adds `IdToEntityBeanConverter` and `JpaAfterClearFlusher`
64+
- Hibernate: Configures `HibernateAwareBeanUnproxy`
65+
- Spring Security: Configures `SpringRoleSecuredCheck` for secured properties
66+
- Spring MVC: Adds `MergedFormMethodArgumentResolver` for merged form handling
67+
68+
### Key Components
69+
70+
**ApplicationScanner** (src/main/java/io/beanmapper/autoconfigure/ApplicationScanner.java): Utility class that scans the classpath for:
71+
- The application package (via `@SpringBootApplication` annotation)
72+
- Classes annotated with `@BeanMapToClass` or `@BeanMapFromClass`
73+
- Implementations of `BeanConverter`, `CollectionHandler`, and `LogicSecuredCheck`
74+
75+
**BeanMapperProperties** (src/main/java/io/beanmapper/autoconfigure/BeanMapperProperties.java): Configuration properties under the `beanmapper` prefix:
76+
- `package-prefix`: Root package for scanning (defaults to `@SpringBootApplication` package)
77+
- `use-hibernate-unproxy`: Enable Hibernate proxy unwrapping (default: true)
78+
- `apply-strict-mapping-convention`: Apply strict mapping rules (default: true)
79+
- `apply-secured-properties`: Enable secured property handling (default: true)
80+
- `strict-source-suffix`: Suffix for strict source classes (default: "Form")
81+
- `strict-target-suffix`: Suffix for strict target classes (default: "Result")
82+
- `diagnostics-level`: Diagnostics detail level (default: DISABLED)
83+
84+
**BeanMapperBuilderCustomizer** (src/main/java/io/beanmapper/autoconfigure/BeanMapperBuilderCustomizer.java): Functional interface allowing users to customize the `BeanMapperBuilder` before the `BeanMapper` bean is created. Users can define a bean of this type to add custom configuration.
85+
86+
### Conditional Component Instantiation
87+
88+
The autoconfiguration attempts to instantiate custom components in two ways (src/main/java/io/beanmapper/autoconfigure/BeanMapperAutoConfig.java:202):
89+
1. First attempts no-arg constructor
90+
2. If that fails, attempts constructor with `ApplicationContext` parameter
91+
92+
This allows custom converters and handlers to optionally receive the Spring ApplicationContext for dependency injection.
93+
94+
### MergedForm Support
95+
96+
The nested `MergedFormConfig` class (src/main/java/io/beanmapper/autoconfigure/BeanMapperAutoConfig.java:243) is conditionally activated when:
97+
- The application is a web application (`@ConditionalOnWebApplication`)
98+
- Spring Data's `EntityInformation` class is on the classpath
99+
100+
It registers a `MergedFormMethodArgumentResolver` that enables merging form data with existing JPA entities.
101+
102+
## Testing Approach
103+
104+
Tests use Spring's `AnnotationConfigWebApplicationContext` to simulate different Spring Boot configurations. Key patterns:
105+
106+
- **Custom ClassLoaders**: Tests like `autoconfig_shouldNotSetSecurityChecks_ifSpringSecurityIsMissingFromClassPath` use custom classloaders (e.g., `NoSpringSecurityClassLoader`, `NoSpringDataClassLoader`) to simulate missing dependencies
107+
- **Property-based testing**: Use `TestPropertyValues` to inject configuration properties
108+
- **Component scanning**: Test classes are in the same package to be discovered by the autoconfiguration scanner
109+
- **Reflection-based assertions**: Use `ReflectionTestUtils.getField()` to verify internal configuration state
110+
111+
## Configuration Properties
112+
113+
Users can customize the BeanMapper configuration through application properties:
114+
115+
```properties
116+
# Override the package to scan for converters/handlers
117+
beanmapper.package-prefix=com.example.myapp
118+
119+
# Disable Hibernate unproxy mechanism
120+
beanmapper.use-hibernate-unproxy=false
121+
122+
# Configure strict mapping convention
123+
beanmapper.apply-strict-mapping-convention=true
124+
beanmapper.strict-source-suffix=Form
125+
beanmapper.strict-target-suffix=Result
126+
127+
# Disable secured property handling
128+
beanmapper.apply-secured-properties=false
129+
```
130+
131+
Or via a `BeanMapperBuilderCustomizer` bean for programmatic configuration.

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
5656
<maven.compiler.release>21</maven.compiler.release>
5757

58-
<spring.boot.version>4.0.0</spring.boot.version>
58+
<spring.boot.version>4.0.1</spring.boot.version>
5959
<beanmapper.spring.version>7.0.0</beanmapper.spring.version>
6060

6161
<dependency-check-maven.version>12.1.9</dependency-check-maven.version>

src/main/java/io/beanmapper/autoconfigure/BeanMapperAutoConfig.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.context.annotation.Configuration;
3737
import org.springframework.context.annotation.EnableAspectJAutoProxy;
3838
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
39+
import tools.jackson.databind.json.JsonMapper;
3940
import org.springframework.util.ClassUtils;
4041
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
4142
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -44,7 +45,7 @@
4445
* In no BeanMapper bean is found, it will be created with sensible webapplication/spring-data-jpa mapping defaults.
4546
* It's possible to customize the BeanMapperBuilder by adding a bean of type {@link BeanMapperBuilderCustomizer}
4647
* to your configuration.
47-
* When a {@link JacksonJsonHttpMessageConverter} bean is found, a {@link MergedFormMethodArgumentResolver}
48+
* When a {@link JsonMapper} bean is found (Jackson is on the classpath), a {@link MergedFormMethodArgumentResolver}
4849
* will be added to the Spring MVC context.
4950
*/
5051
@Configuration
@@ -246,33 +247,35 @@ private void customize(BeanMapperBuilder builder) {
246247
static class MergedFormConfig implements WebMvcConfigurer {
247248

248249
private final Logger log = LoggerFactory.getLogger(MergedFormConfig.class);
249-
private final JacksonJsonHttpMessageConverter jacksonJsonHttpMessageConverter;
250+
private final JsonMapper jsonMapper;
250251
private final BeanMapper beanMapper;
251252
private final ApplicationContext applicationContext;
252253
private final jakarta.persistence.EntityManager entityManager;
253254

254-
public MergedFormConfig(@Autowired(required = false) final JacksonJsonHttpMessageConverter jacksonJsonHttpMessageConverter,
255+
public MergedFormConfig(@Autowired(required = false) final JsonMapper jsonMapper,
255256
final BeanMapper beanMapper, final ApplicationContext applicationContext, @Autowired(required = false) final jakarta.persistence.EntityManager entityManager) {
256-
this.jacksonJsonHttpMessageConverter = jacksonJsonHttpMessageConverter;
257+
this.jsonMapper = jsonMapper;
257258
this.beanMapper = beanMapper;
258259
this.applicationContext = applicationContext;
259260
this.entityManager = entityManager;
260261
}
261262

262263
/**
263-
* If a {@link JacksonJsonHttpMessageConverter} bean is found, adds a {@link MergedFormMethodArgumentResolver} to the Spring MVC context.
264+
* If a {@link JsonMapper} bean is found (Jackson is on the classpath), creates a {@link JacksonJsonHttpMessageConverter}
265+
* and adds a {@link MergedFormMethodArgumentResolver} to the Spring MVC context.
264266
*/
265267
@Override
266268
public void addArgumentResolvers(@Nonnull List<HandlerMethodArgumentResolver> argumentResolvers) {
267-
if (jacksonJsonHttpMessageConverter != null) {
269+
if (jsonMapper != null) {
268270
log.info("Adding MergedFormArgumentResolver to MVC application.");
271+
JacksonJsonHttpMessageConverter jacksonJsonHttpMessageConverter = new JacksonJsonHttpMessageConverter(jsonMapper);
269272
argumentResolvers.add(new MergedFormMethodArgumentResolver(
270273
singletonList(new StructuredJsonMessageConverter(jacksonJsonHttpMessageConverter)),
271274
beanMapper,
272275
applicationContext,
273276
entityManager));
274277
} else {
275-
log.warn("No MergedFormArgumentResolver added to MVC application because no MappingJackson2HttpMessageConverter bean found!");
278+
log.warn("No MergedFormArgumentResolver added to MVC application because no JsonMapper bean found!");
276279
}
277280
}
278281
}

0 commit comments

Comments
 (0)