Skip to content

Commit 6e02049

Browse files
committed
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
2 parents a0518d3 + c11abf4 commit 6e02049

File tree

3 files changed

+73
-18
lines changed

3 files changed

+73
-18
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BeanDefinitionLoader.java

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.boot;
1818

1919
import java.io.IOException;
20+
import java.lang.reflect.Constructor;
2021
import java.util.HashSet;
2122
import java.util.Set;
2223

@@ -31,8 +32,6 @@
3132
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
3233
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
3334
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
34-
import org.springframework.core.annotation.MergedAnnotations;
35-
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
3635
import org.springframework.core.env.ConfigurableEnvironment;
3736
import org.springframework.core.io.ClassPathResource;
3837
import org.springframework.core.io.Resource;
@@ -41,9 +40,9 @@
4140
import org.springframework.core.io.support.ResourcePatternResolver;
4241
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
4342
import org.springframework.core.type.filter.TypeFilter;
44-
import org.springframework.stereotype.Component;
4543
import org.springframework.util.Assert;
4644
import org.springframework.util.ClassUtils;
45+
import org.springframework.util.ObjectUtils;
4746
import org.springframework.util.StringUtils;
4847

4948
/**
@@ -53,6 +52,7 @@
5352
* {@link SpringApplication} for the types of sources that are supported.
5453
*
5554
* @author Phillip Webb
55+
* @author Vladislav Kisel
5656
* @see #setBeanNameGenerator(BeanNameGenerator)
5757
*/
5858
class BeanDefinitionLoader {
@@ -153,7 +153,7 @@ private int load(Class<?> source) {
153153
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
154154
load(loader);
155155
}
156-
if (isComponent(source)) {
156+
if (isEligible(source)) {
157157
this.annotatedReader.register(source);
158158
return 1;
159159
}
@@ -273,16 +273,23 @@ private Package findPackage(CharSequence source) {
273273
return Package.getPackage(source.toString());
274274
}
275275

276-
private boolean isComponent(Class<?> type) {
277-
// This has to be a bit of a guess. The only way to be sure that this type is
278-
// eligible is to make a bean definition out of it and try to instantiate it.
279-
if (MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class)) {
280-
return true;
281-
}
282-
// Nested anonymous classes are not eligible for registration, nor are groovy
283-
// closures
284-
return !type.getName().matches(".*\\$_.*closure.*") && !type.isAnonymousClass()
285-
&& type.getConstructors() != null && type.getConstructors().length != 0;
276+
/**
277+
* Check whether the bean is eligible for registration.
278+
* @param type candidate bean type
279+
* @return true if the given bean type is eligible for registration, i.e. not a groovy
280+
* closure nor an anonymous class
281+
*/
282+
private boolean isEligible(Class<?> type) {
283+
return !(type.isAnonymousClass() || isGroovyClosure(type) || hasNoConstructors(type));
284+
}
285+
286+
private boolean isGroovyClosure(Class<?> type) {
287+
return type.getName().matches(".*\\$_.*closure.*");
288+
}
289+
290+
private boolean hasNoConstructors(Class<?> type) {
291+
Constructor<?>[] constructors = type.getDeclaredConstructors();
292+
return ObjectUtils.isEmpty(constructors);
286293
}
287294

288295
/**

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/BeanDefinitionLoaderTests.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import sampleconfig.MyComponentInPackageWithoutDot;
2323

2424
import org.springframework.boot.sampleconfig.MyComponent;
25+
import org.springframework.boot.sampleconfig.MyNamedComponent;
2526
import org.springframework.context.support.StaticApplicationContext;
2627
import org.springframework.core.io.ClassPathResource;
2728

@@ -31,6 +32,7 @@
3132
* Tests for {@link BeanDefinitionLoader}.
3233
*
3334
* @author Phillip Webb
35+
* @author Vladislav Kisel
3436
*/
3537
class BeanDefinitionLoaderTests {
3638

@@ -53,6 +55,22 @@ void loadClass() {
5355
assertThat(this.registry.containsBean("myComponent")).isTrue();
5456
}
5557

58+
@Test
59+
void anonymousClassNotLoaded() {
60+
MyComponent myComponent = new MyComponent() {
61+
62+
};
63+
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, myComponent.getClass());
64+
assertThat(loader.load()).isEqualTo(0);
65+
}
66+
67+
@Test
68+
void loadJsr330Class() {
69+
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyNamedComponent.class);
70+
assertThat(loader.load()).isEqualTo(1);
71+
assertThat(this.registry.containsBean("myNamedComponent")).isTrue();
72+
}
73+
5674
@Test
5775
void loadXmlResource() {
5876
ClassPathResource resource = new ClassPathResource("sample-beans.xml", getClass());
@@ -83,8 +101,9 @@ void loadGroovyResourceWithNamespace() {
83101
@Test
84102
void loadPackage() {
85103
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyComponent.class.getPackage());
86-
assertThat(loader.load()).isEqualTo(1);
104+
assertThat(loader.load()).isEqualTo(2);
87105
assertThat(this.registry.containsBean("myComponent")).isTrue();
106+
assertThat(this.registry.containsBean("myNamedComponent")).isTrue();
88107
}
89108

90109
@Test
@@ -113,8 +132,9 @@ void loadGroovyName() {
113132
@Test
114133
void loadPackageName() {
115134
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyComponent.class.getPackage().getName());
116-
assertThat(loader.load()).isEqualTo(1);
135+
assertThat(loader.load()).isEqualTo(2);
117136
assertThat(this.registry.containsBean("myComponent")).isTrue();
137+
assertThat(this.registry.containsBean("myNamedComponent")).isTrue();
118138
}
119139

120140
@Test
@@ -131,8 +151,9 @@ void loadPackageNameWithoutDot() {
131151
void loadPackageAndClassDoesNotDoubleAdd() {
132152
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, MyComponent.class.getPackage(),
133153
MyComponent.class);
134-
assertThat(loader.load()).isEqualTo(1);
154+
assertThat(loader.load()).isEqualTo(2);
135155
assertThat(this.registry.containsBean("myComponent")).isTrue();
156+
assertThat(this.registry.containsBean("myNamedComponent")).isTrue();
136157
}
137158

138159
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.sampleconfig;
18+
19+
import javax.inject.Named;
20+
21+
@Named
22+
public class MyNamedComponent {
23+
24+
MyNamedComponent() {
25+
}
26+
27+
}

0 commit comments

Comments
 (0)