How to transfer git repositories from GitLab to GitHub?
Spring 5 + WebFlux without SpringBoot
Andriy AndrunevchynI don’t like SpringBoot because of hidden dependencies and unknown springconfigs behind annotations. But there is one more problem with SpringBoot – as usual latest examples from Pivotal (and others as well) are written based on SpringBoot and sometimes it’s not possible easily to figure out how properly to set up plain Spring config. I’m running new project based on WebFlux and it should be old school war file which we can deploy on any webserver. So task is to configure WebFlux without SpringBoot. There are two ways – mapping based on annotation which could be done even based on WebMvcConfigurer like this
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.mydomain")
@PropertySources({ @PropertySource(value = "classpath:/properties/common.properties", ignoreResourceNotFound = true),
@PropertySource(value = "classpath:/properties/dbConfig.properties") })
public class AppConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/testpage").setViewName("testpage");
}
}
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class WebXmlConfig implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
ctx.setServletContext(container);
ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
}
}
or second approach – mapping based on routes. Unfortunately there is no clear explanation how to do it without SpringBoot. I found only mention about such kind of config in book “Spring 5 Recipes” so I took it and added like
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
public class WebXmlConfig implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) {
AnnotationConfigApplicationContext fluxContext = new AnnotationConfigApplicationContext(WebFluxConfig.class);
HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(fluxContext).build();
ServletHttpHandlerAdapter handlerAdapter = new ServletHttpHandlerAdapter(httpHandler);
ServletRegistration.Dynamic fluxServlet = servletContext.addServlet("SpringFluxDispatcher", handlerAdapter);
fluxServlet.setLoadOnStartup(1);
fluxServlet.setAsyncSupported(true);
fluxServlet.addMapping("/");
}
}
And guess what? It didn’t work for me! I spend some time on investigation and figured out my mapping is not correct I configured route like
import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RouterFunctions.route; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; import com.domain.testproject.EchoHandler; @Configuration @EnableWebFlux @ComponentScan(basePackages = "com.domain.testproject", excludeFilters = { @Filter(Configuration.class) }) public class WebFluxConfig implements WebFluxConfigurer { @Bean public RouterFunction<ServerResponse> monoRouterFunction(EchoHandler echoHandler) { return route(GET("/echo"), echoHandler::echo); } }
But the proper path actually should include war file name because I deployed war file and tomcat run project in folder with the same name so that the proper route is
return route(GET("/testproject/echo"), echoHandler::echo);
As usual Spring has many way to configure the same thing so you can also write this route like this
return route(GET("/*/echo"), echoHandler::echo);
Also you could do like this
return route(request -> request.path().contains("/echo"), echoHandler::echo);
but it’s not really good idea cause urls http://host:port/testproject/echo
and http://host:port/testproject/path/echo
will be managed by the same handler
Actually I don’t understand why it was written like that. Why do we need project name on path? If we check class RequestPatternPredicate which we use for matching we will see the following
private static class PathPatternPredicate implements RequestPredicate {
private final PathPattern pattern;
public PathPatternPredicate(PathPattern pattern) {
Assert.notNull(pattern, "'pattern' must not be null");
this.pattern = pattern;
}
@Override
public boolean test(ServerRequest request) {
PathContainer pathContainer = request.pathContainer();
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
if (info != null) {
mergeTemplateVariables(request, info.getUriVariables());
return true;
}
else {
return false;
}
}
Issue happens because in method matchAndExtract
we pass as parameter container with the full path instead relative path. I don’t see other way except extending this class and overriding method test (( If I do it I’ll publish code later
The funny thing is – plain annotation config works fine with the second approach either, no need to configure path with project name.