Add support for configuration properties scanning
See gh-12602
This commit is contained in:
@@ -25,6 +25,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.context.TypeExcludeFilter;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
@@ -35,9 +36,11 @@ import org.springframework.core.annotation.AliasFor;
|
||||
/**
|
||||
* Indicates a {@link Configuration configuration} class that declares one or more
|
||||
* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
|
||||
* auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
|
||||
* annotation that is equivalent to declaring {@code @Configuration},
|
||||
* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
|
||||
* auto-configuration}, {@link ComponentScan component scanning}, and
|
||||
* {@link ConfigurationPropertiesScan configuration properties scanning}. This is a
|
||||
* convenience annotation that is equivalent to declaring {@code @Configuration},
|
||||
* {@code @EnableAutoConfiguration}, {@code @ComponentScan}, and
|
||||
* {@code @ConfigurationPropertiesScan}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
@@ -50,6 +53,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||
@Inherited
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@ConfigurationPropertiesScan
|
||||
@ComponentScan(excludeFilters = {
|
||||
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
|
||||
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
|
||||
|
||||
@@ -1070,7 +1070,23 @@ missing property.
|
||||
[[boot-features-external-config-enabling]]
|
||||
==== Enabling `@ConfigurationProperties`-annotated types
|
||||
Spring Boot provides an infrastructure to bind such types and register them as beans
|
||||
automatically. Any `@Configuration` class can specify the list of types to process as
|
||||
automatically. If your application uses `@SpringBootApplication`, classes annotated with
|
||||
`@ConfigurationProperties` will automatically be scanned and registered as beans. By default,
|
||||
scanning will occur from the package of the class that declares this annotation. If you want
|
||||
to define specific packages to scan, you can do so using an explicit `@ConfigurationPropertiesScan`
|
||||
directive on your `@SpringBootApplication`-annotated class as shown in the following example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@SpringBootApplication
|
||||
@ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" })
|
||||
public class MyApplication {
|
||||
}
|
||||
----
|
||||
|
||||
Sometimes, classes annotated with `@ConfigurationProperties` might not be suitable
|
||||
for scanning, for example, if you're developing your own auto-configuration. In these
|
||||
cases, you can specify the list of types to process on any `@Configuration` class as
|
||||
shown in the following example:
|
||||
|
||||
[source,java,indent=0]
|
||||
@@ -1083,13 +1099,13 @@ shown in the following example:
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
When the `@ConfigurationProperties` bean is registered that way, the bean has a
|
||||
conventional name: `<prefix>-<fqn>`, where `<prefix>` is the environment key prefix
|
||||
specified in the `@ConfigurationProperties` annotation and `<fqn>` is the fully qualified
|
||||
name of the bean. If the annotation does not provide any prefix, only the fully qualified
|
||||
name of the bean is used.
|
||||
When the `@ConfigurationProperties` bean is registered using scanning or via
|
||||
`@EnableConfigurationProperties`, the bean has a conventional name: `<prefix>-<fqn>`,
|
||||
where `<prefix>` is the environment key prefix specified in the `@ConfigurationProperties`
|
||||
annotation and `<fqn>` is the fully qualified name of the bean. If the annotation does not
|
||||
provide any prefix, only the fully qualified name of the bean is used.
|
||||
|
||||
The bean name in the examples above is `acme-com.example.AcmeProperties`.
|
||||
The bean name in the example above is `acme-com.example.AcmeProperties`.
|
||||
====
|
||||
|
||||
We recommend that `@ConfigurationProperties` only deal with the environment and, in
|
||||
@@ -1100,27 +1116,10 @@ binder that only deals with the environment.
|
||||
For corner cases, setter injection can be used or any of the `*Aware` interfaces provided
|
||||
by the framework (such as `EnvironmentAware` if you need access to the `Environment`).
|
||||
|
||||
If you find using `@EnableConfigurationProperties` tedious, you can also declare a bean
|
||||
yourself. For instance, instead of annotating `MyConfiguration` with
|
||||
`@EnableConfigurationProperties(AcmeProperties.class)`, you could make `AcmeProperties`
|
||||
a bean, as shown in the following example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Component
|
||||
@ConfigurationProperties(prefix="acme")
|
||||
public class AcmeProperties {
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
private InetAddress remoteAddress;
|
||||
|
||||
private final Security security = new Security();
|
||||
|
||||
// ... see the preceding JavaBean properties binding example
|
||||
|
||||
}
|
||||
----
|
||||
NOTE: Annotating a `@ConfigurationProperties` type with `@Component` will result in two
|
||||
beans of the same type if the type is also scanned as part of classpath scanning. If you want
|
||||
to register the bean yourself using `@Component`, consider disabling scanning of
|
||||
`@ConfigurationProperties`.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -336,8 +336,8 @@ best practices that help.
|
||||
When a class does not include a `package` declaration, it is considered to be in the
|
||||
"`default package`". The use of the "`default package`" is generally discouraged and
|
||||
should be avoided. It can cause particular problems for Spring Boot applications that use
|
||||
the `@ComponentScan`, `@EntityScan`, or `@SpringBootApplication` annotations, since every
|
||||
class from every jar is read.
|
||||
the `@ComponentScan`, `@ConfigurationPropertiesScan`, `@EntityScan`, or `@SpringBootApplication`
|
||||
annotations, since every class from every jar is read.
|
||||
|
||||
TIP: We recommend that you follow Java's recommended package naming conventions and use a
|
||||
reversed domain name (for example, `com.example.project`).
|
||||
@@ -355,8 +355,8 @@ is used to search for `@Entity` items. Using a root package also allows componen
|
||||
scan to apply only on your project.
|
||||
|
||||
TIP: If you don't want to use `@SpringBootApplication`, the `@EnableAutoConfiguration`
|
||||
and `@ComponentScan` annotations that it imports defines that behaviour so you can also
|
||||
use that instead.
|
||||
`@ComponentScan`, and `@ConfigurationPropertiesScan` annotations that it imports defines
|
||||
that behaviour so you can also use those instead.
|
||||
|
||||
The following listing shows a typical layout:
|
||||
|
||||
@@ -556,12 +556,14 @@ be able to define extra configuration on their "application class". A single
|
||||
auto-configuration mechanism>>
|
||||
* `@ComponentScan`: enable `@Component` scan on the package where the application is
|
||||
located (see <<using-boot-structuring-your-code,the best practices>>)
|
||||
* `@ConfigurationPropertiesScan`: enable `@ConfigurationProperties` scan on the package
|
||||
where the application is located (see <<using-boot-structuring-your-code,the best practices>>)
|
||||
* `@Configuration`: allow to register extra beans in the context or import additional
|
||||
configuration classes
|
||||
|
||||
The `@SpringBootApplication` annotation is equivalent to using `@Configuration`,
|
||||
`@EnableAutoConfiguration`, and `@ComponentScan` with their default attributes, as shown
|
||||
in the following example:
|
||||
`@EnableAutoConfiguration`, @ComponentScan`, and `@ConfigurationPropertiesScan` with their default
|
||||
attributes, as shown in the following example:
|
||||
|
||||
|
||||
[source,java,indent=0]
|
||||
@@ -571,7 +573,7 @@ in the following example:
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan
|
||||
@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan @ConfigurationPropertiesScan
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -588,7 +590,7 @@ NOTE: `@SpringBootApplication` also provides aliases to customize the attributes
|
||||
====
|
||||
None of these features are mandatory and you may choose to replace this single annotation
|
||||
by any of the features that it enables. For instance, you may not want to use component
|
||||
scan in your application:
|
||||
scan or configuration properties scan in your application:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@@ -612,8 +614,8 @@ scan in your application:
|
||||
----
|
||||
|
||||
In this example, `Application` is just like any other Spring Boot application except that
|
||||
`@Component`-annotated classes are not detected automatically and the user-defined beans
|
||||
are imported explicitly (see `@Import`).
|
||||
`@Component`-annotated classes and `@ConfigurationProperties`-annotated classes are not detected
|
||||
automatically and the user-defined beans are imported explicitly (see `@Import`).
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Registers a bean definition for a type annotated with {@link ConfigurationProperties}
|
||||
* using the prefix of the annotation in the bean name.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
final class ConfigurationPropertiesBeanDefinitionRegistrar {
|
||||
|
||||
private ConfigurationPropertiesBeanDefinitionRegistrar() {
|
||||
}
|
||||
|
||||
private static final boolean KOTLIN_PRESENT = KotlinDetector.isKotlinPresent();
|
||||
|
||||
public static void register(BeanDefinitionRegistry registry,
|
||||
ConfigurableListableBeanFactory beanFactory, Class<?> type) {
|
||||
String name = getName(type);
|
||||
if (!containsBeanDefinition(beanFactory, name)) {
|
||||
registerBeanDefinition(registry, beanFactory, name, type);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getName(Class<?> type) {
|
||||
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type,
|
||||
ConfigurationProperties.class);
|
||||
String prefix = (annotation != null) ? annotation.prefix() : "";
|
||||
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName()
|
||||
: type.getName());
|
||||
}
|
||||
|
||||
private static boolean containsBeanDefinition(
|
||||
ConfigurableListableBeanFactory beanFactory, String name) {
|
||||
if (beanFactory.containsBeanDefinition(name)) {
|
||||
return true;
|
||||
}
|
||||
BeanFactory parent = beanFactory.getParentBeanFactory();
|
||||
if (parent instanceof ConfigurableListableBeanFactory) {
|
||||
return containsBeanDefinition((ConfigurableListableBeanFactory) parent, name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void registerBeanDefinition(BeanDefinitionRegistry registry,
|
||||
ConfigurableListableBeanFactory beanFactory, String name, Class<?> type) {
|
||||
assertHasAnnotation(type);
|
||||
registry.registerBeanDefinition(name,
|
||||
createBeanDefinition(beanFactory, name, type));
|
||||
}
|
||||
|
||||
private static void assertHasAnnotation(Class<?> type) {
|
||||
Assert.notNull(
|
||||
AnnotationUtils.findAnnotation(type, ConfigurationProperties.class),
|
||||
() -> "No " + ConfigurationProperties.class.getSimpleName()
|
||||
+ " annotation found on '" + type.getName() + "'.");
|
||||
}
|
||||
|
||||
private static BeanDefinition createBeanDefinition(
|
||||
ConfigurableListableBeanFactory beanFactory, String name, Class<?> type) {
|
||||
if (canBindAtCreationTime(type)) {
|
||||
return ConfigurationPropertiesBeanDefinition.from(beanFactory, name, type);
|
||||
}
|
||||
else {
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
definition.setBeanClass(type);
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canBindAtCreationTime(Class<?> type) {
|
||||
List<Constructor<?>> constructors = determineConstructors(type);
|
||||
return (constructors.size() == 1 && constructors.get(0).getParameterCount() > 0);
|
||||
}
|
||||
|
||||
private static List<Constructor<?>> determineConstructors(Class<?> type) {
|
||||
List<Constructor<?>> constructors = new ArrayList<>();
|
||||
if (KOTLIN_PRESENT && KotlinDetector.isKotlinType(type)) {
|
||||
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type);
|
||||
if (primaryConstructor != null) {
|
||||
constructors.add(primaryConstructor);
|
||||
}
|
||||
}
|
||||
else {
|
||||
constructors.addAll(Arrays.asList(type.getDeclaredConstructors()));
|
||||
}
|
||||
return constructors;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* Configures the base packages used when scanning for {@link ConfigurationProperties}
|
||||
* classes. One of {@link #basePackageClasses()}, {@link #basePackages()} or its alias
|
||||
* {@link #value()} may be specified to define specific packages to scan. If specific
|
||||
* packages are not defined scanning will occur from the package of the class with this
|
||||
* annotation.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.2.0
|
||||
* @see ConfigurationPropertiesScanRegistrar
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import(ConfigurationPropertiesScanRegistrar.class)
|
||||
public @interface ConfigurationPropertiesScan {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
|
||||
* declarations e.g.: {@code @ConfigurationPropertiesScan("org.my.pkg")} instead of
|
||||
* {@code @ConfigurationPropertiesScan(basePackages="org.my.pkg")}.
|
||||
* @return the base packages to scan
|
||||
*/
|
||||
@AliasFor("basePackages")
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Base packages to scan for configuration properties. {@link #value()} is an alias
|
||||
* for (and mutually exclusive with) this attribute.
|
||||
* <p>
|
||||
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
|
||||
* package names.
|
||||
* @return the base packages to scan
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String[] basePackages() default {};
|
||||
|
||||
/**
|
||||
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
|
||||
* scan for configuration properties. The package of each class specified will be
|
||||
* scanned.
|
||||
* <p>
|
||||
* Consider creating a special no-op marker class or interface in each package that
|
||||
* serves no purpose other than being referenced by this attribute.
|
||||
* @return classes from the base packages to scan
|
||||
*/
|
||||
Class<?>[] basePackageClasses() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} for registering {@link ConfigurationProperties}
|
||||
* bean definitions via scanning.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
|
||||
register(registry, (ConfigurableListableBeanFactory) registry, packagesToScan);
|
||||
}
|
||||
|
||||
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
|
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
|
||||
.getAnnotationAttributes(ConfigurationPropertiesScan.class.getName()));
|
||||
String[] basePackages = attributes.getStringArray("basePackages");
|
||||
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
|
||||
Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));
|
||||
for (Class<?> basePackageClass : basePackageClasses) {
|
||||
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
|
||||
}
|
||||
if (packagesToScan.isEmpty()) {
|
||||
packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
|
||||
}
|
||||
return packagesToScan;
|
||||
}
|
||||
|
||||
protected void register(BeanDefinitionRegistry registry,
|
||||
ConfigurableListableBeanFactory beanFactory, Set<String> packagesToScan) {
|
||||
scan(packagesToScan, beanFactory, registry);
|
||||
}
|
||||
|
||||
protected void scan(Set<String> packages, ConfigurableListableBeanFactory beanFactory,
|
||||
BeanDefinitionRegistry registry) {
|
||||
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(
|
||||
false);
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(ConfigurationProperties.class));
|
||||
for (String basePackage : packages) {
|
||||
if (StringUtils.hasText(basePackage)) {
|
||||
for (BeanDefinition candidate : scanner
|
||||
.findCandidateComponents(basePackage)) {
|
||||
String beanClassName = candidate.getBeanClassName();
|
||||
try {
|
||||
Class<?> type = ClassUtils.forName(beanClassName, null);
|
||||
ConfigurationPropertiesBeanDefinitionRegistrar.register(registry,
|
||||
beanFactory, type);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,27 +16,17 @@
|
||||
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Import selector that sets up binding of external properties to configuration classes
|
||||
@@ -54,8 +44,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
|
||||
|
||||
private static boolean KOTLIN_PRESENT = KotlinDetector.isKotlinPresent();
|
||||
|
||||
private static final String[] IMPORTS = {
|
||||
ConfigurationPropertiesBeanRegistrar.class.getName(),
|
||||
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
|
||||
@@ -74,8 +62,10 @@ class EnableConfigurationPropertiesImportSelector implements ImportSelector {
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
getTypes(metadata).forEach((type) -> register(registry,
|
||||
(ConfigurableListableBeanFactory) registry, type));
|
||||
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry;
|
||||
getTypes(metadata)
|
||||
.forEach((type) -> ConfigurationPropertiesBeanDefinitionRegistrar
|
||||
.register(registry, beanFactory, type));
|
||||
}
|
||||
|
||||
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
|
||||
@@ -92,83 +82,6 @@ class EnableConfigurationPropertiesImportSelector implements ImportSelector {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void register(BeanDefinitionRegistry registry,
|
||||
ConfigurableListableBeanFactory beanFactory, Class<?> type) {
|
||||
String name = getName(type);
|
||||
if (!containsBeanDefinition(beanFactory, name)) {
|
||||
registerBeanDefinition(registry, beanFactory, name, type);
|
||||
}
|
||||
}
|
||||
|
||||
private String getName(Class<?> type) {
|
||||
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type,
|
||||
ConfigurationProperties.class);
|
||||
String prefix = (annotation != null) ? annotation.prefix() : "";
|
||||
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName()
|
||||
: type.getName());
|
||||
}
|
||||
|
||||
private boolean containsBeanDefinition(
|
||||
ConfigurableListableBeanFactory beanFactory, String name) {
|
||||
if (beanFactory.containsBeanDefinition(name)) {
|
||||
return true;
|
||||
}
|
||||
BeanFactory parent = beanFactory.getParentBeanFactory();
|
||||
if (parent instanceof ConfigurableListableBeanFactory) {
|
||||
return containsBeanDefinition((ConfigurableListableBeanFactory) parent,
|
||||
name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void registerBeanDefinition(BeanDefinitionRegistry registry,
|
||||
ConfigurableListableBeanFactory beanFactory, String name, Class<?> type) {
|
||||
assertHasAnnotation(type);
|
||||
registry.registerBeanDefinition(name,
|
||||
createBeanDefinition(beanFactory, name, type));
|
||||
}
|
||||
|
||||
private void assertHasAnnotation(Class<?> type) {
|
||||
Assert.notNull(
|
||||
AnnotationUtils.findAnnotation(type, ConfigurationProperties.class),
|
||||
() -> "No " + ConfigurationProperties.class.getSimpleName()
|
||||
+ " annotation found on '" + type.getName() + "'.");
|
||||
}
|
||||
|
||||
private BeanDefinition createBeanDefinition(
|
||||
ConfigurableListableBeanFactory beanFactory, String name, Class<?> type) {
|
||||
if (canBindAtCreationTime(type)) {
|
||||
return ConfigurationPropertiesBeanDefinition.from(beanFactory, name,
|
||||
type);
|
||||
}
|
||||
else {
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
definition.setBeanClass(type);
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canBindAtCreationTime(Class<?> type) {
|
||||
List<Constructor<?>> constructors = determineConstructors(type);
|
||||
return (constructors.size() == 1
|
||||
&& constructors.get(0).getParameterCount() > 0);
|
||||
}
|
||||
|
||||
private List<Constructor<?>> determineConstructors(Class<?> type) {
|
||||
List<Constructor<?>> constructors = new ArrayList<>();
|
||||
if (KOTLIN_PRESENT && KotlinDetector.isKotlinType(type)) {
|
||||
Constructor<?> primaryConstructor = BeanUtils
|
||||
.findPrimaryConstructor(type);
|
||||
if (primaryConstructor != null) {
|
||||
constructors.add(primaryConstructor);
|
||||
}
|
||||
}
|
||||
else {
|
||||
constructors.addAll(Arrays.asList(type.getDeclaredConstructors()));
|
||||
}
|
||||
return constructors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConfigurationPropertiesScanRegistrar}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class ConfigurationPropertiesScanRegistrarTests {
|
||||
|
||||
private final ConfigurationPropertiesScanRegistrar registrar = new ConfigurationPropertiesScanRegistrar();
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
@Test
|
||||
public void registerBeanDefintionsShouldScanForConfigurationProperties()
|
||||
throws IOException {
|
||||
this.registrar.registerBeanDefinitions(
|
||||
getAnnotationMetadata(ConfigurationPropertiesScanConfiguration.class),
|
||||
this.beanFactory);
|
||||
BeanDefinition bingDefinition = this.beanFactory.getBeanDefinition(
|
||||
"bing-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$BingProperties");
|
||||
BeanDefinition fooDefinition = this.beanFactory.getBeanDefinition(
|
||||
"foo-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$FooProperties");
|
||||
BeanDefinition barDefinition = this.beanFactory.getBeanDefinition(
|
||||
"bar-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$BarProperties");
|
||||
assertThat(bingDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
assertThat(barDefinition)
|
||||
.isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scanWhenBeanDefinitionExistsShouldSkip() throws IOException {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.setAllowBeanDefinitionOverriding(false);
|
||||
this.registrar.registerBeanDefinitions(
|
||||
getAnnotationMetadata(
|
||||
ConfigurationPropertiesScanConfiguration.TestConfiguration.class),
|
||||
beanFactory);
|
||||
BeanDefinition fooDefinition = beanFactory.getBeanDefinition(
|
||||
"foo-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$FooProperties");
|
||||
assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scanWhenBasePackagesAndBasePackcageClassesProvidedShouldUseThat()
|
||||
throws IOException {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.setAllowBeanDefinitionOverriding(false);
|
||||
this.registrar.registerBeanDefinitions(getAnnotationMetadata(
|
||||
ConfigurationPropertiesScanConfiguration.DifferentPackageConfiguration.class),
|
||||
beanFactory);
|
||||
assertThat(beanFactory.containsBeanDefinition(
|
||||
"foo-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$FooProperties"))
|
||||
.isFalse();
|
||||
BeanDefinition aDefinition = beanFactory.getBeanDefinition(
|
||||
"a-org.springframework.boot.context.properties.scan.a.AScanConfiguration$AProperties");
|
||||
BeanDefinition bDefinition = beanFactory.getBeanDefinition(
|
||||
"b-org.springframework.boot.context.properties.scan.b.BScanConfiguration$BProperties");
|
||||
assertThat(aDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
assertThat(bDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
private AnnotationMetadata getAnnotationMetadata(Class<?> source) throws IOException {
|
||||
return new SimpleMetadataReaderFactory().getMetadataReader(source.getName())
|
||||
.getAnnotationMetadata();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.context.properties.scan;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.scan.b.BScanConfiguration;
|
||||
|
||||
/**
|
||||
* Used for testing {@link ConfigurationProperties} scanning.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@ConfigurationPropertiesScan
|
||||
public class ConfigurationPropertiesScanConfiguration {
|
||||
|
||||
@ConfigurationPropertiesScan
|
||||
@EnableConfigurationProperties({
|
||||
ConfigurationPropertiesScanConfiguration.FooProperties.class })
|
||||
public static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationPropertiesScan(basePackages = "org.springframework.boot.context.properties.scan.a", basePackageClasses = BScanConfiguration.class)
|
||||
public static class DifferentPackageConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "foo")
|
||||
static class FooProperties {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "bar")
|
||||
public static class BarProperties {
|
||||
|
||||
public BarProperties(String foo) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "bing")
|
||||
public static class BingProperties {
|
||||
|
||||
public BingProperties() {
|
||||
|
||||
}
|
||||
|
||||
public BingProperties(String foo) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.context.properties.scan.a;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class AScanConfiguration {
|
||||
|
||||
@ConfigurationProperties(prefix = "a")
|
||||
static class AProperties {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.context.properties.scan.b;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class BScanConfiguration {
|
||||
|
||||
@ConfigurationProperties(prefix = "b")
|
||||
static class BProperties {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user