Correctness27 - SpotBugsExtensionForSpringFrameWork/CS5098 GitHub Wiki

By referring this, I realized that this coding error is not good for use.

Bug pattern name: MULTIPLE_AUTOWIRED_CONSTRUCTOR Short description: @Autowired annotation can be applied to only one of the constructor methods, when multiple constructors are used

Description

"The @Autowired annotation can be applied to only one of the constructor methods. If we apply the annotation to more than one constructor method, Spring will complain while bootstrapping ApplicationContext."

@Service("constructorConfusion")
public class ConstructorConfusion {
    // @Autowired -> no!!
    public ConstructorConfusion(String someValue) {
    }
    @Autowired 
    public ConstructorConfusion(@Value("90") int someValue) {
    }
}
//Sample Code To Test 
public class ConstructorMain {
    public static void main(String[] args) throws ClassNotFoundException {
        GenericApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        BeanA bean = (BeanA) ctx.getBean("beanA");
    }
}
//--------------------------------------------
@Configuration
@ComponentScan(basePackages = "com.example.myspringdemo1.constructor")
class AppConfig {}
//--------------------------------------------
@Component
public class BeanB {}
//--------------------------------------------
@Component
public class BeanC {}
//--------------------------------------------
@Component
public class BeanD {}
//--------------------------------------------
@Component
public class BeanA {
    BeanB beanB;
    BeanC beanC;
    BeanD beanD;

    @Autowired
    public BeanA(BeanB beanB) {
        this.beanB = beanB;
        System.out.println("constructor 1");
    }

    @Autowired
    public BeanA(BeanC beanC) {
        this.beanC = beanC;
        System.out.println("constructor 2");
    }

    @Autowired
    public BeanA(BeanD beanB) {
        this.beanD = beanD;
        System.out.println("constructor 3");
    }
}
//--------------------------------------------
// error message
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanA': Invalid autowire-marked constructor: public com.example.myspringdemo1.constructor.BeanA(com.example.myspringdemo1.constructor.BeanC). Found constructor with 'required' Autowired annotation already: public com.example.myspringdemo1.constructor.BeanA(com.example.myspringdemo1.constructor.BeanB)

Theory

The required attribute in @Autowried is set true by default (see Autowired annotation below) and Spring doc stated "Only one constructor of any given bean class may declare @Autowired with the required attribute set to true, ~" (Spring 5 docs)", i.e., if multiple constructors are used, others except for the required constructor should be set false.

// Spring Code
// @Autowired annotation 
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
	boolean required() default true; 
}
// Spring Code
// In AutowiredAnnotationBeanPostProcessor class,
// All constructors become candidate
// If there are more than one required Constructor, throw BeanCreationException
@Nullable
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeanCreationException {
//...
    List<Constructor<?>> candidates = new ArrayList(rawCandidates.length);
//...
    while (true) {
        //...
        if (requiredConstructor == null) {
            if (defaultConstructor != null) {
                candidates.add(defaultConstructor);
            } else if (candidates.size() == 1 && this.logger.isInfoEnabled()) {
                this.logger.info("Inconsistent constructor declaration on bean with name '" + beanName + "': single autowire-marked constructor flagged as optional - this constructor is effectively required since there is no default constructor to fall back to: " + candidates.get(0));
            }
        }
        //...
    }

//...
    if (requiredConstructor != null) {
        throw new BeanCreationException(beanName, "Invalid autowire-marked constructor: " + candidate + ". Found constructor with 'required' Autowired annotation already: " + requiredConstructor);
    }
}

Spring docs extraction of full description

  • Only one constructor of any given bean class may declare @Autowired with the required attribute set to true, indicating the constructor to autowire when used as a Spring bean. As a consequence, if the required attribute is left at its default value true, only a single constructor may be annotated with @Autowired. If multiple constructors declare the annotation, they will all have to declare required=false in order to be considered as candidates for autowiring (analogous to autowire=constructor in XML). The constructor with the greatest number of dependencies that can be satisfied by matching beans in the Spring container will be chosen. If none of the candidates can be satisfied, then a primary/default constructor (if present) will be used. Similarly, if a class declares multiple constructors but none of them is annotated with @Autowired, then a primary/default constructor (if present) will be used. If a class only declares a single constructor to begin with, it will always be used, even if not annotated. Note that an annotated constructor does not have to be public." Spring 5 docs

  • "As of Spring Framework 4.3, an @Autowired annotation on such a constructor is no longer necessary if the target bean defines only one constructor to begin with. However, if several constructors are available and there is no primary/default constructor, at least one of the constructors must be annotated with @Autowired in order to instruct the container which one to use." (Spring Document)

  • Regarding the default constructor: You either need the default constructor, a constructor with the @Autowired annotation when you have multiple constructors, or only one constructor in your class with or without the @Autowired annotation.(StackOverflow)

Solution

https://stackoverflow.com/questions/24723403/spring-3-2-annotation-autowiring-with-multiple-constructors

Reference List