• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.21
today-context/src/main/java/infra/context/properties/ConfigurationPropertiesBean.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.context.properties;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.lang.annotation.Annotation;
23
import java.lang.reflect.AnnotatedElement;
24
import java.lang.reflect.Constructor;
25
import java.lang.reflect.Method;
26
import java.util.Iterator;
27
import java.util.LinkedHashMap;
28
import java.util.Map;
29
import java.util.concurrent.atomic.AtomicReference;
30

31
import infra.aop.support.AopUtils;
32
import infra.beans.factory.NoSuchBeanDefinitionException;
33
import infra.beans.factory.config.BeanDefinition;
34
import infra.beans.factory.config.BeanPostProcessor;
35
import infra.beans.factory.config.ConfigurableBeanFactory;
36
import infra.beans.factory.support.RootBeanDefinition;
37
import infra.context.ApplicationContext;
38
import infra.context.ConfigurableApplicationContext;
39
import infra.context.annotation.Bean;
40
import infra.context.properties.bind.BindConstructorProvider;
41
import infra.context.properties.bind.BindMethod;
42
import infra.context.properties.bind.Bindable;
43
import infra.context.properties.bind.Binder;
44
import infra.core.ResolvableType;
45
import infra.core.annotation.MergedAnnotation;
46
import infra.core.annotation.MergedAnnotations;
47
import infra.core.annotation.MergedAnnotations.SearchStrategy;
48
import infra.lang.Assert;
49
import infra.util.ClassUtils;
50
import infra.util.ReflectionUtils;
51
import infra.validation.annotation.Validated;
52

53
/**
54
 * Provides access to {@link ConfigurationProperties @ConfigurationProperties} bean
55
 * details, regardless of if the annotation was used directly or on a {@link Bean @Bean}
56
 * factory method. This class can be used to access {@link #getAll(ApplicationContext)
57
 * all} configuration properties beans in an ApplicationContext, or
58
 * {@link #get(ApplicationContext, Object, String) individual beans} on a case-by-case
59
 * basis (for example, in a {@link BeanPostProcessor}).
60
 *
61
 * @author Phillip Webb
62
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
63
 * @see #getAll(ApplicationContext)
64
 * @see #get(ApplicationContext, Object, String)
65
 * @since 4.0
66
 */
67
public final class ConfigurationPropertiesBean {
68

69
  private final String name;
70

71
  @Nullable
72
  private final Object instance;
73

74
  private final Bindable<?> bindTarget;
75

76
  private ConfigurationPropertiesBean(String name,
77
          @Nullable Object instance, Bindable<?> bindTarget) {
2✔
78
    this.name = name;
3✔
79
    this.instance = instance;
3✔
80
    this.bindTarget = bindTarget;
3✔
81
  }
1✔
82

83
  /**
84
   * Return the name of the bean.
85
   *
86
   * @return the bean name
87
   */
88
  public String getName() {
89
    return this.name;
3✔
90
  }
91

92
  /**
93
   * Return the actual Infra bean instance.
94
   *
95
   * @return the bean instance
96
   */
97
  @Nullable
98
  public Object getInstance() {
99
    return this.instance;
3✔
100
  }
101

102
  /**
103
   * Return the bean type.
104
   *
105
   * @return the bean type
106
   */
107
  @Nullable
108
  Class<?> getType() {
109
    return this.bindTarget.getType().resolve();
5✔
110
  }
111

112
  /**
113
   * Return the {@link ConfigurationProperties} annotation for the bean. The annotation
114
   * may be defined on the bean itself or from the factory method that create the bean
115
   * (usually a {@link Bean @Bean} method).
116
   *
117
   * @return the configuration properties annotation
118
   */
119
  @SuppressWarnings("NullAway")
120
  public ConfigurationProperties getAnnotation() {
121
    return this.bindTarget.getAnnotation(ConfigurationProperties.class);
6✔
122
  }
123

124
  /**
125
   * Return a {@link Bindable} instance suitable that can be used as a target for the
126
   * {@link Binder}.
127
   *
128
   * @return a bind target for use with the {@link Binder}
129
   */
130
  public Bindable<?> asBindTarget() {
131
    return this.bindTarget;
3✔
132
  }
133

134
  /**
135
   * Return all {@link ConfigurationProperties @ConfigurationProperties} beans contained
136
   * in the given application context. Both directly annotated beans, as well as beans
137
   * that have {@link ConfigurationProperties @ConfigurationProperties} annotated
138
   * factory methods are included.
139
   *
140
   * @param applicationContext the source application context
141
   * @return a map of all configuration properties beans keyed by the bean name
142
   */
143
  public static Map<String, ConfigurationPropertiesBean> getAll(ApplicationContext applicationContext) {
144
    Assert.notNull(applicationContext, "ApplicationContext is required");
3✔
145
    if (applicationContext instanceof ConfigurableApplicationContext configurableContext) {
6!
146
      return getAll(configurableContext);
3✔
147
    }
148
    Map<String, ConfigurationPropertiesBean> propertiesBeans = new LinkedHashMap<>();
×
149
    applicationContext.getBeansWithAnnotation(ConfigurationProperties.class).forEach((name, instance) -> {
×
150
      ConfigurationPropertiesBean propertiesBean = get(applicationContext, instance, name);
×
151
      if (propertiesBean != null) {
×
152
        propertiesBeans.put(name, propertiesBean);
×
153
      }
154
    });
×
155
    return propertiesBeans;
×
156
  }
157

158
  @SuppressWarnings("NullAway")
159
  private static Map<String, ConfigurationPropertiesBean> getAll(ConfigurableApplicationContext applicationContext) {
160
    Map<String, ConfigurationPropertiesBean> propertiesBeans = new LinkedHashMap<>();
4✔
161
    ConfigurableBeanFactory beanFactory = applicationContext.getBeanFactory();
3✔
162
    Iterator<String> beanNames = beanFactory.getBeanNamesIterator();
3✔
163
    while (beanNames.hasNext()) {
3✔
164
      String beanName = beanNames.next();
4✔
165
      if (isConfigurationPropertiesBean(beanFactory, beanName)) {
4✔
166
        try {
167
          Object bean = beanFactory.getBean(beanName);
4✔
168
          ConfigurationPropertiesBean propertiesBean = get(applicationContext, bean, beanName);
5✔
169
          if (propertiesBean != null) {
2!
170
            propertiesBeans.put(beanName, propertiesBean);
5✔
171
          }
172
        }
173
        catch (Exception ignored) {
×
174
        }
1✔
175
      }
176
    }
1✔
177
    return propertiesBeans;
2✔
178
  }
179

180
  private static boolean isConfigurationPropertiesBean(ConfigurableBeanFactory beanFactory, String beanName) {
181
    try {
182
      if (beanFactory.getBeanDefinition(beanName).isAbstract()) {
5!
183
        return false;
×
184
      }
185
      if (beanFactory.findAnnotationOnBean(beanName, ConfigurationProperties.class).isPresent()) {
6✔
186
        return true;
2✔
187
      }
188
      Method factoryMethod = findFactoryMethod(beanFactory, beanName);
4✔
189
      return findMergedAnnotation(factoryMethod, ConfigurationProperties.class).isPresent();
5✔
190
    }
191
    catch (NoSuchBeanDefinitionException ex) {
1✔
192
      return false;
2✔
193
    }
194
  }
195

196
  /**
197
   * Return a {@link ConfigurationPropertiesBean @ConfigurationPropertiesBean} instance
198
   * for the given bean details or {@code null} if the bean is not a
199
   * {@link ConfigurationProperties @ConfigurationProperties} object. Annotations are
200
   * considered both on the bean itself, as well as any factory method (for example a
201
   * {@link Bean @Bean} method).
202
   *
203
   * @param applicationContext the source application context
204
   * @param bean the bean to consider
205
   * @param beanName the bean name
206
   * @return a configuration properties bean or {@code null} if the neither the bean nor
207
   * factory method are annotated with
208
   * {@link ConfigurationProperties @ConfigurationProperties}
209
   */
210
  @Nullable
211
  public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
212
    Method factoryMethod = findFactoryMethod(applicationContext, beanName);
4✔
213
    Bindable<Object> bindTarget = createBindTarget(bean, bean.getClass(), factoryMethod);
6✔
214
    if (bindTarget == null) {
2✔
215
      return null;
2✔
216
    }
217
    bindTarget = bindTarget.withBindMethod(BindMethodAttribute.get(applicationContext, beanName));
6✔
218
    if (bindTarget.getBindMethod() == null && factoryMethod != null) {
5✔
219
      bindTarget = bindTarget.withBindMethod(BindMethod.JAVA_BEAN);
4✔
220
    }
221
    if (bindTarget.getBindMethod() != BindMethod.VALUE_OBJECT) {
4✔
222
      bindTarget = bindTarget.withExistingValue(bean);
4✔
223
    }
224
    return create(beanName, bean, bindTarget);
5✔
225
  }
226

227
  @Nullable
228
  private static Method findFactoryMethod(ApplicationContext applicationContext, String beanName) {
229
    if (applicationContext instanceof ConfigurableApplicationContext configurableContext) {
6!
230
      return findFactoryMethod(configurableContext, beanName);
4✔
231
    }
232
    return null;
×
233
  }
234

235
  @Nullable
236
  private static Method findFactoryMethod(ConfigurableApplicationContext applicationContext, String beanName) {
237
    return findFactoryMethod(applicationContext.getBeanFactory(), beanName);
5✔
238
  }
239

240
  @Nullable
241
  private static Method findFactoryMethod(ConfigurableBeanFactory beanFactory, String beanName) {
242
    if (beanFactory.containsBeanDefinition(beanName)) {
4✔
243
      BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
4✔
244
      if (beanDefinition instanceof RootBeanDefinition rootBeanDefinition) {
6!
245
        Method resolvedFactoryMethod = rootBeanDefinition.getResolvedFactoryMethod();
3✔
246
        if (resolvedFactoryMethod != null) {
2✔
247
          return resolvedFactoryMethod;
2✔
248
        }
249
      }
250
      return findFactoryMethodUsingReflection(beanFactory, beanDefinition);
4✔
251
    }
252
    return null;
2✔
253
  }
254

255
  @Nullable
256
  private static Method findFactoryMethodUsingReflection(ConfigurableBeanFactory beanFactory,
257
          BeanDefinition beanDefinition) {
258
    String factoryMethodName = beanDefinition.getFactoryMethodName();
3✔
259
    String factoryBeanName = beanDefinition.getFactoryBeanName();
3✔
260
    if (factoryMethodName == null || factoryBeanName == null) {
2!
261
      return null;
2✔
262
    }
263
    Class<?> factoryType = beanFactory.getType(factoryBeanName);
×
264
    if (factoryType != null) {
×
265
      if (factoryType.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
×
266
        factoryType = factoryType.getSuperclass();
×
267
      }
268
      AtomicReference<Method> factoryMethod = new AtomicReference<>();
×
269
      ReflectionUtils.doWithMethods(factoryType, (method) -> {
×
270
        if (method.getName().equals(factoryMethodName)) {
×
271
          factoryMethod.set(method);
×
272
        }
273
      });
×
274
      return factoryMethod.get();
×
275
    }
276
    return null;
×
277
  }
278

279
  @SuppressWarnings("NullAway")
280
  static ConfigurationPropertiesBean forValueObject(Class<?> beanType, String beanName) {
281
    Bindable<Object> bindTarget = createBindTarget(null, beanType, null);
5✔
282

283
    if (bindTarget == null || deduceBindMethod(bindTarget) != BindMethod.VALUE_OBJECT) {
6✔
284
      throw new IllegalStateException("Bean '" + beanName + "' is not a @ConfigurationProperties value object");
6✔
285
    }
286

287
    return create(beanName, null, bindTarget.withBindMethod(BindMethod.VALUE_OBJECT));
7✔
288
  }
289

290
  @Nullable
291
  private static Bindable<Object> createBindTarget(@Nullable Object bean, Class<?> beanType, @Nullable Method factoryMethod) {
292
    ResolvableType type = factoryMethod != null
2✔
293
            ? ResolvableType.forReturnType(factoryMethod)
3✔
294
            : ResolvableType.forClass(beanType);
3✔
295
    Annotation[] annotations = findAnnotations(bean, beanType, factoryMethod);
5✔
296
    return (annotations != null) ? Bindable.of(type).withAnnotations(annotations) : null;
9✔
297
  }
298

299
  private static Annotation @Nullable [] findAnnotations(@Nullable Object instance, Class<?> type, @Nullable Method factory) {
300
    ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
7✔
301
    if (annotation == null) {
2✔
302
      return null;
2✔
303
    }
304
    Validated validated = findAnnotation(instance, type, factory, Validated.class);
7✔
305
    return (validated != null) ? new Annotation[] { annotation, validated } : new Annotation[] { annotation };
20✔
306
  }
307

308
  @Nullable
309
  @SuppressWarnings("NullAway")
310
  private static <A extends Annotation> A findAnnotation(@Nullable Object instance, Class<?> type,
311
          @Nullable Method factory, Class<A> annotationType) {
312
    MergedAnnotation<A> annotation = MergedAnnotation.missing();
2✔
313
    if (factory != null) {
2✔
314
      annotation = findMergedAnnotation(factory, annotationType);
4✔
315
    }
316
    if (!annotation.isPresent()) {
3✔
317
      annotation = findMergedAnnotation(type, annotationType);
4✔
318
    }
319
    if (!annotation.isPresent() && AopUtils.isAopProxy(instance)) {
6✔
320
      annotation = MergedAnnotations.from(AopUtils.getTargetClass(instance), SearchStrategy.TYPE_HIERARCHY)
5✔
321
              .get(annotationType);
2✔
322
    }
323
    return annotation.isPresent() ? annotation.synthesize() : null;
8✔
324
  }
325

326
  private static <A extends Annotation> MergedAnnotation<A> findMergedAnnotation(
327
          @Nullable AnnotatedElement element, Class<A> annotationType) {
328
    return element != null
3✔
329
            ? MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY).get(annotationType)
6✔
330
            : MergedAnnotation.missing();
1✔
331
  }
332

333
  @Nullable
334
  private static ConfigurationPropertiesBean create(String name, @Nullable Object instance, @Nullable Bindable<Object> bindTarget) {
335
    return bindTarget != null
3!
336
            ? new ConfigurationPropertiesBean(name, instance, bindTarget)
7✔
337
            : null;
×
338
  }
339

340
  /**
341
   * Deduce the {@code BindMethod} that should be used for the given type.
342
   *
343
   * @param type the source type
344
   * @return the bind method to use
345
   */
346
  public static BindMethod deduceBindMethod(Class<?> type) {
347
    return deduceBindMethod(BindConstructorProvider.DEFAULT.getBindConstructor(type, false));
6✔
348
  }
349

350
  /**
351
   * Deduce the {@code BindMethod} that should be used for the given {@link Bindable}.
352
   *
353
   * @param bindable the source bindable
354
   * @return the bind method to use
355
   */
356
  static BindMethod deduceBindMethod(Bindable<Object> bindable) {
357
    return deduceBindMethod(BindConstructorProvider.DEFAULT.getBindConstructor(bindable, false));
6✔
358
  }
359

360
  private static BindMethod deduceBindMethod(@Nullable Constructor<?> bindConstructor) {
361
    return bindConstructor != null ? BindMethod.VALUE_OBJECT : BindMethod.JAVA_BEAN;
6✔
362
  }
363

364
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc