Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a no-args constructor needed to run this simple Spring configuration?

I was trying to follow a tutorial over at Mkyong regarding Spring. I created two simple classes, Customer and Person, which look like so:

Customer:

public class Customer {

    private Person person;

    public Customer() {
    }

    //Getters and setters for person here

    public String toString() {
        return "Customer [person=" + person +"]";
    }
}

Person:

public class Person {

    private String name;
    private String address;
    private int age;

    public Person() {

    }

    public Person(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }

    //All getters and setters

    public String toString() {
        return "Person [address=" + address + ", age=" + age + ", name=" + name + "]";
    }
}

I then created a Bean configuration file, using an inner bean (which is why I was taking the tutorial) that looks like so:

// Standard bean declarations

    <bean id="CustomerBean" class="com.andrew.SpringInnerBeans.Customer">
        <property name="person">
            <bean class="com.andrew.SpringInnerBeans.Person">
                <property name="name" value="Andrew" />
                <property name="address" value="Address" />
                <property name="age" value="27" />
            </bean>
        </property>
    </bean>
</beans>

Finally, I created a MainApp to run this code:

public static void main (String[] args) {

    ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {
            "NewBean.xml"});

    Customer cust = (Customer) context.getBean("CustomerBean");
    System.out.println(cust);

}

Now, to my understanding, this is what is happening:

The xml file is loaded and the bean with id CustomerBean is stored in the reference cust (which is a Customer object). CustomerBean takes an argument, "person", and the details of the three parameters which create the Person are supplied as the inner bean of customerBean.

In the MainApp, the toString method of the reference cust is called and the proper output is displayed.

As I am specifically providing references in my examples, why is a no argument constructor required to run this code? If I remove either the empty Customer or empty Person constructors, the code fails. Why does it fail, at run-time, when the bean I've set up doesn't require a no argument constructor?

Edit:

Example of code pass:

Customer [person=Person [address=4 Ascot House, age=27, name=Andrew]]

Example of code fail:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'CustomerBean' defined in class path resource [NewBean.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.andrew.SpringInnerBeans.Customer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.andrew.SpringInnerBeans.Customer.<init>()
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1040)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:505)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:725)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:93)
    at com.andrew.SpringInnerBeans.MainApp.main(MainApp.java:10)
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.andrew.SpringInnerBeans.Customer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.andrew.SpringInnerBeans.Customer.<init>()
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:85)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1088)
    ... 13 more
Caused by: java.lang.NoSuchMethodException: com.andrew.SpringInnerBeans.Customer.<init>()
    at java.lang.Class.getConstructor0(Unknown Source)
    at java.lang.Class.getDeclaredConstructor(Unknown Source)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:80)
    ... 14 more

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'CustomerBean' defined in class path resource [NewBean.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.andrew.SpringInnerBeans.Customer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.andrew.SpringInnerBeans.Customer.<init>()
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1040)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:505)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:725)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:93)
    at com.andrew.SpringInnerBeans.MainApp.main(MainApp.java:10)
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.andrew.SpringInnerBeans.Customer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.andrew.SpringInnerBeans.Customer.<init>()
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:85)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1088)
    ... 13 more
Caused by: java.lang.NoSuchMethodException: com.andrew.SpringInnerBeans.Customer.<init>()
    at java.lang.Class.getConstructor0(Unknown Source)
    at java.lang.Class.getDeclaredConstructor(Unknown Source)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:80)
    ... 14 more
like image 576
Andrew Martin Avatar asked Sep 19 '14 13:09

Andrew Martin


2 Answers

Well look again at what you are asking from the Spring container point of view.

  • found a bean of class Customer, with only properties : ok create it as new Customer() using the no-arg constructor and keep it for the moment.
  • found a bean of class Person with only properties : ok create it as new Person(), still using the no-arg constructor, give it an arbitrary name and keep it for the moment
  • populate the Person bean with its properties using the setters : ok it is ready
  • populate the Customer bean with its only property using the setter and the Person bean as parameter : ok it is ready
  • fine, application context is now fully populated

So you get the beans from the application context and have no new in your own code, but Spring has constructed them for you, and did use the no-arg constructors.

You could instead have for example :

public class Customer {

    private Person person;

    public Customer(Person person) {
        this.person = person;
    }
    ...
}

and your XML would have to be :

<bean id="CustomerBean" class="com.andrew.SpringInnerBeans.Customer">
    <constructor-arg>
        <bean class="com.andrew.SpringInnerBeans.Person">
            <property name="name" value="Andrew" />
            <property name="address" value="Address" />
            <property name="age" value="27" />
        </bean>
    </constructor-arg>
</bean>

That way, Spring would have first constructed Person bean as above and would have created Customer bean as new Customer(person) using the one arg constructor.

like image 165
Serge Ballesta Avatar answered Sep 22 '22 05:09

Serge Ballesta


because it uses fields injection instead your Person(String name, String address, int age) constructor. Please try something like:

 ... 
<property name="person">
        <bean class="com.andrew.SpringInnerBeans.Person">
            <constructor-arg index="0" value="Andrew"/>
            <constructor-arg index="1" value="Address"/>
            <constructor-arg index="2" value="27"/>
       </bean>
 </property>
... 

instead. Thats solution for Person class, for Customer - I dunno ; )

like image 44
hi_my_name_is Avatar answered Sep 23 '22 05:09

hi_my_name_is