0

I have a Spring Boot Configuration class that looks like the following:

package foo.bar;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Configuration @ConfigurationProperties(prefix = "myConfig") public class MyConfig
{

    private String foo;
    private int myValue;

    @NotNull private String requiredString;

    @Min(0) @Max(100) private int smallPositiveInt;

    public void setFoo(String foo) { this.foo = foo; }

    public void setMyValue(int myValue) { this.myValue = myValue; }

    public void setRequiredString(String requiredString) { this.requiredString = requiredString; }

    public void setSmallPositiveInt(int smallPositiveInt)
    {
        this.smallPositiveInt = smallPositiveInt;
    }

    public String getRequiredString() { return requiredString; }

    public int getSmallPositiveInt() { return smallPositiveInt; }

    public String getFoo() { return foo; }

    public int getMyValue() { return myValue; }
}

And a YAML config file that looks like:

server:
  port: 0

myConfig:
  myValue: 9876543
  foo: Goodbye
  requiredString: Here
---

And my SpringApplication code looks like the following:

package foo.bar;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication public class MyService implements ApplicationRunner
{

    // Define the logger object for this class
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired private MyConfig myConfig;

    // myConfig is null when I uncomment these lines:
    //    @Bean
    //    public Validator configurationPropertiesValidator()
    //    {
    //        return new MyConfigValidator();
    //    }

    @Autowired ApplicationContext applicationContext;

    @Override public void run(ApplicationArguments applicationArguments) throws Exception
    {
        log.info("Running application...");

        log.info("MyConfig values:");
        log.info("foo=" + myConfig.getFoo());
        log.info("myValue=" + myConfig.getMyValue());
        log.info("smallPositiveInt=" + myConfig.getSmallPositiveInt());

        log.warn("Example warning log message.");
        log.error("Example error log message.");
        log.debug("Example debug log message.");
    }

    public static void main(String[] args) { SpringApplication.run(MyService.class, args); }
}

When I uncomment the validator the autowired config is null but it works fine when this is commented out. Any ideas what could be happening with the dependency injection?

MyConfigValidator is now completely blank and just implements Validator with no actual functionality I can post it if someone believes that may be the issue.

Update: When I debug the code I can see that validate() is being called in MyConfigValidator with a MyConfig object that has the correct values from the YAML file. Is it possible that Spring injects this object into MyConfigValidator over the Spring Boot Application when it is included in the code as a Bean?

Update 2: After looking at the Spring Boot property validation example I was able to get this to work by creating a static class that implements ApplicationRunner. However, I don't see why this is necessary and would like to avoid doing this.

Lucas
  • 2,514
  • 4
  • 27
  • 37
  • @Makoto I disagree that this is a duplicate question. The linked thread does not answer my question. – Lucas Aug 11 '16 at 15:13
  • This question helped substantially: http://stackoverflow.com/questions/33838978/multiple-configurationproperties-validator-beans-in-spring-environment I was able to move the validation code into the configuration class and Spring appears to inject things correctly. Not sure if this is best practice or not though. – Lucas Aug 11 '16 at 22:24

2 Answers2

2

I think the reason that myConfig is null is because configurationPropertiesValidator gets instantiated early. This is causing MyService to get instantiated before AutowiredAnnotationBeanPostProcessor is available to inject fields.

If you make the configurationPropertiesValidator() method static, things should work fine:

@SpringBootApplication 
public class MyService implements ApplicationRunner {

    // ...

    @Bean
    public static Validator configurationPropertiesValidator() {
        return new MyConfigValidator();
    }

    // ...

}
Phil Webb
  • 8,119
  • 1
  • 37
  • 37
1

The approach that seems to work the best for my project is to embed the validator inside the configuration class like so:

@Component 
@ConfigurationProperties(prefix = "myConfig") 
public class MyConfig implements Validator
{

private String foo;
private int myValue;
private String requiredString;
private int smallPositiveInt;

private final Logger log = LoggerFactory.getLogger(this.getClass());

// This means that this validator only supports validating configs of type "MyConfig".
@Override public boolean supports(Class<?> type) { return type == MyConfig.class; }

@Override public void validate(Object o, Errors errors)
{
    MyConfig c = (MyConfig)o;
    log.info("Validating: " + c.toString());
    if(c.getSmallPositiveInt() == 60)
    {
        errors.rejectValue("smallPositiveInt", "error", "Cannot be 60!");
    }
}
}

Please let me know of any downsides to using this approach, it appears to be the best solution when using multiple configuration objects since they seem to be validated whenever they are created via Spring dependency injection.

Lucas
  • 2,514
  • 4
  • 27
  • 37