Merge pull request #20929 from encircled

* pr/20929:
  Polish 'Allow beans without public constructors to load'
  Allow beans without public constructors to load

Closes gh-20929
pull/21793/head
Phillip Webb 5 years ago
commit 6e02049dd9

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.boot; package org.springframework.boot;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -31,8 +32,6 @@ import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@ -41,9 +40,9 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter; import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
import org.springframework.core.type.filter.TypeFilter; import org.springframework.core.type.filter.TypeFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -53,6 +52,7 @@ import org.springframework.util.StringUtils;
* {@link SpringApplication} for the types of sources that are supported. * {@link SpringApplication} for the types of sources that are supported.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Vladislav Kisel
* @see #setBeanNameGenerator(BeanNameGenerator) * @see #setBeanNameGenerator(BeanNameGenerator)
*/ */
class BeanDefinitionLoader { class BeanDefinitionLoader {
@ -153,7 +153,7 @@ class BeanDefinitionLoader {
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
load(loader); load(loader);
} }
if (isComponent(source)) { if (isEligible(source)) {
this.annotatedReader.register(source); this.annotatedReader.register(source);
return 1; return 1;
} }
@ -273,16 +273,23 @@ class BeanDefinitionLoader {
return Package.getPackage(source.toString()); return Package.getPackage(source.toString());
} }
private boolean isComponent(Class<?> type) { /**
// This has to be a bit of a guess. The only way to be sure that this type is * Check whether the bean is eligible for registration.
// eligible is to make a bean definition out of it and try to instantiate it. * @param type candidate bean type
if (MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class)) { * @return true if the given bean type is eligible for registration, i.e. not a groovy
return true; * closure nor an anonymous class
} */
// Nested anonymous classes are not eligible for registration, nor are groovy private boolean isEligible(Class<?> type) {
// closures return !(type.isAnonymousClass() || isGroovyClosure(type) || hasNoConstructors(type));
return !type.getName().matches(".*\\$_.*closure.*") && !type.isAnonymousClass() }
&& type.getConstructors() != null && type.getConstructors().length != 0;
private boolean isGroovyClosure(Class<?> type) {
return type.getName().matches(".*\\$_.*closure.*");
}
private boolean hasNoConstructors(Class<?> type) {
Constructor<?>[] constructors = type.getDeclaredConstructors();
return ObjectUtils.isEmpty(constructors);
} }
/** /**

@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
import sampleconfig.MyComponentInPackageWithoutDot; import sampleconfig.MyComponentInPackageWithoutDot;
import org.springframework.boot.sampleconfig.MyComponent; import org.springframework.boot.sampleconfig.MyComponent;
import org.springframework.boot.sampleconfig.MyNamedComponent;
import org.springframework.context.support.StaticApplicationContext; import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
@ -31,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link BeanDefinitionLoader}. * Tests for {@link BeanDefinitionLoader}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Vladislav Kisel
*/ */
class BeanDefinitionLoaderTests { class BeanDefinitionLoaderTests {
@ -53,6 +55,22 @@ class BeanDefinitionLoaderTests {
assertThat(this.registry.containsBean("myComponent")).isTrue(); assertThat(this.registry.containsBean("myComponent")).isTrue();
} }
@Test
void anonymousClassNotLoaded() {
MyComponent myComponent = new MyComponent() {
};
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, myComponent.getClass());
assertThat(loader.load()).isEqualTo(0);
}
@Test
void loadJsr330Class() {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyNamedComponent.class);
assertThat(loader.load()).isEqualTo(1);
assertThat(this.registry.containsBean("myNamedComponent")).isTrue();
}
@Test @Test
void loadXmlResource() { void loadXmlResource() {
ClassPathResource resource = new ClassPathResource("sample-beans.xml", getClass()); ClassPathResource resource = new ClassPathResource("sample-beans.xml", getClass());
@ -83,8 +101,9 @@ class BeanDefinitionLoaderTests {
@Test @Test
void loadPackage() { void loadPackage() {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyComponent.class.getPackage()); BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyComponent.class.getPackage());
assertThat(loader.load()).isEqualTo(1); assertThat(loader.load()).isEqualTo(2);
assertThat(this.registry.containsBean("myComponent")).isTrue(); assertThat(this.registry.containsBean("myComponent")).isTrue();
assertThat(this.registry.containsBean("myNamedComponent")).isTrue();
} }
@Test @Test
@ -113,8 +132,9 @@ class BeanDefinitionLoaderTests {
@Test @Test
void loadPackageName() { void loadPackageName() {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyComponent.class.getPackage().getName()); BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyComponent.class.getPackage().getName());
assertThat(loader.load()).isEqualTo(1); assertThat(loader.load()).isEqualTo(2);
assertThat(this.registry.containsBean("myComponent")).isTrue(); assertThat(this.registry.containsBean("myComponent")).isTrue();
assertThat(this.registry.containsBean("myNamedComponent")).isTrue();
} }
@Test @Test
@ -131,8 +151,9 @@ class BeanDefinitionLoaderTests {
void loadPackageAndClassDoesNotDoubleAdd() { void loadPackageAndClassDoesNotDoubleAdd() {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyComponent.class.getPackage(), BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyComponent.class.getPackage(),
MyComponent.class); MyComponent.class);
assertThat(loader.load()).isEqualTo(1); assertThat(loader.load()).isEqualTo(2);
assertThat(this.registry.containsBean("myComponent")).isTrue(); assertThat(this.registry.containsBean("myComponent")).isTrue();
assertThat(this.registry.containsBean("myNamedComponent")).isTrue();
} }
} }

@ -0,0 +1,27 @@
/*
* Copyright 2012-2020 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.sampleconfig;
import javax.inject.Named;
@Named
public class MyNamedComponent {
MyNamedComponent() {
}
}
Loading…
Cancel
Save