Longing for the Summer...20th of June 2017My last experience with the Spring framework was with its third iteration during one of my previous projects. Spring version 3.0 hailed the introduction of configuration through annotations and was a significant difference from the xml-configurations of its predecessor. I kind of glossed over the fourth major release, because it mainly focused on improving its under-the-hood functionalities and the introduction of Java 8 concepts (websockets, STOMP,…) and a Groovy DSL. |
Deviating a bit from the pure Spring 5 framework, I also took a look at the Spring Boot project, which eliminates the need for setting up an independent app server, or a profile in eclipse for my development ease. All of this just by adding the proper dependencies in my pom.xml and writing one line of code in the main function:
@Controller
@EnableAutoConfiguration
public class TestController {
@RequestMapping("/")
public @ResponseBody String home() {
return "Hello World!";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(TestController.class, args);
}
}
Considering the new additions for Spring 5, there are once again a string of improvements on existing functionality (testing, mvc, support for JDK8…), removal of several deprecated packages (like the BeanFactoryLocator mechanism), and discontinued support (Portlet, Velocity, JasperReports, XMLBeans, JDO, Guava). The two main new topics for me are the Kotlin support and the reactive programming framework (Spring WebFlux). The Kotlin support I will not be looking at, as I do not see myself using it any time soon, so this article will mainly focus on the reactive programming.
The reactive framework is built with several components of the Spring Framework, and under the hood it employs Reactor for most of its reactive features. The diagram shows the different modules it uses, but the WebFlux module contains most of the support syntax. WebFlux was introduced because the Spring MVC framework was found to be too hard to adapt to including non-blocking IO support (Servlet 3.1 spec),even though asynchronous call support (Servlet 3.0 spec) was already included. For those interested in the complete Servlet specification, it can be downloaded from the Oracle website, or you can download it here.
The framework utilizes Function objects to configure how a server must react to HTTP requests. A caveat: An application cannot use both @RequestMapping and RouterFunction in its configuration. They are mutually exclusive. These Function objects can be deployed on any server supporting the Servlet 3 spec, or can be used with one of the embedded Spring servers. Webflux comes with support for multiple of the current servers, for example Tomcat:
Tomcat tomcat = new Tomcat(); Context rootContext = tomcat.addContext("", System.getProperty("java.io.tmpdir")); ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(RouterFunctions.toHttpHandler(routingFunction())); Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet); rootContext.addServletMappingDecoded("/", "httpHandlerServlet"); TomcatWebServer server = new TomcatWebServer(tomcat); server.start();
The RouterFunction maps a RequestPredicate (combining a URL mapping and a RequestMethod from the @RequestMapping annotation) to a HandlerFunction, which extends from Function<Request, Response<T>> (new interfaces in JDK8).
FormHandler formHandler = new FormHandler(); RouterFunctions.route(RequestPredicates.GET("/hello"), formHandler::handleHello); public class FormHandler { Mono<ServerResponse> handleHello(ServerRequest request) { return ok().body(BodyInserters.fromObject("helloworld")); } }
There is a shorthand available curtesy of the lambda functions introduction in JDK8:
RouterFunctions.route(GET("/test"), serverRequest -> ok().body(BodyInserters.fromObject("helloworld")));
One of the ideas of reactive programming is to render code more understandable and readable. However, I find this convoluted syntax doesn’t help a lot in that respect, unless you start using a lot of static imports, which I have always abhorred in the past, as it obfuscates a lot of code, and although it renders your code shorter, I found it to blur the lines between classes too much, much like the #define from the C programming language.
import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.POST; import static org.springframework.web.reactive.function.server.RequestPredicates.path; import static org.springframework.web.reactive.function.server.ServerResponse.ok;
The main principle is to further expand on the RouterFunction with additional URL mappings, filters, and nests (collection of URL mappings to delegate to in case no match is found in the original RouterFunction). These are stringed together with the and-method, and convenience methods exist to concatenate the and-method with the others (for example andRoute). This allows for defining all request mappings in a single location (in this case a configuration class), fixing the problem of finding the proper class containing the @RequestMapping annotations. The overview becomes easier, but this does not way up against the clunkyness of the syntax for me.
RouterFunction<ServerResponse> defaultRouter = RouterFunctions.route(GET("/**"), request -> Mono.empty()) .filter((request, next) -> { System.out.println("Default handler invocation: " + request.path()); return next.handle(request); }); RouterFunction<?> chainRouter = RouterFunctions .route(GET("/test"), formHandler::handleHello) .andRoute(POST("/login"), formHandler::handleLogin) .and(RouterFunctions.resources("/files/**", new ClassPathResource("files/"))) .andNest(path("/actor"), restfulRouter) .andOther(defaultRouter) .filter((request, next) -> { System.out.println("Router Handler triggered: " + request.path()); return next.handle(request); });
If you would register multiple filters, these are fired LIFO, meaning the last filter defined, is the first to be fired. Another example of a filter use would be to wire in a Security Manager to check the request.
RouterFunction<?> chainRouter = RouterFunctions .route(GET("/test"), formHandler::handleHello) .filter((request, next) -> { System.out.println("Before Handler 1 triggered: " + request.path()); return next.handle(request); }) .filter((request, next) -> { System.out.println("Before Handler 2 triggered: " + request.path()); return next.handle(request); }) .filter((request, next) -> { System.out.println("Before Handler 3 triggered: " + request.path()); return next.handle(request); });
Before Handler 3 triggered: /test Before Handler 2 triggered: /test Before Handler 1 triggered: /test
Next up on the review list is the interaction with the WebSocket functionality of HTML5. But I will leave that for a future thought.
Thought | Coding |