Skip to content

Commit c8a2e10

Browse files
authored
Merge pull request #142 from mercyblitz/dev-1.x
Enhance configuration properties handling and update README
2 parents efc0834 + d67c934 commit c8a2e10

13 files changed

Lines changed: 396 additions & 215 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ Choose the version that matches your Spring Boot generation:
110110

111111
| Branch | Spring Boot Compatibility | Latest Version |
112112
|--------|---------------------------|----------------|
113-
| `main` | 3.0.x – 3.5.x, 4.1.x | `0.2.24` |
114-
| `1.x` | 2.0.x – 2.7.x | `0.1.24` |
113+
| `main` | 3.0.x – 3.5.x, 4.1.x | `0.2.26` |
114+
| `1.x` | 2.0.x – 2.7.x | `0.1.26` |
115115

116116
### Add Module Dependencies
117117

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.properties;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Annotation that can be used to indicate that configuration properties should be bound
27+
* using constructor arguments rather than by calling setters. Can be added at the type
28+
* level (if there is an unambiguous constructor) or on the actual constructor to use.
29+
* <p>
30+
* Note: To use constructor binding the class must be enabled using
31+
* {@link EnableConfigurationProperties @EnableConfigurationProperties} or configuration
32+
* property scanning. Constructor binding cannot be used with beans that are created by
33+
* the regular Spring mechanisms (e.g.
34+
* {@link org.springframework.stereotype.Component @Component} beans, beans created via
35+
* {@link org.springframework.context.annotation.Bean @Bean} methods or beans loaded using
36+
* {@link org.springframework.context.annotation.Import @Import}).
37+
*
38+
* @author Phillip Webb
39+
* @since 2.2.0
40+
* @see ConfigurationProperties
41+
*/
42+
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR })
43+
@Retention(RetentionPolicy.RUNTIME)
44+
@Documented
45+
public @interface ConstructorBinding {
46+
47+
}

microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java

Lines changed: 227 additions & 83 deletions
Large diffs are not rendered by default.

microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanProperty.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ public Object getValue() {
110110
}
111111

112112
public String getName() {
113+
if (this.name == null) {
114+
return resolveName();
115+
}
116+
return this.name;
117+
}
118+
119+
String resolveName() {
113120
Field field = this.field;
114121
if (field != null) {
115122
return field.getName();
@@ -142,6 +149,6 @@ public ResolvableType getType() {
142149

143150
@Override
144151
public String toString() {
145-
return format("{} {}.{} = {}", getType(), getDeclaringClassType(), getName(), getValue());
152+
return format("{} {}.{} = {}", getType(), getDeclaringClassType(), resolveName(), getValue());
146153
}
147154
}

microsphere-spring-boot-core/src/main/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListener.java

Lines changed: 26 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919
import io.microsphere.annotation.Nullable;
2020
import io.microsphere.logging.Logger;
2121
import org.springframework.beans.BeansException;
22+
import org.springframework.beans.factory.InitializingBean;
2223
import org.springframework.beans.factory.SmartInitializingSingleton;
23-
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
24-
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2524
import org.springframework.boot.context.properties.ConfigurationProperties;
2625
import org.springframework.boot.context.properties.bind.BindContext;
2726
import org.springframework.boot.context.properties.bind.Bindable;
@@ -30,21 +29,16 @@
3029
import org.springframework.context.ApplicationContext;
3130
import org.springframework.context.ApplicationContextAware;
3231
import org.springframework.context.ConfigurableApplicationContext;
33-
import org.springframework.core.ResolvableType;
34-
import org.springframework.core.annotation.AnnotationAttributes;
3532

3633
import java.util.Map;
3734
import java.util.function.Supplier;
3835

39-
import static io.microsphere.collection.MapUtils.newHashMap;
4036
import static io.microsphere.logging.LoggerFactory.getLogger;
37+
import static io.microsphere.spring.boot.context.properties.bind.ConfigurationPropertiesBeanContext.buildConfigurationPropertiesBeanContexts;
4138
import static io.microsphere.spring.boot.context.properties.bind.util.BindUtils.isBoundProperty;
4239
import static io.microsphere.spring.boot.context.properties.bind.util.BindUtils.isConfigurationPropertiesBean;
4340
import static io.microsphere.spring.boot.context.properties.source.util.ConfigurationPropertyUtils.getPrefix;
44-
import static io.microsphere.spring.boot.context.properties.util.ConfigurationPropertiesUtils.CONFIGURATION_PROPERTIES_CLASS;
45-
import static io.microsphere.spring.boot.context.properties.util.ConfigurationPropertiesUtils.findConfigurationProperties;
46-
import static io.microsphere.spring.core.annotation.AnnotationUtils.getAnnotationAttributes;
47-
import static org.springframework.util.Assert.isInstanceOf;
41+
import static io.microsphere.spring.context.ApplicationContextUtils.asConfigurableApplicationContext;
4842

4943
/**
5044
* A {@link BindListener} implementation of {@link ConfigurationProperties @ConfigurationProperties} Bean to publish
@@ -56,12 +50,10 @@
5650
* @see ConfigurationPropertiesBeanPropertyChangedEvent
5751
* @since 1.0.0
5852
*/
59-
public class EventPublishingConfigurationPropertiesBeanPropertyChangedListener implements BindListener, BeanFactoryPostProcessor, ApplicationContextAware, SmartInitializingSingleton {
53+
public class EventPublishingConfigurationPropertiesBeanPropertyChangedListener implements BindListener, ApplicationContextAware, InitializingBean, SmartInitializingSingleton {
6054

6155
private static final Logger logger = getLogger(EventPublishingConfigurationPropertiesBeanPropertyChangedListener.class);
6256

63-
private static final Class<ConfigurableApplicationContext> CONFIGURABLE_APPLICATION_CONTEXT_CLASS = ConfigurableApplicationContext.class;
64-
6557
private Map<String, ConfigurationPropertiesBeanContext> beanContexts;
6658

6759
private ConfigurableApplicationContext context;
@@ -95,16 +87,14 @@ public <T> void onStart(ConfigurationPropertyName name, Bindable<T> target, Bind
9587
*/
9688
@Override
9789
public void onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
98-
setConfigurationPropertiesBeanProperty(name, target, context, result);
90+
if (isBound()) {
91+
setConfigurationPropertiesBeanProperty(name, target, context, result);
92+
}
9993
}
10094

10195
void initConfigurationPropertiesBeanContext(ConfigurationPropertyName name, Bindable<?> target, BindContext context) {
10296
ConfigurationPropertiesBeanContext configurationPropertiesBeanContext = getConfigurationPropertiesBeanContext(name, target, context);
10397
if (configurationPropertiesBeanContext == null) {
104-
if (logger.isWarnEnabled()) {
105-
logger.warn("No ConfigurationPropertiesBeanContext was found[name : '{}' , target : {} , depth : {}]",
106-
name, target, context.getDepth());
107-
}
10898
return;
10999
}
110100
if (isConfigurationPropertiesBean(context)) {
@@ -116,45 +106,27 @@ void initConfigurationPropertiesBeanContext(ConfigurationPropertyName name, Bind
116106
name, target, context.getDepth());
117107
}
118108
} else {
119-
configurationPropertiesBeanContext.initialize(bean);
109+
configurationPropertiesBeanContext.setBean(bean);
120110
if (logger.isTraceEnabled()) {
121111
logger.trace("The ConfigurationPropertiesBean binding is finished[name : '{}' , target : {} , depth : {} , bean : '{}']",
122112
name, target, context.getDepth(), bean);
123113
}
124114
}
125-
} else {
126-
configurationPropertiesBeanContext.initProperty(name, target);
127115
}
128116
}
129117

130118
@Nullable
131119
private ConfigurationPropertiesBeanContext getConfigurationPropertiesBeanContext(ConfigurationPropertyName name,
132120
Bindable<?> target, BindContext context) {
133121
String prefix = getPrefix(name, context);
134-
return beanContexts.computeIfAbsent(prefix, p -> newConfigurationPropertiesBeanContext(target, p));
135-
}
136-
137-
ConfigurationPropertiesBeanContext newConfigurationPropertiesBeanContext(Bindable<?> target, String prefix) {
138-
ConfigurationProperties annotation = findConfigurationProperties(target);
139-
if (annotation == null) {
140-
if (logger.isWarnEnabled()) {
141-
logger.warn("The ConfigurationPropertiesBeanContext is not created caused by the missing @ConfigurationProperties annotation, target : {}",
142-
target);
143-
}
144-
return null;
145-
}
146-
AnnotationAttributes annotationAttributes = getAnnotationAttributes(annotation);
147-
String actualPrefix = annotationAttributes.getString("prefix");
148-
if (!prefix.equalsIgnoreCase(actualPrefix)) {
122+
ConfigurationPropertiesBeanContext configurationPropertiesBeanContext = this.beanContexts.get(prefix);
123+
if (configurationPropertiesBeanContext == null) {
149124
if (logger.isWarnEnabled()) {
150-
logger.warn("The ConfigurationPropertiesBeanContext is not created caused by the mismatched prefix[expected : '{}' , actual : '{}'], target : {}",
151-
prefix, actualPrefix, target);
125+
logger.warn("No ConfigurationPropertiesBeanContext was found[name : '{}' , target : {} , depth : {}]",
126+
name, target, context.getDepth());
152127
}
153-
return null;
154128
}
155-
156-
ResolvableType beanType = target.getType();
157-
return new ConfigurationPropertiesBeanContext(beanType, annotationAttributes, prefix, this.context);
129+
return configurationPropertiesBeanContext;
158130
}
159131

160132
/**
@@ -170,88 +142,48 @@ void setConfigurationPropertiesBeanProperty(ConfigurationPropertyName name, Bind
170142
if (isBoundProperty(context)) {
171143
ConfigurationProperty property = context.getConfigurationProperty();
172144
ConfigurationPropertiesBeanContext configurationPropertiesBeanContext = getConfigurationPropertiesBeanContext(name, target, context);
173-
configurationPropertiesBeanContext.setProperty(property, result, isBound());
145+
if (configurationPropertiesBeanContext == null) {
146+
return;
147+
}
148+
configurationPropertiesBeanContext.setProperty(property, result);
174149
if (logger.isTraceEnabled()) {
175150
logger.trace("binding Bean property is finished , configuration property : '{}' , type : '{}' , depth : {} , result : '{}'", property, target.getType(), context.getDepth(), result);
176151
}
177152
}
178153
}
179154

180-
/**
181-
* Post-processes the bean factory to initialize the internal map of
182-
* {@link ConfigurationPropertiesBeanContext} instances for all
183-
* {@link ConfigurationProperties @ConfigurationProperties} beans.
184-
*
185-
* <h3>Example Usage</h3>
186-
* <pre>{@code
187-
* EventPublishingConfigurationPropertiesBeanPropertyChangedListener listener =
188-
* new EventPublishingConfigurationPropertiesBeanPropertyChangedListener();
189-
* listener.postProcessBeanFactory(beanFactory);
190-
* }</pre>
191-
*
192-
* @param beanFactory the bean factory to post-process
193-
* @throws BeansException if an error occurs
194-
*/
195-
@Override
196-
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
197-
initConfigurationPropertiesBeanContexts(beanFactory);
198-
}
199-
200-
private void initConfigurationPropertiesBeanContexts(ConfigurableListableBeanFactory beanFactory) {
201-
String[] beanNames = beanFactory.getBeanNamesForAnnotation(CONFIGURATION_PROPERTIES_CLASS);
202-
int beanCount = beanNames.length;
203-
this.beanContexts = newHashMap(beanCount);
204-
}
205-
206155
/**
207156
* Sets the {@link ApplicationContext}, which must be a {@link ConfigurableApplicationContext},
208157
* for publishing property change events.
209158
*
210-
* <h3>Example Usage</h3>
211-
* <pre>{@code
212-
* EventPublishingConfigurationPropertiesBeanPropertyChangedListener listener =
213-
* new EventPublishingConfigurationPropertiesBeanPropertyChangedListener();
214-
* listener.setApplicationContext(applicationContext);
215-
* }</pre>
216-
*
217159
* @param context the application context, must be a {@link ConfigurableApplicationContext}
218160
* @throws BeansException if the context is not a {@link ConfigurableApplicationContext}
219161
*/
220162
@Override
221163
public void setApplicationContext(ApplicationContext context) throws BeansException {
222-
Class<ConfigurableApplicationContext> expectedType = CONFIGURABLE_APPLICATION_CONTEXT_CLASS;
223-
isInstanceOf(expectedType, context, "The 'context' argument is not an instance of " + expectedType.getName());
224-
this.context = expectedType.cast(context);
164+
this.context = asConfigurableApplicationContext(context);
165+
}
166+
167+
@Override
168+
public void afterPropertiesSet() {
169+
this.beanContexts = buildConfigurationPropertiesBeanContexts(this.context);
225170
}
226171

227172
/**
228173
* Called after all singleton beans have been instantiated, marking that initial binding
229174
* is complete. Subsequent binding operations will detect and publish property changes.
230-
*
231-
* <h3>Example Usage</h3>
232-
* <pre>{@code
233-
* EventPublishingConfigurationPropertiesBeanPropertyChangedListener listener = ...;
234-
* // Called automatically by the Spring container
235-
* listener.afterSingletonsInstantiated();
236-
* assertTrue(listener.isBound());
237-
* }</pre>
238175
*/
239176
@Override
240177
public void afterSingletonsInstantiated() {
178+
for (ConfigurationPropertiesBeanContext beanContext : this.beanContexts.values()) {
179+
beanContext.bindPropertyValues();
180+
}
241181
bound = true;
242182
}
243183

244184
/**
245185
* Returns whether the initial binding of all singleton beans has been completed.
246186
*
247-
* <h3>Example Usage</h3>
248-
* <pre>{@code
249-
* EventPublishingConfigurationPropertiesBeanPropertyChangedListener listener = ...;
250-
* if (listener.isBound()) {
251-
* // Property changes will now be published as events
252-
* }
253-
* }</pre>
254-
*
255187
* @return {@code true} if initial binding is complete, {@code false} otherwise
256188
*/
257189
public boolean isBound() {

microsphere-spring-boot-core/src/test/java/io/microsphere/spring/boot/context/properties/TestConfigurationProperties.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package io.microsphere.spring.boot.context.properties;
1919

20+
import org.springframework.boot.autoconfigure.info.ProjectInfoProperties;
2021
import org.springframework.boot.context.properties.ConfigurationProperties;
2122

2223
import java.util.List;
@@ -40,6 +41,8 @@ public class TestConfigurationProperties {
4041

4142
private List<Integer> ports;
4243

44+
private Map<String, List<ProjectInfoProperties>> projects;
45+
4346
public String getName() {
4447
return name;
4548
}
@@ -71,4 +74,12 @@ public List<Integer> getPorts() {
7174
public void setPorts(List<Integer> ports) {
7275
this.ports = ports;
7376
}
77+
78+
public Map<String, List<ProjectInfoProperties>> getProjects() {
79+
return projects;
80+
}
81+
82+
public void setProjects(Map<String, List<ProjectInfoProperties>> projects) {
83+
this.projects = projects;
84+
}
7485
}

microsphere-spring-boot-core/src/test/java/io/microsphere/spring/boot/context/properties/TestConstructorBindingConfigurationProperties.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package io.microsphere.spring.boot.context.properties;
1919

2020
import org.springframework.boot.context.properties.ConfigurationProperties;
21+
import org.springframework.boot.context.properties.ConstructorBinding;
2122

2223
/**
2324
* {@link ConfigurationProperties @ConfigurationProperties} for testing
@@ -31,12 +32,32 @@
3132
@ConfigurationProperties("test.constructor.binding")
3233
public class TestConstructorBindingConfigurationProperties {
3334

34-
private final String name;
35+
private String name;
3536

36-
private final String value;
37+
private String value;
3738

39+
@ConstructorBinding
3840
public TestConstructorBindingConfigurationProperties(String name, String value) {
3941
this.name = name;
4042
this.value = value;
4143
}
44+
45+
public TestConstructorBindingConfigurationProperties() {
46+
}
47+
48+
public String getName() {
49+
return name;
50+
}
51+
52+
public void setName(String name) {
53+
this.name = name;
54+
}
55+
56+
public String getValue() {
57+
return value;
58+
}
59+
60+
public void setValue(String value) {
61+
this.value = value;
62+
}
4263
}

0 commit comments

Comments
 (0)