SpringBootServletInitializer和@ConfigurationProperties不使用.war部署的根属性

时间:2018-02-16 15:33:55

标签: java spring spring-boot

我使用Spring Boot 2.0.0.RC1来构建我的REST服务。为了提供jar执行和.war部署,我像这样扩展SpringBootServletInitializer

@Configuration
@SpringBootApplication
@EnableWebFlux
@EnableConfigurationProperties({ RbsConfiguration.class, 
JwtConfiguration.class })
public class RbsApplication extends SpringBootServletInitializer 
implements WebFluxConfigurer {

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

    ...
}

我还使用@ConfigurationProperties将我的application.yml配置绑定到这样的bean:

@ConfigurationProperties
@Validated
public class RbsConfiguration {

    private Map<String, String> users;

    @NotEmpty
    public Map<String, String> getUsers() {
        return users;
    }

    public void setUsers(Map<String, String> users) {
        this.users = users;
    }
}

使用此application.yml

users:
  user1:
    password: secret

当我使用java -jar启动应用程序时,一切都按预期工作,我可以通过RbsConfiguration访问用户。但是如果我将它作为.war部署到Tomcat,我会得到这个例外:

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to bind properties under '' to foo.bar.RbsConfiguration:

    Reason: PropertyName must not be empty

Action:

Update your application's configuration

...

Caused by: org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under '' to foo.bar.RbsConfiguration
    at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:227)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:203)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:187)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:169)
    at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:79)
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:167)
    ... 100 more
Caused by: java.lang.IllegalArgumentException: PropertyName must not be empty
    at org.springframework.util.Assert.hasLength(Assert.java:233)
    at org.springframework.boot.origin.PropertySourceOrigin.<init>(PropertySourceOrigin.java:41)
    at org.springframework.boot.origin.PropertySourceOrigin.get(PropertySourceOrigin.java:79)
    at org.springframework.boot.context.properties.source.SpringConfigurationPropertySource.find(SpringConfigurationPropertySource.java:121)
    at org.springframework.boot.context.properties.source.SpringConfigurationPropertySource.find(SpringConfigurationPropertySource.java:104)
    at org.springframework.boot.context.properties.source.SpringConfigurationPropertySource.getConfigurationProperty(SpringConfigurationPropertySource.java:86)
    at org.springframework.boot.context.properties.bind.Binder.lambda$findProperty$3(Binder.java:294)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1812)
    at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
    at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
    at org.springframework.boot.context.properties.bind.Binder.findProperty(Binder.java:295)
    at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:239)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:198)
    ... 104 more

所以我想知道这里的区别是什么。它似乎在作为.war开始时,它需要一个前缀,当通过Spring Boot直接启动时,它可以保持没有前缀。除RbsConfiguration之外,我还有其他配置类(例如JwtConfiguration),它们使用前缀并且似乎可以在.war部署中正常工作。

任何暗示我为什么会看到这种行为?

3 个答案:

答案 0 :(得分:0)

您需要在@ConfigurationProperties定义中提供前缀。否则,你会如何期待事情的发展?应该将所有内容映射到您的配置类,包括spring.*命名空间?

我猜您在这些部署之间可能会看到的差异可能与启动应用程序时环境中存在的值有关。

答案 1 :(得分:0)

扩展SpringBootServletInitializer最终对我没有用。它没有正确绑定root属性(我认为它甚至没有加载application.yml)并且它以某种方式忽略了我的Spring Security设置并且提出了它自己的默认设置。

对我有用的是放弃extends RbsApplication并自行提供Webflux初始化程序,手动设置SpringBoot应用程序 - 受SpringBootServletInitializer的启发。

public class WebfluxInitializer extends AbstractReactiveWebInitializer {

    private ServletContext servletContext;

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        this.servletContext = servletContext;
        super.onStartup(servletContext);
    }

    @Override
    protected ApplicationContext createApplicationContext() {

        SpringApplicationBuilder builder = new SpringApplicationBuilder();

        StandardServletEnvironment environment = new StandardServletEnvironment();
        environment.initPropertySources(servletContext, null);

        builder.environment(environment);
        builder.sources(getConfigClasses());
        builder.web(WebApplicationType.NONE);
        builder.main(RbsApplication.class);

        return builder.build().run();
    }

    @Override
    protected Class<?>[] getConfigClasses() {
        return new Class[] { RbsApplication.class };
    }
}

通过此Webflux和Security按定义工作,RbsConfiguration的绑定根属性也在.war部署中工作。

我希望这可以帮助任何有类似问题的人(提供.war混合并尝试正确绑定root用户)。

如果有人找到了更简单的方法来完成这项任务,我将不胜感激任何提示!

答案 2 :(得分:0)

使用WAR模式时,您可以从application.yaml加载属性:

@Bean
public PropertySourcesPlaceholderConfigurer properties() {
    PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
    YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
    List<Resource> resources = new ArrayList<>();
    resources.add(new ClassPathResource("application.yaml"));
    yaml.setResources(resources.toArray(new Resource[0]));
    propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
    return propertySourcesPlaceholderConfigurer;
}

SpringBoot不支持现成的WebFlux堆栈使用WAR模式。 使用上述方法,您可以使用@EnableConfigurationProperties批注。 但这仍然是黑客。
关键是该bean创建得太晚了,因此许多有据可查的Spring Boot功能都无法使用。 例如属性加密(jasypt)或有条件的bean加载等。