In traditional Java Web development, developers had to independently deploy Servlet containers, such as Tomcat, and put the application into a war package to run, which was more or less tedious and inconvenient to debug. The emergence of embedded Servlet containers has changed this situation. When using an embedded Servlet container, we no longer need any external support, and the application itself is a standalone entity. As a prime example of unlocking productivity, SpringBoot uses Embedded Tomcat by default to launch Web applications, which we’ll explore today (based on SpringBoot 2.3.0).
@SpringBootApplication
public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code
This code I believe you are familiar, it is the entrance to the application, everything starts from here. We directly follow up springApplication.run (…) And came to
public static ConfigurableApplicationContext run(Class
[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
Copy the code
Static run (…). The function creates an instance of SpringApplication and calls its run(…). Method to follow up on the SpringApplication constructor
// The irrelevant logic has been removed
public SpringApplication(ResourceLoader resourceLoader, Class
... primarySources) {
// Determine the current operating environment according to whether there is a corresponding class file in the classpath
// We introduced spring-boot-starter-web, so the type is inferred to be SERVLET
this.webApplicationType = WebApplicationType.deduceFromClasspath();
}
Copy the code
Here we focus on how SpringBoot is concluded that the type of application, it is based on simple classpath to determine whether there is any specified class file, in our example type is inferred to WebApplicationType SERVLET. Then come run(…) Instance methods
// The irrelevant logic has been removed
public ConfigurableApplicationContext run(String... args) {
ConfigurableApplicationContext context = null;
try {
1. Create ApplicationContext
context = createApplicationContext();
// refresh ApplicationContext
refreshContext(context);
}
catch (Throwable ex) {
throw new IllegalStateException(ex);
}
return context;
}
Copy the code
Leaving out irrelevant logic, run(…) The method mainly does two things:
- create
ApplicationContext
- The refresh
ApplicationContext
Huh? Embedded Tomcat is now up and running? It seems the secret lies in these two steps. View the source these two steps, it is easy to know is the specific type of the ApplicationContext AnnotationConfigServletWebServerApplicationContext, and refresh is called its refresh () method.
AnnotationConfigServletWebServerApplicationContext
To observe theAnnotationConfigServletWebServerApplicationContext
The inheritance tree, as you can see, outside the red circle is what we are very familiar with in the traditionWeb
For use in the environmentApplicationContext
; The part in the red circle, just by looking at the name can also be guessed is the focus of our study —WebServerApplicaitonContext
.
A little further AnnotationConfigServletWebServerApplicationContext, it inherited from ServletWebServerApplicationContext, It also provides support for Component Scan and reading and parsing of @Configuration classes on the basis of the parent class.
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
implements AnnotationConfigRegistry {
// Read and parse the @Configuration Configuration class
private final AnnotatedBeanDefinitionReader reader;
// Scan all components under the specified package
private final ClassPathBeanDefinitionScanner scanner;
// rest of codes are omitted...
}
Copy the code
This part of the function is implemented by the agent to AnnotatedBeanDefinitionReader and ClassPathBeanDefinitionScanner. These two brothers are also old friends and have nothing to do with the startup of the embedded Servlet container, so we won’t go into that.
WebServerApplicaitonContext
Next we focus falls on the WebServerApplicaitonContext, first take a look at the definition of it
/** * the implemented ApplicationContext is responsible for the creation and lifecycle management of the embedded WebServer.@link ApplicationContext application contexts} that
* create and manage the lifecycle of an embedded {@link WebServer}.
*/
public interface WebServerApplicationContext extends ApplicationContext {
/** * Returns the WebServer created by the current ApplicationContext and manages its lifecycle by returning a WebServer reference */
WebServer getWebServer(a);
/** * namespace, which can be used to avoid ambiguity when the application has multiple Webservers running */
String getServerNamespace(a);
static boolean hasServerNamespace(ApplicationContext context, String serverNamespace) {
return (context instanceofWebServerApplicationContext) && ObjectUtils .nullSafeEquals(((WebServerApplicationContext) context).getServerNamespace(), serverNamespace); }}// Subinterface, which can be configured to ApplicationContext
public interface ConfigurableWebServerApplicationContext extends ConfigurableApplicationContext.WebServerApplicationContext {
/** * sets the namespace */
void setServerNamespace(String serverNamespace);
}
Copy the code
Take a look at the definition of its associated WebServer
/** * represents a configured WebServer, * * Simple interface that represents the configuration of the web server (for example Tomcat, * Jetty, Netty). Allows the server to be {@link #start() started} and {@link #stop()
* stopped}.
*/
public interface WebServer {
/** * Start the server */
void start(a) throws WebServerException;
/** * Stop the server */
void stop(a) throws WebServerException;
/** * returns the port on which the server listens */
int getPort(a);
/** * gracefully close */
default void shutDownGracefully(GracefulShutdownCallback callback) { callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE); }}Copy the code
WebServer is an abstraction of an embedded Servlet container, and it represents a fully configured Servlet container. In other words, the end user doesn’t need to care about the specifics of the container, just how to turn it on or off; And WebServerApplicationContext responsible for creating the WebServer, and as the end user at the right time to start or shut it down (that is, its lifecycle management).
ServletWebServerApplicationContext
ServletWebServerApplicationContext WebServerApplicationContext interface is achieved, and its creation and management for WebServer are concentrated in its own process, Namely ConfigurableApplicationContext# refresh () is called. Specifically, ServletWebServerApplicationContext fu wrote onRefresh (…). The hook method, which is called when:
BeanFactory
Initialization completeBeanDefinition
Parsing is completeNon-Lazy Init
The type ofBean
It’s not initialized yet
@Override
protected void onRefresh(a) {
super.onRefresh();
try {
/ / create the WebServer
createWebServer();
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex); }}private void createWebServer(a) {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
// webServer == null: No webServer instance has been created
// servletContext == null: no external container is used
if (webServer == null && servletContext == null) {
// 1. Get a factory to create WebServer
ServletWebServerFactory factory = getWebServerFactory();
// 2. Create a WebServer from the factory
this.webServer = factory.getWebServer(getSelfInitializer());
// 3. Listen for the lifecycle of ApplicationContext
// Gracefully close WebServer on ApplicationContext#stop()
getBeanFactory().registerSingleton("webServerGracefulShutdown".new WebServerGracefulShutdownLifecycle(this.webServer));
// 4. Listen for the lifecycle of ApplicationContext
// Start WebServer after all non-lazy Init beans have been initialized
// Close WebServer on ApplicationContext#stop()
getBeanFactory().registerSingleton("webServerStartStop".new WebServerStartStopLifecycle(this.this.webServer));
}
// External container
else if(servletContext ! =null) {
try {
getSelfInitializer().onStartup(servletContext);
} catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex); }}/ / initialize ServletContextPropertySource
// Expose the init-parameters of the ServletContext to the Environment
initPropertySources();
}
Copy the code
The time to create a WebServer is before the Bean of type non-lazy Init is initialized by getting a unique ServletWebServerFactory in the BeanFactory. Note that getSelfInitializer() is an important parameter that we’ll get to later.
We then register two SmartLifecycle type components into the BeanFactory to manage the WebServer life cycle, one for gracefully shutting down the WebServer and one for starting or stopping the WebServer.
class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
private final WebServer webServer;
private volatile boolean running;
WebServerGracefulShutdownLifecycle(WebServer webServer) {
this.webServer = webServer;
}
@Override
public void start(a) {
this.running = true;
}
@Override
public void stop(a) {
throw new UnsupportedOperationException("Stop must not be invoked directly");
}
@Override
public void stop(Runnable callback) {
// Close webServer gracefully
this.running = false;
this.webServer.shutDownGracefully((result) -> callback.run());
}
@Override
public boolean isRunning(a) {
return this.running; }}class WebServerStartStopLifecycle implements SmartLifecycle {
private final ServletWebServerApplicationContext applicationContext;
private final WebServer webServer;
private volatile boolean running;
WebServerStartStopLifecycle(ServletWebServerApplicationContext applicationContext, WebServer webServer) {
this.applicationContext = applicationContext;
this.webServer = webServer;
}
@Override
public void start(a) {
/ / start the webServer
this.webServer.start();
this.running = true;
this.applicationContext.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}
@Override
public void stop(a) {
/ / close the webServer
this.webServer.stop();
}
@Override
public boolean isRunning(a) {
return this.running;
}
@Override
public int getPhase(a) {
/ / control of # stop () calls after WebServerGracefulShutdownLifecycle# stop (Runnable)
return Integer.MAX_VALUE - 1; }}Copy the code
SmartLifecycle is the underlying component of spring-Context definition and is not the subject of this article. But to clarify the order of the calls, here is a brief description: It is driven by LifecycleProcessor, and ApplicationContext calls LifecycleProcessor#onRefresh() after all non-lazy Init beans have been initialized. And processing SmartLifecycle in it.
/ / the source code is located in the AbstractApplicationContext
protected void finishRefresh(a) {
// Clear context-level resource caches (such as ASM metadata from scanning).
clearResourceCaches();
// Initialize LifecycleProcessor
initLifecycleProcessor();
/ / callback LifecycleProcessor# onRefresh ()
// Call SmartLifecycle#start() one by one in onRefresh()
// There are, of course, some filtering conditions that we won't go into
getLifecycleProcessor().onRefresh();
/ / release ContextRefreshedEvent
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}
Copy the code
This completes the analysis of how the embedded Servlet container is started.
ServletContextInitializer
ServletWebServerFactory mentioned in front, a parameter when creating WebServer — getSelfInitializer (), it is of type ServletContextInitializer.
public interface ServletContextInitializer {
/** * Configure the ServletContext programmatically, such as registering servlets, filters, etc. */
void onStartup(ServletContext servletContext) throws ServletException;
}
Copy the code
ServletContextInitializer role similar to ServletContainerInitializer, the latter is the Servlet API provides standard initializer. We can also to configure the ServletContext in ServletContextInitializer, the difference is that its life cycle by the BeanFactory management rather than the Servlet container.
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer(a) {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
ServletContext and ApplicationContext are bound to each other
prepareWebApplicationContext(servletContext);
// 2. Register ServletContextScope
registerApplicationScope(servletContext);
// 3. Register ServletContext and its associated init-parameters in BeanFactory
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 4. Key: Dynamically register servlets, filters and other components in ServletContext through ServletRegistrationBean, FilterRegistrationBean, etc
for(ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); }}Copy the code
Step 1, step 2 and step 3 are relatively easy. Let’s take a look at step 4.
/ / initialization ServletContextInitializerBeans, it inherited from AbstractCollection
protected Collection<ServletContextInitializer> getServletContextInitializerBeans(a) {
return new ServletContextInitializerBeans(getBeanFactory());
}
// constructor
@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory, Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
this.initializerTypes = (initializerTypes.length ! =0)? Arrays.asList(initializerTypes) : Collections.singletonList(ServletContextInitializer.class);/ / all of the traverse the BeanFactory ServletContextInitializer
// Handle ServletRegistrationBean, FilterRegistrationBean, etc
// Finally add them into the initializers
addServletContextInitializerBeans(beanFactory);
// Iterate over the Servlet, Filter, and so on in the BeanFactory and wrap them into the corresponding RegistrationBean
// Finally add them into the initializers
addAdaptableBeans(beanFactory);
// Order by the Ordered interface or @order annotation
List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
/ / ServletContextInitializerBeans inherited from AbstractCollection, sortedList is its back to the list
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);
}
Copy the code
ServletContextInitializerBeans at the time of initialization will retrieve all of the the BeanFactory RegistrationBean; If native Servlet, Filter, or Servlet Listener type beans still exist in the BeanFactory, they are wrapped into the corresponding RegistrationBeans, and finally all registrationBeans are sorted. Let’s use the ServletRegistrationBean to see how it implements adding servlets to the ServletContext.
As you can see from the inheritance tree,ServletRegistrationBean
It’s also implementedServletContextInitializer
To check theonStartup(...)
methods
// The source code is located at RegistrationBean
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
// Get the description
String description = getDescription();
if(! isEnabled()) { logger.info(StringUtils.capitalize(description) +" was not registered (disabled)");
return;
}
// Register with servletContext
register(description, servletContext);
}
Copy the code
DynamicRegistrationBean implements register(…) methods
@Override
protected final void register(String description, ServletContext servletContext) {
/ / add the registration
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?) ");
return;
}
Registration / / configuration
configure(registration);
}
Copy the code
addRegistration(…) The final implementation is the ServletRegistrationBean
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
Copy the code
But is directly using the ServletContext. AddServlet (…). A Servlet was dynamically added and nothing more.
Afterword.
We analyzed when the embedded Servlet container was created and started, not how it was created. Take Tomcat as an example. The corresponding TomcatWebServer encapsulates the Tomcat API and provides configuration for Connector, ErrorPage and other components. However, I am not familiar with the architecture of these containers, so I am not blind