A Spring Web MVC application without Spring Boot.
What's all the boilerplate needed to start up a Spring Web MVC application complete with a backing web server (like embedded Tomcat)? How convoluted is it? How practical is it? I want to know.
Follow these instructions to build and run the example program.
- Pre-requisite: Java
- I used Java 21
- Build the program distribution
-
./gradlew installDist
-
- Start the Spring program and Tomcat server
-
./build/install/bootless-web-mvc/bin/bootless-web-mvc
- The program will log something that looks like the following.
-
00:37:15.264 [main] INFO dgroomes.spring_playground.bootless_web_mvc.Main - Starting an embedded Tomcat server and wiring up a simple Spring web application context... 00:37:15.729 [main] DEBUG dgroomes.spring_playground.bootless_web_mvc.Main - Tomcat server started and Spring application context initialized in PT0.463398S 00:37:15.729 [main] INFO dgroomes.spring_playground.bootless_web_mvc.Main - Open http://localhost:8080/message in your browser to see the message. Press Ctrl-C to stop the program and server.
-
- Open the browser
- Let's see the final effect by opening the browser to http://[::1]:8080/message. You should see a special message from the server.
- Stop the server
- When you're ready, stop the demo program and server with the
Ctrl+C
key combination.
- When you're ready, stop the demo program and server with the
To understand how we can use the Spring Web MVC APIs directly (i.e. without Spring Boot), we can study how Spring Boot codes to these APIs. In particular, I noticed a few places of interest:
DispatcherServletAutoConfiguration
ServletWebServerApplicationContext
TomcatServletWebServerFactory
TomcatStarter
TomcatWebServer#initialize
In general, I find the core Spring Framework software machinery tenable but when we expand to the "instantiation and initialization code paths" of Spring MVC, the cost/benefit worsens and then when we expand to the "instantiation and initialization code paths" of Spring Boot (I'm mainly talking about "autoconfiguration") I find it untenable. (To be clear I do appreciate the immense work, innovation, documentation, community, energy, and support of all contributors. I use Spring Boot and I benefit from it! It's too easy to be a critic). I think to preserve my own time, I have to decide to not spend too much time learning the internals and mental models (complete with accidental complexity). But I will sample some of the internals.
In particular, there is a line in ServletWebServerApplicationContext
which I find interesting.
this.webServer = factory.getWebServer(getSelfInitializer());
This line creates a web server. Why doesn't it use the bean metaphor? It codes to a bespoke interface method: ServletWebServerFactory#getWebServer
.
I know that building up the web server, like an embedded Tomcat instance, is especially complicated compared to the
average beans in a Spring application, like a DataSource
, Executor
, RestTemplate
, etc. And I know that dealing
with the initialization concerns of a servlet container is complicated because of the weight of supporting so many
features of an API that's grown and changed over many years. And I know that the web server is a foundational component
of the overall application, so we're in the bowels of bootstrapping complexity which is often the oddest, arbitrary, and
unloved part of any software system. But, is there a way to stay aligned with the core building block metaphor of Spring,
which is beans? I'm not an expert here, so I'm oversimplifying by definition, but that's my perspective.
General clean-ups, TODOs and things I wish to implement for this project:
- Spring's alternative web support. I know you can use the functional (non-annotation) support, but I think it might be tied to the reactive stack. Is it?
dgroomes/tomcat-playground
- This is another of my playground projects. It shows a working example of embedded Tomcat which I can conveniently use here.