如何在使用Spring @Value

时间:2015-04-22 12:52:45

标签: java spring validation annotations

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

@Component
public class Service {

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

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

我目前的解决方案是执行"手写"在属性的setter中进行验证,但对于这样简单的事情来说,代码太多了。

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

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

3 个答案:

答案 0 :(得分:2)

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

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来处理或设置值,这里是您可以抛出某种可读异常的地方。

@Component
public class Service {

    private String property;

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

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

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

@Component
public class Service {

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

答案 1 :(得分:1)

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

@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

答案 2 :(得分:0)

这是我的解决方案,只需将该类放入代码中(只需修复“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<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.");
        }
    }
}

这里是对它的测试:

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

}