I'm using Spring Boot 2.0.0.RC1 to build my REST service. In order to provide jar execution and .war deployment I extend SpringBootServletInitializer
like this:
@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);
}
...
}
I'm also using @ConfigurationProperties
to bind my application.yml
config to a bean like this:
@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;
}
}
Using this application.yml
:
users:
user1:
password: secret
When I start the application using java -jar
everything works as expected and I can access the users via RbsConfiguration
. But If I deploy it as a .war to a Tomcat, I get this exception:
***************************
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
So I'm wondering what's the difference here. It seems when started as a .war, it requires a prefix, when started via Spring Boot directly, it is ok leaving it unprefixed. In addition to the RbsConfiguration
I have further config classes (eg JwtConfiguration
) that use a prefix and seem to work fine with .war deployment.
Any hint why I'm seeing this behavior?
Extending SpringBootServletInitializer
didn't work out for me in the end. It didn't bind root properties properly (I think it didn't even load application.yml
) and it ignored my Spring Security setup somehow and came up with its own default setup.
What worked for me was dropping the extends
in RbsApplication
and simply provide a Webflux initializer by myself, setting up the SpringBoot application manually - heavily inspired by 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 };
}
}
With this Webflux and Security works as defined AND binding root properties to RbsConfiguration
is also working in .war
deployments.
I hope this helps anybody having similar problems (providing a .war hybrid and trying to get root properies correctly bound).
If someone found an easier way to get this done, I would appreciate any hint!
You need to provide a prefix in your @ConfigurationProperties
definition. Otherwise, how would you expect things to work? Everything should be mapped to your configuration class, including spring.*
namespace?
I guess the difference you might see between those deployments might have to do with values present in the environment when you start the application.
You can load properties from application.yaml when use WAR mode:
@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;
}
WAR mode is not supported by SpringBoot for WebFlux stack out-of-the-box. Using aforementioned approach you get and ability to use @EnableConfigurationProperties annotation. But it is still a hack.
The point is that this bean created too late and therefore many well documented features spring boot features are not working. For instance property encryption (jasypt) or conditional bean loading etc.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.