How to do simple property validation when using Spring @Value

How can I check if is ${service.property}

not an empty string, and if so, throw some readable exception? This should happen when the Bean is created.

@Component
public class Service {

  @Value("${service.property}")
  private String property;
}

      

I'm looking for the easiest way (least written code). It would be great if annotations were used.

My current solution is to do "handwritten" validation inside the setter for the property, but there is very little code for that.

Hint: I was looking for a way to use SpEL since I am using it already internally @Value

, but as far as I found out it would not be that easy / clean. But I could have missed something.

Clarification: The expected behavior is that the application will not start. The goal is to ensure that all properties are set, and especially that the row properties are not empty . The error should clearly state what is missing. I do not want to set default values! The user has to install everything.

+3


source to share


2 answers


What you have there will work. If you have not included the property in your properties file, you will get an exception org.springframework.beans.factory.BeanCreationException

when starting the server.

Apr 22, 2015 9:47:37 AM org.apache.catalina.core.ApplicationContext log
SEVERE: StandardWrapper.Throwable
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.lang.String com.util.Service.property; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'service.property' in string value "${service.property}"
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:306)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1146)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)

      

An alternative would be to use initProperty

to handle or set the value, here you can throw some kind of readable exception.

@Component
public class Service {

    private String property;

    @Autowired
    public void initProperty(@Value("${service.property}") String property) {
        if(property == null) {
            // Error handling here
        }
    }
}

      



It really depends on whether you want to run the application regardless of whether the property is set, and if not, throw a readable exception to the log or console and then set it with a default value, or if you want the error to be thrown when starting the server and creating the bean.

I think the third option would be to just set the value if none were set using the default installer.

@Component
public class Service {

    @Value("${service.property:'This is my default setter string'}")
    private String property;
}

      

+2


source


Here's my solution, just put this class in your code (just fix the "my.package" line):

/**
 * Validates the environment-dependent properties during application start. Finds all spring beans, which classes are in
 * defined package, validates them and in case of error tries to log the property name (not class field name), taken
 * from {@link Value} annotation.
 * 
 * @author Tomasz
 */
@Component
public class ConfigurationChecker implements ApplicationListener<ContextRefreshedEvent> {
    private static final Logger LOG = LoggerFactory.getLogger(ConfigurationChecker.class);

    // this is a property, that is set in XML, so we bind it here to be found by checker. For properties wired directly in Beans using @Value just add validation constraints
    @Value("${authorization.ldap.url}")
    @NotBlank
    private String ldapUrl;

    private static final String FAIL_FAST_PROPERTY = "hibernate.validator.fail_fast";
    private Validator validator = Validation.byDefaultProvider().configure().addProperty(FAIL_FAST_PROPERTY, "false")
            .buildValidatorFactory().getValidator();

    /**
     * Performs the validation and writes all errors to the log.
     */
    @SneakyThrows
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {

        LOG.info("Validating properties");

        Set<ConstraintViolation<Object>> allViolations = new HashSet<>();

        // Find all spring managed beans (including ConfigurationChecker)...
        for (String beanName : event.getApplicationContext().getBeanDefinitionNames()) {
            Object bean = event.getApplicationContext().getBean(beanName);

            // ...but validate only ours.
            if (bean.getClass().getCanonicalName().startsWith("my.package")) {
                Set<ConstraintViolation<Object>> viol = this.validator.validate(bean);
                LOG.info("Bean '" + beanName + "': " + (viol.isEmpty() ? " OK" : viol.size() + " errors found"));
                allViolations.addAll(viol);
            } else {
                continue;
            }

        }

        // if any error found...
        if (allViolations.size() > 0) {

            for (ConstraintViolation<Object> violation : allViolations) {
                // ...extract "property.name" from field annotation like @Value("${property.name}")
                String propertyName = violation.getLeafBean().getClass()
                        .getDeclaredField(violation.getPropertyPath().toString()).getAnnotation(Value.class).value();
                propertyName = StringUtils.substring(propertyName, 2, -1);

                // .. log it ..
                LOG.error(propertyName + " " + violation.getMessage());
            }

            // ... and do not let the app start up.
            throw new IllegalArgumentException("Invalid configuration detected. Please check the log for details.");
        }
    }
}

      



And here's a test for it:

@RunWith(EasyMockRunner.class)
public class ConfigurationCheckerTest extends EasyMockSupport {

    @TestSubject
    private ConfigurationChecker checker = new ConfigurationChecker();

    @Mock
    private ContextRefreshedEvent event;
    @Mock
    private ApplicationContext applicationContext;

    @Test(expected = IllegalArgumentException.class)
    public void test() {

        expect(this.event.getApplicationContext()).andReturn(this.applicationContext).anyTimes();
        expect(this.applicationContext.getBeanDefinitionNames()).andReturn(new String[] { "configurationChecker" });
        expect(this.applicationContext.getBean("configurationChecker")).andReturn(this.checker);

        replayAll();
        this.checker.onApplicationEvent(this.event);

        verifyAll();
    }

}

      

0


source







All Articles