Vaadin Servlet Auto-Loading
When a project defines no @WebServlet
, how come that VaadinServlet
gets loaded automatically?
Without automatic loading of VaadinServlet
there would be no servlet to handle
requests, and Jetty/Tomcat would simply return 404 NOT FOUND.
Let’s take a look, both on standard servlet project and on a Spring-Boot project, how
the automatic loading works.
Servlet project
In this case we’re running Vaadin app in a servlet container without Spring (e.g.
embedded container via Vaadin-Boot).
The ServletDeployer.contextInitialized()
gets called by the servlet container,
since it implements the ServletContextListener
. It ultimately calls ServletDeployer.createServletIfNotExists()
which in turn calls ServletContext.addServlet()
to register a new VaadinServlet
dynamically.
The servlet mapping is set to /*
which means that it will handle all requests by default
(unless there is a servlet mapped to a more specific path).
Spring-Boot project
When running Vaadin via Spring Boot, the important code is at SpringBootAutoConfiguration.servletRegistrationBean()
which creates the servlet binding of SpringServlet
. Note the @ConditionalOnMissingBean
annotation
which only creates the binding if the project doesn’t have a custom @WebServlet
extending
from SpringServlet
.
Note that this annotation was missing for Vaadin 23.4.1 which causes the default servlet to load anyway, causing WebSocket issues: #19888.
What’s interesting is that the default servlet is mapped to /vaadinServlet/*
and not /*
,
yet the app apparently works and requests are handled at /*
. There is a Spring magic which
configures Spring Dispatcher Servlet to dispatch requests accordingly.
Spring Dispatcher Servlet
Spring uses something called the Front Controller pattern where a single servlet forwards requests to other controllers. There’s An Intro to the Spring DispatcherServlet about the concept.
To debug this monster, just open the DispatcherServlet
class coming from
Spring, and place a breakpoint to DispatcherServlet.doDispatch()
.
The handlerMappings
is a list of all handlers where the dispatcher servlet
will try to dispatch the HTTP request.
One of those is VaadinServletConfiguration$RootExcludeHandler
which
dispatches requests to /*
to the vaadinForwardingController
bean,
which is a ServletForwardingController
, forwarding requests to SpringServlet
.
See both beans in the VaadinServletConfiguration
class.
The handler sets its order to almost the lowest value, so almost any other handler
will come before this one. That’s why it’s easy to snatch request processing
away from Vaadin SpringServlet
.
Open questions
I don’t know the answers for these yet.
- Is there a configuration in
application.properties
which increases the order ofVaadinServletConfiguration$RootExcludeHandler
? - If I introduce my own servlet extending
SpringServlet
,servletRegistrationBean()
still auto-loadsSpringServlet
loads anyway because of #19888. Can this auto-loading be turned off somehow? - If I introduce my own custom servlet extending
SpringServlet
, will theDispatcherServlet
be turned off, or will it merely prioritize my custom servlet above other handlers?
Vaadin 8
The abovementioned applied to Vaadin 23+. Let’s take a look how Vaadin 8 works under the hood.
Servlet project
There’s no ServletDeployer
and no automatic registration of VaadinServlet
. You
have to have a servlet registered in your app, in order for Vaadin 8 to work.
That’s what you usually do anyway, since Vaadin 8 auto-configures itself via the
@VaadinServletConfiguration
annotation expected to be present on the servlet.
Usually you have something like this in your code:
@VaadinServletConfiguration(ui = MyVaadin8UI.class, productionMode = false)
@WebServlet(urlPatterns = {"/*"})
public class MyServlet extends VaadinServlet {}
Spring-Boot project
When running Vaadin 8 via Spring Boot, the important code is at VaadinServletConfiguration.vaadinServlet()
(which creates the SpringVaadinServlet
itself)
and VaadinServletConfiguration.createServletRegistrationBean()
(which creates the ServletRegistrationBean
).
Note the @ConditionalOnMissingBean
annotation
which only creates the binding if the project doesn’t have a custom @WebServlet
extending
from SpringServlet
.
It’s not possible to add @VaadinServletConfiguration
annotation to
the auto-loaded servlet but that’s fine:
the UI is discovered via the @SpringUI
annotation, and other settings are taken from Spring
setting machinery. Actually, according to
Vaadin 8 docs: Vaadin Spring add-on,
the custom servlet must not have @VaadinServletConfiguration
.
What’s interesting is that the default servlet is mapped to /vaadinServlet/*
and not /*
,
yet the app apparently works and requests are handled at /*
. There is a Spring magic which
configures Spring Dispatcher Servlet to dispatch requests accordingly.
Registration with DispatcherServlet
VaadinServletConfiguration.vaadinUiForwardingHandlerMapping()
creates a HandlerMapping
which forwards all requests to vaadinUiForwardingController()
which is a ServletForwardingController
forwarding requests to the vaadinServlet()
bean.
The mapping places almost highest in the list of mappings, which means that
it’s almost a guarantee DispatcherServlet
will forward requests to this mapping.
See the documentation above for other Spring-related stuff.