Browse Source

Automatically autowire a bean with one constructor

Previously, if a managed bean had only one non-default constructor, we
should still annotate it with `@Autowired` to properly use constructor
injection. Not doing so resulted in an error as the container was
trying to call the default (non-existing) constructor.

This commit updates this behaviour to automatically applyed the
autowiring semantic to any bean that has only one constructor. As
before, if more than one constructor is defined, `@Autowired` must be
specified to teach the container the constructor it has to use.

Issue: SPR-12278
pull/938/head
Stephane Nicoll 9 years ago
parent
commit
9e7c791a0f
  1. 6
      spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
  2. 115
      spring-context/src/test/java/org/springframework/context/annotation/Spr12278Tests.java
  3. 18
      spring-context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java

6
spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 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.
@ -107,6 +107,7 @@ import org.springframework.util.StringUtils;
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Mark Fisher * @author Mark Fisher
* @author Stephane Nicoll
* @since 2.5 * @since 2.5
* @see #setAutowiredAnnotationType * @see #setAutowiredAnnotationType
* @see Autowired * @see Autowired
@ -312,6 +313,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
} }
candidateConstructors = candidates.toArray(new Constructor<?>[candidates.size()]); candidateConstructors = candidates.toArray(new Constructor<?>[candidates.size()]);
} }
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterTypes().length > 0) {
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
}
else { else {
candidateConstructors = new Constructor<?>[0]; candidateConstructors = new Constructor<?>[0];
} }

115
spring-context/src/test/java/org/springframework/context/annotation/Spr12278Tests.java

@ -0,0 +1,115 @@
/*
* Copyright 2002-2015 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
*
* http://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.context.annotation;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanCreationException;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
/**
* @author Stephane Nicoll
*/
public class Spr12278Tests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private AnnotationConfigApplicationContext context;
@After
public void close() {
if (context != null) {
context.close();
}
}
@Test
public void componentSingleConstructor() {
this.context = new AnnotationConfigApplicationContext(BaseConfiguration.class,
SingleConstructorComponent.class);
assertThat(this.context.getBean(SingleConstructorComponent.class).autowiredName, is("foo"));
}
@Test
public void componentTwoConstructorsNoHint() {
this.context = new AnnotationConfigApplicationContext(BaseConfiguration.class,
TwoConstructorsComponent.class);
assertThat(this.context.getBean(TwoConstructorsComponent.class).name, is("fallback"));
}
@Test
public void componentTwoSpecificConstructorsNoHint() {
thrown.expect(BeanCreationException.class);
thrown.expectMessage(NoSuchMethodException.class.getName());
new AnnotationConfigApplicationContext(BaseConfiguration.class,
TwoSpecificConstructorsComponent.class);
}
@Configuration
static class BaseConfiguration {
@Bean
public String autowiredName() {
return "foo";
}
}
private static class SingleConstructorComponent {
private final String autowiredName;
// No @Autowired - implicit wiring
public SingleConstructorComponent(String autowiredName) {
this.autowiredName = autowiredName;
}
}
private static class TwoConstructorsComponent {
private final String name;
public TwoConstructorsComponent(String name) {
this.name = name;
}
public TwoConstructorsComponent() {
this("fallback");
}
}
private static class TwoSpecificConstructorsComponent {
private final Integer counter;
public TwoSpecificConstructorsComponent(Integer counter) {
this.counter = counter;
}
public TwoSpecificConstructorsComponent(String name) {
this(Integer.valueOf(name));
}
}
}

18
spring-context/src/test/java/org/springframework/context/annotation/configuration/AutowiredConfigurationTests.java

@ -89,19 +89,17 @@ public class AutowiredConfigurationTests {
assertThat(context.getBean(TestBean.class).getName(), equalTo("")); assertThat(context.getBean(TestBean.class).getName(), equalTo(""));
} }
/** @Test
* {@link Autowired} constructors are not supported on {@link Configuration} classes public void testAutowiredConfigurationConstructorsAreSupported() {
* due to CGLIB constraints DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
*/ new XmlBeanDefinitionReader(factory).loadBeanDefinitions(
@Test(expected = BeanCreationException.class)
public void testAutowiredConfigurationConstructorsAreNotSupported() {
DefaultListableBeanFactory context = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(context).loadBeanDefinitions(
new ClassPathResource("annotation-config.xml", AutowiredConstructorConfig.class)); new ClassPathResource("annotation-config.xml", AutowiredConstructorConfig.class));
GenericApplicationContext ctx = new GenericApplicationContext(context); GenericApplicationContext ctx = new GenericApplicationContext(factory);
ctx.registerBeanDefinition("config1", new RootBeanDefinition(AutowiredConstructorConfig.class)); ctx.registerBeanDefinition("config1", new RootBeanDefinition(AutowiredConstructorConfig.class));
ctx.registerBeanDefinition("config2", new RootBeanDefinition(ColorConfig.class)); ctx.registerBeanDefinition("config2", new RootBeanDefinition(ColorConfig.class));
ctx.refresh(); // should throw ctx.refresh();
assertSame(ctx.getBean(AutowiredConstructorConfig.class).colour,
ctx.getBean(Colour.class));
} }
@Test @Test

Loading…
Cancel
Save