如何在使用Spring @Value时进行简单的属性validation

如果${service.property}不是空字符串,如何检查,抛出某种可读exception,我如何检查? 它必须在Bean创建期间发生。

 @Component public class Service { @Value("${service.property}") private String property; } 

我正在寻找最简单的方法(最少编写代码)。 如果使用注释会很棒。

我目前的解决方案是在属性的setter中执行“手写”validation,但对于这样简单的事情来说,这是一个太多的代码。

提示:我找了一些使用SpEL的方法,因为我已经在@Value使用了它,但是到目前为止我发现它并不那么容易/干净。 但本来可以忽略一些东西。

澄清:预期的行为是,应用程序无法启动。 目标是确保所有属性都已设置,尤其是字符串属性不为空 。 错误应该清楚地说,缺少什么。 我不想设置任何默认值! 用户必须全部设置。

您可以将该组件用作属性占位符本身。 然后您可以使用任何您想要的validation。

 @Component @Validated @PropertySource("classpath:my.properties") @ConfigurationProperties(prefix = "my") public class MyService { @NotBlank private String username; public void setUsername(String username) { this.username = username; } ... } 

你的my.properties文件将如下所示:

 my.username=felipe 

你有什么工作。 如果未在属性文件中包含该属性,则在服务器启动时将收到org.springframework.beans.factory.BeanCreationExceptionexception。

 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) 

另一种方法是使用initProperty来处理或设置值,这里是你可以抛出某种可读exception的地方。

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

这取决于您是否希望启动应用程序,无论属性是否已设置,如果没有,请向日志或控制台抛出可读的exception,然后使用默认值设置它,或者如果您希望在服务器启动时抛出错误 – 和创建bean。

我想第三个选项是只设置值,如果没有使用默认的setter。

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

在这里我的解决方案,只需将该类放在您的代码中(只需修复“my.package”字符串):

 /** * 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 { 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> 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> 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 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."); } } } 

在这里测试它:

 @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(); } }