Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Spring component scan for beans with no no-args constructor

DZone's Guide to

Spring component scan for beans with no no-args constructor

· Java Zone
Free Resource

Just released, a free O’Reilly book on Reactive Microsystems: The Evolution of Microservices at Scale. Brought to you in partnership with Lightbend.

Suppose you have the following spring set-up 

  • component scan enabled
  • a class (MailServer) with
    • some constructors (no no-args constructor)
    • annotated with @Component (or @Service / @Named)
  • declaration of above class in the spring-config
    • (Even though you have component-scan enabled, suppose you need to configure the default mail server with a name "defaultMailServer" )
  • injecting the above "defaultMailServer" to a property of another class (i.e. MailSender)


Spring Configuration

 <bean id="defaultMailServer" class="me.fahimfarook.mail.MailServer"  scope="singleton">  
     <constructor-arg name="host" value="smtp.gmail.com" />  
     <constructor-arg name="port" value="587" />  
     <constructor-arg name="protocol" value="smtp" />  
 </bean>  
 <context:component-scan base-package="me.fahimfarook.mail" />  


MailServer.java

 @Component  
 public class MailServer {  
    private String host;   
    private int port;   
    private String protocol;  

    public MailServer(final String host, final int port, final String protocol) {  
       this.host = host;  
       this.port = port;  
       this.protocol = protocol;  
    }  

    //getters  
 }  


MailSender.java

@Service  
 public class MailSenderImpl implements MailSender {  

    @Autowired  
    @Qualifier("defaultMailServer")  
    private MailServer mailServer;  

    @Override  
    public void send() {  
      // TODO  
    }  
 }  


Main.java

public class Main {  
    private static final String CONFIG_PATH = "classpath*:application-config.xml";  

    public static void main(final String[] args) {  
       final ApplicationContext context = new ClassPathXmlApplicationContext(CONFIG_PATH);  
       final MailSender mailSender = context.getBean(MailSenderImpl.class);  
       mailSender.send();  
    }  
 }  


Nothing looks wrong. But when you execute the program you will get the following exception.

org.springframework.beans.BeanInstantiationException: 
Could not instantiate bean class [me.fahimfarook.mail.MailServer]: No default constructor found;  


This error message is misleading because we are not accessing MailServer with default constructor anywhere in our code. We have injected "defaultMailServer" which is properly defined with constructor-args.

However, if you add a no-args constructor to MailServer this exception will go away. That means spring context is trying to create a MailServer object using no-args constructor somewhere even though we have defined a MailServer with constructor-args. But we have defined a MailServer bean with constructor-args and accessing that bean only?

The reason for that is - we have defined a bean explicitly in the spring config xml while we have enabled component scanning. Spring will create the "defaultMailServer" bean successfully since constructor-args have been defined in the spring config xml. However, since component scanning has been enabled, spring will try to automatically discover and register another MailServer bean. However, this will fail because spring can't create an object using the defined constructor as we have not auto-wired the constructor / parameters in the constructor. As the second step, spring will try to create a MailServer with no-args constructor.

Therefore you need to have auto-wired the constructor which you defined in the spring-config file in order to get rid of this error.

MailServer.java - Fixed

@Component  
 public class MailServer {  
      private String host;  
      private int port;  
      private String protocol;

      @Autowired  
      public MailServer(@Value("")final String host, @Value("#{new Integer(-1)}")final int port, @Value("")final String protocol) {   
           this.host = host;  
           this.port = port;  
           this.protocol = protocol;  
      }  
 }  


Now that you have auto-wired the constructor which is defined with "defaultMailServer", spring can create this bean and it will not demand a default constructor. However, if spring had thrown BeanInstantiationException with a message like "Constructor for defaultMailServer could not be found" instead of "No default constructor found", the exact issue with the code could have been identified easily.


In summary:

  • If component scanning is enabled, spring will try to create a bean even though a bean of that class has already been defined in the spring config xml.
  • However if the bean defined in the spring config file and the auto-discovered bean have the same name, spring will not to create a new bean while it does component scanning.
  • If a bean does not have a no-args constructor, at-least one of the constructors must be auto-wired.
  • If no constructor is auto-wired, spring will try to create an object using default no-args constructor.
Original post  here  

Strategies and techniques for building scalable and resilient microservices to refactor a monolithic application step-by-step, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:
java ,high-perf ,spring ,tips and tricks ,component-scan

Published at DZone with permission of Fahim Farook. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}