Antonio
Antonio

Reputation: 369

Working with properties in Spring to make a list of objects

I'm working with properties in Spring and I have a doubt about the Properties

Valores.properties

estudiante.nombre=Antonio, Juan , Maria, Raquel
estudiante.edad=28,20,21,23

Now I have a class to develop the bean

public class Estudiante {

    public Estudiante() {
    }

    public Estudiante(String nombre, Integer edad) {
        super();
        this.nombre = nombre;
        this.edad = edad;
    }

    @Value("${estudiante.nombre}")
    private String nombre;

    @Value("${estudiante.edad}")  
    private Integer edad;

    public Integer getEdad() {      
        return edad;
    }

    public void setEdad(Integer edad) {     
        this.edad = edad;
    }

    public String getNombre() {     
        return nombre;
    }

    public void setNombre(String nombre) {  
        this.nombre = nombre;
    }

    @Override
    public String toString() {
        return '\n' +"Estudiante{" + "nombre=" + nombre + ", edad=" + edad + '}';
    }

}

A java class to make the configuration

@Configuration
@PropertySource(value="classpath:valores.properties")
public class AppConfig {

    @Value("#{'${estudiante.nombre}'.split(',')}")
    private List<String> nombres;
    @Value("#{'${estudiante.edad}'.split(',')}")    
    private List<Integer> edades;

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    public List<String> getNombres() {
        return nombres;
    }


    public List<Integer> getEdades() {
        return edades;
    }

    public List<Estudiante> getListaStudents() {

        List<Estudiante> listaStudents = new ArrayList<>();

        for (int i= 0;i< nombres.size();i++){
            listaStudents.add(new Estudiante(nombres.get(i),edades.get(i)));
        }

        return listaStudents;
    }

}

And the main java class

public class Principal {

    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        AppConfig appConfig = context.getBean(AppConfig.class);
        System.out.println(appConfig.getListaStudents());
        ((ConfigurableApplicationContext)context).close();
    }

}

The program works and the output is OK

[
Estudiante{nombre=Antonio, edad=28}, 
Estudiante{nombre= Juan , edad=20}, 
Estudiante{nombre= Maria, edad=21}, 
Estudiante{nombre= Raquel, edad=23}]

But I don't know if it is the right way to develop. I dón't like to build a method getListaStudents() in the AppConfig class to make a list of objets and I don't like to use the new() method in Spring

I think it's not a good idea but I don't know other way to solve. Any solution or any idea?

Thanks in advance

Upvotes: 5

Views: 10511

Answers (2)

Antonio
Antonio

Reputation: 369

Uooohh.. It's a good solution. Thanks for your time Bunti (and thanks to the revisions of other users)

I have develop a solution with your advices, buy I have a problem to catch and split the values of the properties File.

The new class (with the advices) is here

@Configuration
@PropertySource(value="classpath:valores.properties")
public class AppConfig {

    @Resource(name = "getStudents")
    private List<Estudiante> estudiantes;

    public List<Estudiante> getEstudiantes() {  
        return estudiantes;
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public ConversionService conversionService() {
        return new DefaultConversionService();
    }

    @Bean
    public List<Estudiante> getStudents(
        @Value("${estudiante.nombre}")List<String> nombres,
        @Value("${estudiante.edad}") List<Integer> numbers
        ) {

        List<Estudiante> listaStudents = new ArrayList<>();
        for (int i= 0;i< nombres.size();i++){
            listaStudents.add(new Estudiante(nombres.get(i),numbers.get(i)));
            //listaStudents.add(new Estudiante(nombres.get(i)));
        }
        return listaStudents;
    }
}

Running, I have several exception but I think It is the same problem

With only catch the String values of the Property Files (in this example the values from name) the system write

[Estudiante{nombre=Antonio, Juan , Maria, Raquel, edad=null}]

To allow this output, I have commented all the references to Integer Values (so, the age/edad is null... no problem)

Really, it doesn`t split the properties File and the string values is "a,b,c,d..." (and the size is 1)

So, when I use the Properties File

estudiante.nombre=Antonio, Juan , Maria, Raquel
estudiante.edad=28,20,21,23

and catch String and Integer there is an exception

Exception encountered during context initialization - cancelling refresh attempt
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'appConfig': 
Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'getStudents' defined in test.AppConfig: 
Unsatisfied dependency expressed through constructor argument with index 1 of type [java.util.List]: : 
Failed to convert value of type 'java.lang.String' to required type 'java.util.List'; nested exception is java.lang.NumberFormatException: 
For input string: "28,20,21,23"; nested exception is org.springframework.beans.TypeMismatchException: 
Failed to convert value of type 'java.lang.String' to required type 'java.util.List'; 
nested exception is java.lang.NumberFormatException: For input string: "28,20,21,23"

The exception is logical. I expect a number to parse... but i catch "28,20,21,23"... and not convert into a number.

So, the converts method understands a list of values separated by , ?

Upvotes: 0

Bunti
Bunti

Reputation: 1760

I think this is a proper approach with few improvements, but depending on the data size and requirements you may not want to load data from property files. Property files are usually used for configuration options such as database configurations for different environments, caching configurations etc..

In your AppConfig, you don't need to use SpringEL to parse a List of Integers or Strings like this,

@Value("#{'${estudiante.nombre}'.split(',')}")
private List<String> nombres;

I would suggest to use something like this instead,

@Value("${estudiante.edad}") List<Integer> nombres

but for this to work you need to have one addtional bean configuration for Spring's ConversionService

@Bean
public ConversionService conversionService() {
    return new DefaultConversionService();
}

This way Spring's conversion service will have default converters to convert a list of Strings or Integers so that you can avoid somewhat less readable #{'${estudiante.edad}'.split(',')} in @Value annotation.

Now you can use above @Value to be used directly in a new bean to create a set of Students like this.

@Bean
public List<Estudiante> getStudents(
        @Value("${estudiante.edad}") List<Integer> numbers,
        @Value("${estudiante.nombre}") List<String> names) {

    List<Estudiante> listaStudents = new ArrayList<Estudiante>();
    for (int i= 0;i< numbers.size();i++){
        listaStudents.add(new Estudiante(names.get(i), numbers.get(i)));
    }

    return listaStudents;
}

and you can inject list of Estudiantes with @Resource,

@Resource(name = "getStudents")
private List<Estudiante> estudiantes;

I don't see anything wrong with creating Estudiante objects with new keyword. Just as we use new in other methods that are annotated with @Bean this is a perfectly valid scenario. If you want, for whatever reason to avoid new in creating Estudiante, you can inject ApplicationContext and get Estudiante with Estudiante studiante = applicationContext.getBean(Estudiante.class); and don' forget to mark Estudiante class with @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

I dón't like to build a method getListaStudents() in the AppConfig class to make a list of objects

As far as I know there is no simple and proper way to create a List of Estudiante objects as they need to have their own values. You can make student creation to its own configuration class such as EstudianteConfiguration and import it to your main AppConfig class with @Import annotation.

Upvotes: 2

Related Questions