How to define isolated web contexts in a modular Spring Boot application? - spring-mvc

Given a Spring Boot application comprised of a bootstrap module, and two or more isolated business modules - each of which exposes REST API specific to a business domain, and each of which uses an independent, isolated document store for data persistence, how do I go about configuring such an application, such that:
The bootstrap module defines a parent context (non-web) which provides certain shared resources to underlying modules (global config, object mappers, etc.)
Each business module exposes their REST controllers on the same port but using a different context path. Ideally, I want to be able to define a base path for each module (e.g. /api/catalog, /api/orders) and separately define the sub-path within each controller.
Each business module defines its own repository configuration (e.g. different MongoDB settings for each module)
In order to isolate the contexts of the individual business modules (which allows me to manage independent repository configurations in each module) I have tried using the context hierarchies available in SpringApplicationBuilder to isolate the contexts of each of the individual business modules:
public class Application {
#Configuration
protected static class ParentContext {
}
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(ParentContext.class)
.child(products.config.ModuleConfiguration.class)
.web(true)
.sibling(orders.config.ModuleConfiguration.class)
.web(true)
.run(args);
}
}
however as each module contains a configuration class annotated with #EnableAutoConfiguration this causes Spring Boot to attempt to launch two independent embedded servlet containers, each trying to bind to the same port:
#Configuration
#EnableAutoConfiguration
public class WebApplicationConfiguration {
#Value("${api.basePath:/api}")
protected String apiBasePath;
#Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
#Bean
public ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(),
apiBasePath + "/products/*");
registration.setName(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return registration;
}
}
The Spring Boot documentation on context hierarchies states that the parent context cannot be a web context, so I'm a bit lost as to how I can share an embedded servlet container between isolated child contexts.
I have created a minimal GitHub project to illustrate the point:

Create configuration class for business module child1's context
package com.child1;
#Configuration
#ComponentScan(basePackages = {"com.child1a", "com.child1b"})
public class Child1Configuration {
}
Create configuration class for business module child2's context
package com.child2;
#Configuration
#ComponentScan(basePackages = {"com.child2a", "com.child2b"})
public class Child2Configuration {
}
Create a configuration class for bootstrap module parent context. Specify the component scanning for beans to be shared by child contexts
package com.parent;
#Configuration
#ComponentScan(basePackages = {"com.parent1", "com.root"})
public class ParentConfiguration {
}
Create SpringBootApplication class with two dispatcher servlets beans, one for each business module. Create application context for each servlet and set the context created by the boot application as root. Basically spring will inject the context into the ApplicationContext parameter of the #Bean methods.
package com.parent;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean child1(ApplicationContext parentContext) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDetectAllHandlerMappings(false);
AnnotationConfigWebApplicationContext applicationContext = new
AnnotationConfigWebApplicationContext();
applicationContext.setParent(parentContext);
applicationContext.register(Child1Configuration.class);
applicationContext.refresh();
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new
ServletRegistrationBean(dispatcherServlet, true, "/child1/*");
servletRegistrationBean.setName("child1");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
#Bean
public ServletRegistrationBean child2(ApplicationContext parentContext) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDetectAllHandlerMappings(false);
AnnotationConfigWebApplicationContext applicationContext = new
AnnotationConfigWebApplicationContext();
applicationContext.setParent(parentContext);
applicationContext.register(Child2Configuration.class);
applicationContext.refresh();
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new
ServletRegistrationBean(dispatcherServlet, true, "/child2/*");
servletRegistrationBean.setName("child2");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
}

Related

Integrate vertex with existing spring web application

I have an existing Spring based web application. I want to integrate vertx within the application.
Is there a way to do so?
Yes, have a look at the Vert.x with Spring section in the examples repository on GitHub.
In spring boot it is fairly simple
#SpringBootApplication
#ComponentScan(basePackages = { "com.mypackage", "com.myotherpackage" })
public class MyApplication {
#Autowired
private MainVerticle mainVertical;
public static void main(String[] args) throws Exception {
new SpringApplication(MyApplication.class).run(args);
}
#PostConstruct
public void deployServerVerticle() {
Vertx.vertx().deployVerticle(mainVertical);
}
}
The #PostConstuct allows you to deploy all the verticals you want (all the properties are set at this point).
And it goes without saying that the MainVerticle should be marked with the #Component annotation.

Spring Boot not using configured Jackson ObjectMapper with #EnableWebMvc

I wanted to use a configured version of Jackson ObjectMapper in my project (ignoring null values and snake_case, also using some custom modules).
In my large project I wasn't able to get Spring MVC to actually use this mapper.
The build.gradle:
buildscript {
ext {
springBootVersion = '1.5.6.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter')
compile("org.springframework.boot:spring-boot-starter-jetty:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.8'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.8'
testCompile('org.springframework.boot:spring-boot-starter-test')
}
My application.yml:
spring:
application:
name: Jackson test
jackson:
property-naming-strategy: SNAKE_CASE
default-property-inclusion: non_empty
debug: true
A container class:
public class MyLocationEntity {
public String nameAndSnake;
}
A config class:
#Configuration
#EnableWebMvc
public class AppConfig {
}
And a controller:
#RestController
#RequestMapping("/test")
public class TestController {
#Autowired
private ObjectMapper objectMapper;
#RequestMapping(value = "/test", produces = "application/json")
public MyLocationEntity test() throws JsonProcessingException {
MyLocationEntity location = new MyLocationEntity();
location.nameAndSnake = "hello world";
String expexted = objectMapper.writeValueAsString(location);
return location;
}
}
If I now look at the value of expected in the debugger it is {"name_and_snake":"hello world"}.
But if I let the controller run through the actual response is {"nameAndSnake":"hello world"}.
When I remove #EnableWebMvc it works. How can I use the configured mapper with MVC and not remove the rest of the autoconfiguration for Web MVC?
It's not evident just from the Javadocs, but #EnableWebMvc disables the Spring Boot default web MVC auto configuration provided by WebMvcAutoConfiguration, including the use of the Jackson ObjectMapper bean configured by the application.yml properties. Per the Spring Boot Reference Documentation:
9.4.7. Switch off the Default MVC Configuration
The easiest way to take complete control over MVC configuration is to provide your own #Configuration with the #EnableWebMvc annotation. Doing so leaves all MVC configuration in your hands.
Since #EnableWebMvc has the (likely surprising) behavior of disabling auto configuration, using this annotation may have unintended and undesirable side-effects. A different approach may be more appropriate.
That being said, it is possible that the behavior of #EnableWebMvc is still desired. To use application.yml properties in concert with the #EnableWebMvc annotation, the MVC configuration must be manually configured to mimic the relevant disabled Spring Boot auto-configuration. There are a few different possible approaches to this.
The first approach is to duplicate the Spring Boot configuration code from WebMvcAutoConfiguration.EnableWebMvcConfiguration.configureMessageConverters(). This will replace the message converters — including the MappingJackson2HttpMessageConverter containing the unconfigured ObjectMapper — with the ones that would have used with the default Spring Boot configuration:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private ObjectProvider<HttpMessageConverters> messageConvertersProvider;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
}
Alternatively, rather than using the default Spring Boot list of message converters, it is possible to swap in just the ObjectMapper or MappingJackson2HttpMessageConverter bean provided by Spring Boot (which have the application.yml properties applied):
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private ObjectMapper objectMapper;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.map(c -> (MappingJackson2HttpMessageConverter) c)
.forEach(c -> c.setObjectMapper(objectMapper));
}
}
or
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (int i = 0; i < converters.size(); i++) {
if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
converters.set(i, mappingJackson2HttpMessageConverter);
}
}
}
}

Spring boot No WebApplicationContext found

I have a simple spring boot app and I'm trying to get it up and running. The config consists of an app context ( applicationContext.xml) XML with a bunch of beans in it. I have a Spring application class:
#SpringBootApplication
#Configuration
#ImportResource("classpath:applicationContext.xml")
public class WebCheckApplication {
private static final Logger logger = Logger.getLogger(WebCheckApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(WebCheckApplication.class, args);
if (logger.isDebugEnabled()) {
logger.debug("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
logger.debug(beanName);
}
}
}
}
And I have a #WebListener class that grabs a few beans from the WebContext from within the ServletContext:
#WebListener
public class SystemPropertiesContextInitializer extends SysPropsAlertsFetcher implements ServletContextListener {
private static final Logger logger = Logger.getLogger(SystemPropertiesContextInitializer.class);
#Override
public void contextDestroyed(ServletContextEvent sce) {
//remove the SystemProperties and alert types map object from context
sce.getServletContext().removeAttribute(BaseAuthenticatedController.SYSPROPS_KEY);
sce.getServletContext().removeAttribute(BaseAuthenticatedController.ALERT_TYPES_MAP_KEY);
}
#Override
public void contextInitialized(ServletContextEvent sce) {
SysPropsDataAccess = (SystemPropertiesDataAccess) WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getBean("SystemPropertiesDataAccess");
AlertsDataAccess = (AlertDataAccess) WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getBean("AlertsDataAccess");
fetchObjects(sce.getServletContext());
}
}
When I attempt to start the app, I get the following error:
SEVERE: Exception sending context initialized event to listener instance of class web.SystemPropertiesContextInitializer
java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
at org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext(WebApplicationContextUtils.java:83)
at .web.SystemPropertiesContextInitializer.contextInitialized(SystemPropertiesContextInitializer.java:31)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4994)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5492)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
and it occurs at this line:
SysPropsDataAccess = (SystemPropertiesDataAccess) WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getBean("SystemPropertiesDataAccess");
It looks like Spring isn't creating a WebApplicationContext.
Greater than or equal 1.3.0.RC1 use #ServletComponentScan
#ServletComponentScan // <-- This scans for EJB #WebFilter, #WebListener and #WebServlet
#SpringBootApplication
#ImportResource("classpath:applicationContext.xml")
public class WebCheckApplication {
Less than or equal 1.2.x use #Component to scan for listener
#Component // <-- This allows the component to be found by #ComponentScan inside of #SpringBootApplication
#WebListener
public class MojoSystemPropertiesContextInitializer extends MojoSysPropsAlertsFetcher implements ServletContextListener {
War Deploy extend SpringBootServletInitializer
public class WebCheckApplication extends SpringBootServletInitializer {
In 1.3.0.RC1 #ServletComponentScan was added so simply annotating your main application config should allow these to be picked up. Otherwise adding #Component to your ServletContextListener should work
This link is a discussion on how they currently handle #WebFilter how they decided to handle #WebFilter and they also discuss SpringBootServletInitializer and how this would pick process each item twice if both were to be used. Also links to the commits that implement the new feature.
https://github.com/spring-projects/spring-boot/issues/2290
If you intend to deploy your application as a war file you may also have your main configuration extend SpringBootServletInitializer
http://docs.spring.io/spring-boot/docs/current/reference/html/howto-traditional-deployment.html
All that was needed was to make the Application class extend SpringBootServletInitializer

Embedded Jetty + Spring MVC main method

I'm using Embedded Jetty and Spring MVC and i'm starting my WebApplication through the main function.
i don't like the way it's looks, it's not very clean and i have the feeling i'm doing something wrong even though it's working just fine.
my problem is that i want to initialize ServerHolder,ServletContextHandler and Server objects outside the main method, but i cannot do it because DispatcherServlet requires and ApplicationContext.
Any ideas?
public static void main(String[] args) throws Exception {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(EPConfiguration.class);
ServletHolder servletHolder = new ServletHolder(new DispatcherServlet(applicationContext));
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(servletHolder, "/*");
BasicConfigurator.configure();
Server server = new Server();
server.setHandler(context);
//HTTP
ServerConnector connector = new ServerConnector(server);
connector.setPort(9999);
server.setConnectors(new Connector[]{connector});
server.start();
server.join();
}
I'm not familier with Jetty, but you can setup and initialize DispatcherServlet in the following way:
Declare a java class which implements the abstract class AbstractAnnotationConfigDispatcherServletInitializer (it usually will be called WebAppInitializer).
Implement its abstract methods - getRootConfigClasses() (which return the classes which configure Application Context), getServletConfigClasses() (which returns the classes which configure the ServletContext) and getServletMappings() (which sets the DispatcherServletmapping).
I usually call the class that configures the ServletContext as WebConfig. You should annotate it with the #Configuration and #EnableWebMvc. In addition you should use it to define a ViewResolver bean:
```java
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/view"):
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
```

Configuring spring to return a static file on a Controller method

How do I say to Spring MVC to return a static resource at runtime(in a method)?
Explain better. I configured my application(spring-mvc 3.2.4) to deal with some static resource and to work with two view resolvers, FreeMaker and Json. I wish that in a controller method I would be able to say to spring-mvc that he must take the file in the static resource despite try hadle by one of views resolvers.
My configuration class looks like this:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.xxxx", excludeFilters = #Filter(Configuration.class)})
public class WebConfig extends WebMvcConfigurerAdapter {
...
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/app.js").addResourceLocations("/app.js");
}
...
#Bean
public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
// Define the view resolvers
List<ViewResolver> resolvers = new ArrayList<ViewResolver>();
JsonViewResolver jsonViewResolver = new JsonViewResolver();
FreeMarkerViewResolver freeMarkerViewResolver = new FreeMarkerViewResolver();
freeMarkerViewResolver.setSuffix("rtl");
resolvers.add(jsonViewResolver);
resolvers.add(freeMarkerViewResolver);
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
resolver.setViewResolvers(resolvers);
return resolver;
}
}
My controller:
#Controller
#RequestMapping("/")
public class JSViewController {
#RequestMapping(value="*.js")
public String resolveArquivo(HttpServletRequest request){
// Here I would be able to say to sprint to return a static resource instead of hadle it with one of the View Resolvers.
return request.getRequestURI().replace(request.getContextPath(),"");
}
}
Its would be better if you try to access static and dynamic data seperately as hitting a controller for static data will waste your resources, increase latency.
For eg. if you want to access js from jsp then you can use JSTL tag library which would dynamically return the output directory from where you can fetch the static resource.
This gives you the flexibility to cache your static resources (Akamei or something else) or server them from apache instead of tomcat.
If you really want to render your static resource from controller then you need to have an interceptor which could be called before any of the view resolvers which will identify whether the requested resource us static and render accordingly. But this would be invoked for every htttp request which is not desirable.
Unfortunately I wasn't able to figure out how return a static resource in runtime with spring, but I resolved the situation using the old and good Filter.

Resources