From e12cbfc4e6b81ef26f86c74b648b2371ad0cf300 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Wed, 1 Jul 2026 19:32:56 +0800 Subject: [PATCH 1/3] Use ResolvableType.resolve for bean classes Replace `getRawClass()` with `resolve()` when deriving configuration properties bean classes. This aligns class resolution with Spring's `ResolvableType` API and avoids relying on raw-type access during bean initialization and nested property binding. --- .../properties/bind/ConfigurationPropertiesBeanContext.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java b/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java index 4c91ea34..a45acdce 100644 --- a/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java +++ b/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java @@ -176,7 +176,7 @@ String getPrefix() { */ @Nonnull Class getBeanClass() { - return getBeanType().getRawClass(); + return getBeanType().resolve(); } void initializeBean(Object bean) { @@ -209,7 +209,7 @@ private void initBeanProperties() { } private void initBeanProperties(ResolvableType beanType, ConfigurationPropertyName prefixName, String nestedPath) { - Class beanClass = beanType.getRawClass(); + Class beanClass = beanType.resolve(); if (isCandidateClass(beanClass)) { Constructor bindConstructor = this.bindConstructor; if (bindConstructor == null) { From 30f609f96fb11121d9d818064bf1d2bb3b587cdc Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Wed, 1 Jul 2026 19:55:10 +0800 Subject: [PATCH 2/3] Update README with latest release versions Refresh the branch compatibility table in README.md to show the newest module versions: `main` from `0.2.27` to `0.2.28` and `1.x` from `0.1.27` to `0.1.28`. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d8e0e3a2..c8f10ef5 100644 --- a/README.md +++ b/README.md @@ -110,8 +110,8 @@ Choose the version that matches your Spring Boot generation: | Branch | Spring Boot Compatibility | Latest Version | |--------|---------------------------|----------------| -| `main` | 3.0.x – 3.5.x, 4.1.x | `0.2.27` | -| `1.x` | 2.0.x – 2.7.x | `0.1.27` | +| `main` | 3.0.x – 3.5.x, 4.1.x | `0.2.28` | +| `1.x` | 2.0.x – 2.7.x | `0.1.28` | ### Add Module Dependencies From fee05e088d03a8c4852a24f80bbb9b9a222361cb Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Wed, 1 Jul 2026 19:58:03 +0800 Subject: [PATCH 3/3] Harden candidate checks in bean context Add defensive null handling and stricter filtering in `ConfigurationPropertiesBeanContext`: `isCandidateProperty` now rejects null descriptors and properties without a read method, and `isCandidateClass` now rejects null classes. Tests were updated to cover these edge cases and verify expected behavior for primitives, wrappers, enums, JDK types, and valid configuration properties. --- .../ConfigurationPropertiesBeanContext.java | 11 +++++++- ...PropertiesBeanPropertyChangedListener.java | 5 ++-- ...onfigurationPropertiesBeanContextTest.java | 26 ++++++++++++++++++- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java b/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java index a45acdce..fe982be0 100644 --- a/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java +++ b/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java @@ -544,8 +544,14 @@ static String buildPropertyPath(String propertyName, @Nullable String nestedPath * @return {@code true} if the property is a binding candidate, {@code false} otherwise */ static boolean isCandidateProperty(PropertyDescriptor descriptor) { + if (descriptor == null) { + return false; + } Method readMethod = descriptor.getReadMethod(); - return readMethod == null || !Object.class.equals(readMethod.getDeclaringClass()); + if (readMethod == null) { + return false; + } + return !Object.class.equals(readMethod.getDeclaringClass()); } /** @@ -563,6 +569,9 @@ static boolean isCandidateProperty(PropertyDescriptor descriptor) { * @return {@code true} if the class is a binding candidate, {@code false} otherwise */ static boolean isCandidateClass(Class beanClass) { + if (beanClass == null) { + return false; + } if (isPrimitiveOrWrapper(beanClass) || beanClass.isEnum()) { return false; } diff --git a/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListener.java b/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListener.java index 82982aa4..f1bcd925 100644 --- a/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListener.java +++ b/microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListener.java @@ -50,8 +50,7 @@ * @see ConfigurationPropertiesBeanPropertyChangedEvent * @since 1.0.0 */ -public class EventPublishingConfigurationPropertiesBeanPropertyChangedListener implements BindListener, - ApplicationContextAware, InitializingBean, SmartInitializingSingleton { +public class EventPublishingConfigurationPropertiesBeanPropertyChangedListener implements BindListener, ApplicationContextAware, InitializingBean, SmartInitializingSingleton { private static final Logger logger = getLogger(EventPublishingConfigurationPropertiesBeanPropertyChangedListener.class); @@ -190,4 +189,4 @@ public void afterSingletonsInstantiated() { public boolean isBound() { return bound; } -} \ No newline at end of file +} diff --git a/microsphere-spring-boot-core/src/test/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContextTest.java b/microsphere-spring-boot-core/src/test/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContextTest.java index 75b8b1af..c5ebf669 100644 --- a/microsphere-spring-boot-core/src/test/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContextTest.java +++ b/microsphere-spring-boot-core/src/test/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContextTest.java @@ -31,8 +31,10 @@ import org.springframework.core.annotation.AnnotationAttributes; import java.beans.PropertyDescriptor; +import java.util.concurrent.TimeUnit; import static io.microsphere.spring.boot.context.properties.bind.ConfigurationPropertiesBeanContext.getInstance; +import static io.microsphere.spring.boot.context.properties.bind.ConfigurationPropertiesBeanContext.isCandidateClass; import static io.microsphere.spring.boot.context.properties.bind.ConfigurationPropertiesBeanContext.isCandidateProperty; import static io.microsphere.spring.core.annotation.AnnotationUtils.getAnnotationAttributes; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -144,11 +146,33 @@ void testGetInstance() { @Test void testIsCandidateProperty() { + assertFalse(isCandidateProperty(null)); PropertyDescriptor descriptor = getPropertyDescriptor(ConfigurationPropertiesBeanContextTest.class, "name"); - assertTrue(isCandidateProperty(descriptor)); + assertFalse(isCandidateProperty(descriptor)); + + descriptor = getPropertyDescriptor(ConfigurationPropertiesBeanContextTest.class, "not-found"); + assertFalse(isCandidateProperty(descriptor)); descriptor = getPropertyDescriptor(ConfigurationPropertiesBeanContextTest.class, "class"); assertFalse(isCandidateProperty(descriptor)); + + descriptor = getPropertyDescriptor(ServerProperties.class, "port"); + assertTrue(isCandidateProperty(descriptor)); + } + + @Test + void testIsCandidateClass() { + // null + assertFalse(isCandidateClass(null)); + // primitive type and wrapper type are not candidate class + assertFalse(isCandidateClass(int.class)); + assertFalse(isCandidateClass(Integer.class)); + // enumeration type + assertFalse(isCandidateClass(TimeUnit.class)); + // String is candidate class,but is under java.lang package. + assertFalse(isCandidateClass(String.class)); + // current class + assertTrue(isCandidateClass(getClass())); } public void setName(String name) {