2

I have an entity:

public class Auditorium {
    private Integer id;
    private String name;
    private Integer numberOfSeats;
    private List<String> vipSeats;

    public Auditorium(String name, Integer numberOfSeats, List<String> vipSeats) {
        this.name = name;
        this.numberOfSeats = numberOfSeats;
        this.vipSeats = vipSeats;
    }

and I want to create Auditorium instance from property file, like:

auditorium1.name=yellow hall
auditorium1.number-of-seats=150
auditorium1.vip-seats=1,2,3,4,5,6,7,8,9

I have few different files and I want to create different Auditorium beans.

Here is snippet from spring.xml:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:auditorium1.properties</value>
            <value>classpath:auditorium2.properties</value>
            <value>classpath:auditorium3.properties</value>
        </list>
    </property>
    <property name="ignoreResourceNotFound" value="true"/>
    <property name="systemPropertiesMode">
        <util:constant
                static-field="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
    </property>
</bean>

<util:list id="auditorium1" value-type="java.lang.String">
    <value></value>
</util:list>

<util:list>
    <bean class="net.lelyak.edu.entity.Auditorium"
          p:name="${auditorium1.name}" p:numberOfSeats="${auditorium1.number-of-seats}" p:vipSeats="${auditorium1.vip-seats}"/>
</util:list>

But Iam getting an error:

enter image description here

Because No matching constructor found.

And much better will have List<Integer> vipSeats. Is it possible at this situation?

How to solve this trouble?

catch23
  • 17,519
  • 42
  • 144
  • 217

1 Answers1

1

There are 2 aspects here to be covered, so let's take them one at a time:

1)IJ (short for IntelliJ Idea) is suggesting that according to your bean declaration, Spring will be trying to invoke a no-arg constructor, which clearly does not exist since you've defined public Auditorium(String name, Integer numberOfSeats, List<String> vipSeats).

Thus you can configure it to call the above mentioned constructor passing the correct arguments, as per the spring docs:

<bean class="com.example.Auditorium">
    <constructor-arg name="name" value="${auditorium1.name}"/>
    <constructor-arg name="numberOfSeats" value="${auditorium1.number-of-seats}"/>
    <constructor-arg name="vipSeats" value="${auditorium1.vip-seats}"/>
</bean>

Apart from this, no special handling is required for Spring to inject your list, as you can see in the log snippet below, outputted by a simple toString() IJ auto-generated method:

16:13:56.569 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'com.example.Auditorium#0'
16:13:56.601 [main] DEBUG com.example.Auditorium - Auditorium{id=null, name='yellow hall', numberOfSeats=150, vipSeats=[1,2,3,4,5,6,7,8,9]}



2) I see you're trying to use the p-namespace to pass arguments to your constructor, however that's used to set properties/attribute after bean instantiation. You can use the c-namespace to achieve the same effect as using the <constructor-arg> tag and be less verbose (please note that as per the spring docs, p & c namespaces the are not defined in an XSD file and exist only in the core of Spring):

<beans ...
       xmlns:c="http://www.springframework.org/schema/c"
       ...>

    <bean id="myAuditorium" class="com.example.Auditorium" c:name="${auditorium1.name}" c:numberOfSeats="${auditorium1.number-of-seats}" c:vipSeats="${auditorium1.vip-seats}"/>

And again the log snippet, generated for both the definitions this time:

16:26:52.258 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'com.example.Auditorium#0'
16:26:52.287 [main] DEBUG com.example.Auditorium - Auditorium{id=null, name='yellow hall', numberOfSeats=150, vipSeats=[1,2,3,4,5,6,7,8,9]}
...
16:26:52.287 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'com.example.Auditorium#1'
16:26:52.288 [main] DEBUG com.example.Auditorium - Auditorium{id=null, name='yellow hall', numberOfSeats=150, vipSeats=[1,2,3,4,5,6,7,8,9]}



Note: Depending on the Spring version that you are using & your needs, it may be worth also checking annotation-based configs. It may be a personal preference, but I find them cleaner and easier to maintain, and IJ has excellent Spring support for both types of config.



Special request update: Brief annotation introduction

First of all it depends on your project's setup, whether you use basic Spring Core or Spring Boot as well, etc. For this introduction: I'll just start from where you are at this point:

Enable component scanning so Spring can discover and create your components, services, etc

<?xml version="1.0" encoding="UTF-8"?>
<beans ...
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="...
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.example" />

Then change our class. If you don't need to do anything else in your constructor then we can simply remove it and annotate the fields. When annotating fields please take into consideration that Spring will be able to populate them only after instantiating the beans, hence using then in a constructor will most likly result in a NPE. In such cases you can use @PostConstruct

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

@Component
public class Auditorium {
    private static final Logger log = LoggerFactory.getLogger(Auditorium.class);
    private Integer id;

    @Value("${auditorium1.name}")
    private String name;

    @Value("${auditorium1.number-of-seats}")
    private Integer numberOfSeats;

    @Value("${auditorium1.vip-seats}")
    private List<String> vipSeats;

    @PostConstruct
    private void doAfterConstruction() {
        log.debug(this.toString());
    }

    @Override
    public String toString() {
        return "Auditorium{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", numberOfSeats=" + numberOfSeats +
                ", vipSeats=" + vipSeats +
                '}';
    }
}

As you'll see in the log, Spring will instantiate the bean, then inject the values for its fields and eventually the post-processors will call the @PostConstruct annotated method:

12:20:34.037 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'auditorium'
12:20:34.038 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'auditorium'
12:20:34.045 [main] DEBUG o.s.c.a.CommonAnnotationBeanPostProcessor - Found init method on class [com.example.Auditorium]: private void com.example.Auditorium.doAfterConstruction()
12:20:34.045 [main] DEBUG o.s.c.a.CommonAnnotationBeanPostProcessor - Registered init method on class [com.example.Auditorium]: org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement@1ec4fbf0
12:20:34.060 [main] DEBUG o.s.b.f.annotation.InjectionMetadata - Registered injected element on class [com.example.Auditorium]: AutowiredFieldElement for private java.lang.String com.example.Auditorium.name
12:20:34.060 [main] DEBUG o.s.b.f.annotation.InjectionMetadata - Registered injected element on class [com.example.Auditorium]: AutowiredFieldElement for private java.lang.Integer com.example.Auditorium.numberOfSeats
12:20:34.060 [main] DEBUG o.s.b.f.annotation.InjectionMetadata - Registered injected element on class [com.example.Auditorium]: AutowiredFieldElement for private java.util.List com.example.Auditorium.vipSeats
12:20:34.060 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Eagerly caching bean 'auditorium' to allow for resolving potential circular references
12:20:34.062 [main] DEBUG o.s.b.f.annotation.InjectionMetadata - Processing injected element of bean 'auditorium': AutowiredFieldElement for private java.lang.String com.example.Auditorium.name
12:20:34.067 [main] DEBUG o.s.b.f.annotation.InjectionMetadata - Processing injected element of bean 'auditorium': AutowiredFieldElement for private java.lang.Integer com.example.Auditorium.numberOfSeats
12:20:34.072 [main] DEBUG o.s.b.f.annotation.InjectionMetadata - Processing injected element of bean 'auditorium': AutowiredFieldElement for private java.util.List com.example.Auditorium.vipSeats
12:20:34.084 [main] DEBUG o.s.c.a.CommonAnnotationBeanPostProcessor - Invoking init method on bean 'auditorium': private void com.example.Auditorium.doAfterConstruction()
12:20:34.085 [main] DEBUG com.example.Auditorium - Auditorium{id=null, name='yellow hall', numberOfSeats=150, vipSeats=[1,2,3,4,5,6,7,8,9]}
12:20:34.094 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'auditorium'

Or if you'd like to keep your constructor, we'll just mark it as @Autowired and annotate the parameters as well:

@Component
public class Auditorium {
    private static final Logger log = LoggerFactory.getLogger(Auditorium.class);
    private Integer id;
    private String name;
    private Integer numberOfSeats;
    private List<String> vipSeats;

    @Autowired
    public Auditorium(@Value("${auditorium1.name}") String name,
                      @Value("${auditorium1.number-of-seats}") Integer numberOfSeats,
                      @Value("${auditorium1.vip-seats}") List<String> vipSeats) {
        this.name = name;
        this.numberOfSeats = numberOfSeats;
        this.vipSeats = vipSeats;
        log.debug(this.toString());
    }
}

And the log, which is simpler this time

12:29:09.492 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'auditorium'
12:29:09.492 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'auditorium'
12:29:09.525 [main] DEBUG com.example.Auditorium - Auditorium{id=null, name='yellow hall', numberOfSeats=150, vipSeats=[1,2,3,4,5,6,7,8,9]}
12:29:09.526 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Eagerly caching bean 'auditorium' to allow for resolving potential circular references
12:29:09.548 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'auditorium'
Morfic
  • 15,178
  • 3
  • 51
  • 61
  • What does `IJ` mean? – catch23 Feb 10 '16 at 14:27
  • 1
    @nazar_art Good point. It's short for IntelliJ Idea, I recognized the theme from the XML definition screenshot. I'll just update the answer to make it clearer. Thanks – Morfic Feb 10 '16 at 14:51
  • Can you show an example how to use `Annotation approach` here. It will be very useful for me, coz I am using `Spring 3.2.13. – catch23 Feb 11 '16 at 09:36
  • @nazar_art this is to broad a subject to be covered on SO. Take a look at the [spring doc link](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-annotation-config) I had provided and other [similar tutorials](http://www.mkyong.com/spring/spring-auto-scanning-components/). They're better suited for the job and if you have trouble, then you'll surely find answers on SO. But just to get you started I added a short sample based on your original project. Cheers – Morfic Feb 11 '16 at 10:35
  • do you know how to save `vipSeats` exactly like Set. Because now it is saved like one string `1,2,3,4,5`? – catch23 Feb 13 '16 at 19:40
  • @nazar_art I apologise, it looks like some processing is indeed required, and the simplest solution seems to be SpEL (spring expression language), something along the lines of `"#{'${list.of.strings}'.split(',')}"`. More details [here](http://stackoverflow.com/questions/27390363/spring-how-to-inject-an-inline-list-of-strings-using-value-annotation) and [here](http://stackoverflow.com/questions/12576156/reading-a-list-from-properties-file-and-load-with-spring-annotation-value) – Morfic Feb 13 '16 at 19:46
  • How can I configure 3 different auditories if I have to set only specific one to my entity? Like `@Value("${auditorium1.name}") private String name;` – catch23 Feb 13 '16 at 20:00
  • @nazar_art The simplest solution which comes to mind is to define your own `@Configuration` with 3 different methods for creating `@Bean`. The spring docs have [a short intro](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-java-bean-annotation) and a more [detailed section](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-java-bean-annotation) on this matter. – Morfic Feb 15 '16 at 09:38
  • I have tried, and finally asked one more question - [How to configure Spring configuration for List of Beans from different property files?](http://stackoverflow.com/questions/35443942/how-to-configure-spring-configuration-for-list-of-beans-from-different-property) – catch23 Feb 16 '16 at 22:09