1616
1717package org .springframework .boot .context .properties ;
1818
19+ import java .util .ArrayList ;
20+ import java .util .List ;
21+
1922import org .springframework .boot .context .properties .bind .BindHandler ;
2023import org .springframework .boot .context .properties .bind .Bindable ;
2124import org .springframework .boot .context .properties .bind .Binder ;
2225import org .springframework .boot .context .properties .bind .PropertySourcesPlaceholdersResolver ;
2326import org .springframework .boot .context .properties .bind .handler .IgnoreErrorsBindHandler ;
2427import org .springframework .boot .context .properties .bind .handler .NoUnboundElementsBindHandler ;
2528import org .springframework .boot .context .properties .bind .validation .ValidationBindHandler ;
26- import org .springframework .boot .context .properties .source .ConfigurationPropertySource ;
2729import org .springframework .boot .context .properties .source .ConfigurationPropertySources ;
2830import org .springframework .boot .context .properties .source .UnboundElementsSourceFilter ;
29- import org .springframework .core .ResolvableType ;
30- import org .springframework .core .convert .ConversionService ;
31- import org .springframework .core .env .PropertySource ;
31+ import org .springframework .context .ApplicationContext ;
32+ import org .springframework .core .env .PropertySources ;
3233import org .springframework .util .Assert ;
33- import org .springframework .util .ClassUtils ;
34- import org .springframework .validation .Errors ;
3534import org .springframework .validation .Validator ;
35+ import org .springframework .validation .annotation .Validated ;
3636
3737/**
38- * Bind {@link ConfigurationProperties} annotated object from a configurable list of
39- * {@link PropertySource} .
38+ * Internal class by the {@link ConfigurationPropertiesBindingPostProcessor} to handle the
39+ * actual {@link ConfigurationProperties} binding .
4040 *
4141 * @author Stephane Nicoll
42+ * @author Phillip Webb
4243 */
4344class ConfigurationPropertiesBinder {
4445
45- private final Iterable < PropertySource <?>> propertySources ;
46+ private final ApplicationContext applicationContext ;
4647
47- private final ConversionService conversionService ;
48+ private final PropertySources propertySources ;
4849
49- private final Validator validator ;
50+ private final Validator configurationPropertiesValidator ;
5051
51- private Iterable < ConfigurationPropertySource > configurationSources ;
52+ private final Validator jsr303Validator ;
5253
53- private final Binder binder ;
54+ private volatile Binder binder ;
5455
55- ConfigurationPropertiesBinder (Iterable < PropertySource <?>> propertySources ,
56- ConversionService conversionService , Validator validator ) {
57- Assert . notNull ( propertySources , "PropertySources must not be null" ) ;
58- this .propertySources = propertySources ;
59- this . conversionService = conversionService ;
60- this .validator = validator ;
61- this . configurationSources = ConfigurationPropertySources . from ( propertySources );
62- this .binder = new Binder ( this . configurationSources ,
63- new PropertySourcesPlaceholdersResolver ( this . propertySources ),
64- this . conversionService );
56+ ConfigurationPropertiesBinder (ApplicationContext applicationContext ,
57+ String validatorBeanName ) {
58+ this . applicationContext = applicationContext ;
59+ this .propertySources = new PropertySourcesDeducer ( applicationContext )
60+ . getPropertySources () ;
61+ this .configurationPropertiesValidator = getConfigurationPropertiesValidator (
62+ applicationContext , validatorBeanName );
63+ this .jsr303Validator = ConfigurationPropertiesJsr303Validator
64+ . getIfJsr303Present ( applicationContext );
65+ }
6566
67+ public void bind (Bindable <?> target ) {
68+ ConfigurationProperties annotation = target
69+ .getAnnotation (ConfigurationProperties .class );
70+ Assert .state (annotation != null , "Missing @ConfigurationProperties on " + target );
71+ List <Validator > validators = getValidators (target );
72+ BindHandler bindHandler = getBindHandler (annotation , validators );
73+ getBinder ().bind (annotation .prefix (), target , bindHandler );
6674 }
6775
68- /**
69- * Bind the specified {@code target} object using the configuration defined by the
70- * specified {@code annotation}.
71- * @param target the target to bind the configuration property sources to
72- * @param annotation the binding configuration
73- * @param targetType the resolvable type for the target
74- * @throws ConfigurationPropertiesBindingException if the binding failed
75- */
76- void bind (Object target , ConfigurationProperties annotation ,
77- ResolvableType targetType ) {
78- Validator validator = determineValidator (target );
79- BindHandler handler = getBindHandler (annotation , validator );
80- Bindable <?> bindable = Bindable .of (targetType ).withExistingValue (target );
81- try {
82- this .binder .bind (annotation .prefix (), bindable , handler );
83- }
84- catch (Exception ex ) {
85- String message = "Could not bind properties to '"
86- + ClassUtils .getShortName (target .getClass ()) + "': "
87- + getAnnotationDetails (annotation );
88- throw new ConfigurationPropertiesBindingException (message , ex );
76+ private Validator getConfigurationPropertiesValidator (
77+ ApplicationContext applicationContext , String validatorBeanName ) {
78+ if (applicationContext .containsBean (validatorBeanName )) {
79+ return applicationContext .getBean (validatorBeanName , Validator .class );
8980 }
81+ return null ;
9082 }
9183
92- private Validator determineValidator ( Object bean ) {
93- boolean supportsBean = ( this . validator != null
94- && this .validator . supports ( bean . getClass ()));
95- if ( ClassUtils . isAssignable ( Validator . class , bean . getClass ())) {
96- if ( supportsBean ) {
97- return new ChainingValidator (this .validator , ( Validator ) bean );
98- }
99- return ( Validator ) bean ;
84+ private List < Validator > getValidators ( Bindable <?> target ) {
85+ List < Validator > validators = new ArrayList <>( 3 );
86+ if ( this .configurationPropertiesValidator != null ) {
87+ validators . add ( this . configurationPropertiesValidator );
88+ }
89+ if (this .jsr303Validator != null
90+ && target . getAnnotation ( Validated . class ) != null ) {
91+ validators . add ( this . jsr303Validator ) ;
10092 }
101- return (supportsBean ? this .validator : null );
93+ if (target .getValue () != null && target .getValue ().get () instanceof Validator ) {
94+ validators .add ((Validator ) target .getValue ().get ());
95+ }
96+ return validators ;
10297 }
10398
10499 private BindHandler getBindHandler (ConfigurationProperties annotation ,
105- Validator validator ) {
100+ List < Validator > validators ) {
106101 BindHandler handler = BindHandler .DEFAULT ;
107102 if (annotation .ignoreInvalidFields ()) {
108103 handler = new IgnoreErrorsBindHandler (handler );
@@ -111,55 +106,22 @@ private BindHandler getBindHandler(ConfigurationProperties annotation,
111106 UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter ();
112107 handler = new NoUnboundElementsBindHandler (handler , filter );
113108 }
114- if (validator != null ) {
115- handler = new ValidationBindHandler (handler , validator );
109+ if (!validators .isEmpty ()) {
110+ handler = new ValidationBindHandler (handler ,
111+ validators .toArray (new Validator [validators .size ()]));
116112 }
117113 return handler ;
118114 }
119115
120- private String getAnnotationDetails (ConfigurationProperties annotation ) {
121- if (annotation == null ) {
122- return "" ;
116+ private Binder getBinder () {
117+ if (this .binder == null ) {
118+ this .binder = new Binder (
119+ ConfigurationPropertySources .from (this .propertySources ),
120+ new PropertySourcesPlaceholdersResolver (this .propertySources ),
121+ new ConversionServiceDeducer (this .applicationContext )
122+ .getConversionService ());
123123 }
124- StringBuilder details = new StringBuilder ();
125- details .append ("prefix=" ).append (annotation .prefix ());
126- details .append (", ignoreInvalidFields=" ).append (annotation .ignoreInvalidFields ());
127- details .append (", ignoreUnknownFields=" ).append (annotation .ignoreUnknownFields ());
128- return details .toString ();
129- }
130-
131- /**
132- * {@link Validator} implementation that wraps {@link Validator} instances and chains
133- * their execution.
134- */
135- private static class ChainingValidator implements Validator {
136-
137- private final Validator [] validators ;
138-
139- ChainingValidator (Validator ... validators ) {
140- Assert .notNull (validators , "Validators must not be null" );
141- this .validators = validators ;
142- }
143-
144- @ Override
145- public boolean supports (Class <?> clazz ) {
146- for (Validator validator : this .validators ) {
147- if (validator .supports (clazz )) {
148- return true ;
149- }
150- }
151- return false ;
152- }
153-
154- @ Override
155- public void validate (Object target , Errors errors ) {
156- for (Validator validator : this .validators ) {
157- if (validator .supports (target .getClass ())) {
158- validator .validate (target , errors );
159- }
160- }
161- }
162-
124+ return this .binder ;
163125 }
164126
165127}
0 commit comments