Home > database >  Why doesn't spring framework bind my YAML list to an entity?
Why doesn't spring framework bind my YAML list to an entity?

Time:12-06

I'm trying to bind a list of objects to my entity class using @ConfigurationProperties annotation. Spring Boot framework seems to ignore that annotation, and it literally does nothing.

Here is my application.yml properties file:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/search-engine
    username: landsreyk
    password: 12345678
  jpa:
    database-platform: org.hibernate.dialect.MySQLDialect
    show-sql: false
    hibernate:
      ddl-auto: none
sites:
  - url: http://someurl1.com
    name: somename1
  - url: https://someurl2.com
    name: somename2
  - url: https://someurl3.com
    name: somename3

And here is my entity class:

package main.model;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.*;
import java.sql.Timestamp;

@Getter
@Setter
@ToString
@Entity
@Table(name = "_site")
public class Site {
    @Column(nullable = false)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Enumerated(EnumType.STRING)
    @Column(name = "status")
    private Status status;

    @Column(name = "status_time")
    private Timestamp statusTime;

    @Column(name = "last_error")
    private String lastError;

    private String url;

    private String name;

    public enum Status {
        INDEXING, INDEXED, FAILED
    }
}

Binding is pretty straight forward:

package main.utilities;

import lombok.Getter;
import lombok.Setter;
import main.model.Site;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

@ConfigurationProperties
public class ApplicationProperties {

    @Getter
    @Setter
    private List<Site> sites;

}

Somewhere in application I'm testing that binding, let's say I created end point in my API to test that binding, i.e. my controller calls a method which just prints all objects in a list:

package main.application.indexer;

import main.utilities.ApplicationProperties;
import org.springframework.beans.factory.annotation.Autowired;

public class IndexBuilder {
    @Autowired
    private ApplicationProperties properties;

    public void run() {
        System.out.println(properties.getSites());
    }

}

Expected:

After launch, ApplicationProperties instance is not null. Calling properties.getSites() returns a list of Site objects. Each Site object has url and name fields initialized from yaml source.

Actual:

After launch ApplicationProperties instance is null.

I was shocked to realize that Spring wasn't able to accomplish such a simple binding. Knowing that just parsing a yaml file is not such a hard task to accomplish, I thought that Spring Framework should have this feature build-in. How do I bind my list?

By the way, here is my project structure.

enter image description here

UPDATE:

I edited IndexBuilder class. Added @Configuration and @Bean annotations. Now it looks like this:

package main.application.indexer;

import main.utilities.ApplicationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class IndexBuilder {
    @Autowired
    private ApplicationProperties properties;

    @Bean
    public void run() {
        System.out.println(properties.getSites());
    }

}

That fixed the problem with initialization, but Spring Framework just call run() immideately after launch. That isn't an expected behavior.

CodePudding user response:

Turns out, Spring isn't that smart as I thought.

To access @ConfigurationProperties class, that is: accessing a bean from a non-managed-by-spring class, - I had to go through this article: https://dzone.com/articles/autowiring-spring-beans-into-classes-not-managed-by-spring

It solved the problem, but... What a shame, because I expected Spring Framework to automatically inject a required resource into any class, without some mambo-jumbo stuff.

CodePudding user response:

Instead of using @Configuration you should use @Component on your IndexBuilder class.

@Configuration should only be used for configuration classes that define the application‘s beans. Here it is actually expected that the methods annotated with @Bean are executed immediately on application start. This is the phase where spring creates all needed beans. However I’m wondering spring accepts a void method with the @Bean annotation.

What’s your expected time when the run method should be executed? With a normal bean/component/service you could for example use the @PostConstruct annotation on methods. These will however be executed in the very same phase as the @Bean methods or at least not much later.

  • Related