parent
711169aa8a
commit
8f693a0277
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue