A previous article introduced some important contexts in Spring. See the previous article for some of the contexts mentioned in this article.
A big part of the SpringBoot project’s ease of deployment is that you don’t have to mess with Tomcat configuration yourself, because it comes with Servlet containers built in. Always curious about how SpringBoot can launch a container and deploy itself to it by simply running a main function. This article wants to sort out this problem clearly.
We analyze from SpringBoot boot entry:
The Context to create
1// Create.load.refresh and run the ApplicationContext
2context = createApplicationContext();
Copy the code
One of the most important steps we found in SpringBoot’s Run method is the above line of code. The comments are clear:
Create, load, refresh, and run ApplicationContext.
Keep going inside.
1protected ConfigurableApplicationContext createApplicationContext() {
2Class<? > contextClass =this.applicationContextClass;
3 if (contextClass == null) {
4 try {
5 contextClass = Class.forName(this.webEnvironment
6 ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
7 }
8 catch (ClassNotFoundException ex) {
9 throw new IllegalStateException(
10 "Unable create a default ApplicationContext, "
11 + "please specify an ApplicationContextClass".
12 ex);
13 }
14 }
15 return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
16}
Copy the code
The logic is clear:
Find the Context class and instantiate it using utility methods.
Line 5 has a judgment: if the environment is Web, load the DEFAULT _WEB_CONTEXT_CLASS class. See the member variable definition, whose class name is:
1AnnotationConfigEmbeddedWebApplicationContext
Copy the code
The inheritance structure of this class is shown below:
Direct GenericWebApplicationContext inheritance. This class was introduced earlier, just remember that it is specifically designed to provide context for Web Applications.
refresh
After the Context has been created and some of its columns initialized, the refresh method of the Context is called and the real fun begins.
From the front, we can see AnnotationConfigEmbeddedWebApplicationContext inheritance structure, call the refresh method, will directly from its parent class: EmbeddedWebApplicationContext to execute.
1@Override
2protected void onRefresh(a) {
3 super.onRefresh();
4 try {
5 createEmbeddedServletContainer();
6 }
7 catch (Throwable ex) {
8 throw new ApplicationContextException("Unable to start embedded container".
9 ex);
10 }
11}
Copy the code
Let’s focus on line 5.
1private void createEmbeddedServletContainer() {
2 EmbeddedServletContainer localContainer = this.embeddedServletContainer;
3 ServletContext localServletContext = getServletContext();
4 if (localContainer == null && localServletContext == null) {
5 EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
6 this.embeddedServletContainer = containerFactory
7 .getEmbeddedServletContainer(getSelfInitializer());
8 }
9 else if(localServletContext ! =null) {
10 try {
11 getSelfInitializer().onStartup(localServletContext);
12 }
13 catch (ServletException ex) {
14 throw new ApplicationContextException("Cannot initialize servlet context".
15 ex);
16 }
17 }
18 initPropertySources();
19}
Copy the code
Code line 5, access to a EmbeddedServletContainerFactory, as the name implies, its function is to create an embedded servlet container: next EmbeddedServletContainer.
1public interface EmbeddedServletContainerFactory {
2
3 / * *
4* Create a fully configured instance that is currently in the "Pause" state.
5* The Client cannot establish a connection with its start method until it is called.
6* /
7 EmbeddedServletContainer getEmbeddedServletContainer(
8 ServletContextInitializer... initializers);
9
10}
Copy the code
In lines 6 and 7, when containerFactory gets EmbeddedServletContainer, the arguments are the result of the getSelfInitializer function. Temporarily regardless of its internal mechanism, just know that it will return a ServletContextInitializer initialization for container object, we continue to look down.
Because EmbeddedServletContainerFactory is an abstract factory, different containers have different implementations, because SpringBoot default using Tomcat, So in Tomcat factory implementation class TomcatEmbeddedServletContainerFactory analysis:
1@Override
2public EmbeddedServletContainer getEmbeddedServletContainer(
3 ServletContextInitializer... initializers) {
4 Tomcat tomcat = new Tomcat();
5 File baseDir = (this.baseDirectory ! =null ? this.baseDirectory
6 : createTempDir("tomcat"));
7 tomcat.setBaseDir(baseDir.getAbsolutePath());
8 Connector connector = new Connector(this.protocol);
9 tomcat.getService().addConnector(connector);
10 customizeConnector(connector);
11 tomcat.setConnector(connector);
12 tomcat.getHost().setAutoDeploy(false);
13 tomcat.getEngine().setBackgroundProcessorDelay(- 1);
14 for (Connector additionalConnector : this.additionalTomcatConnectors) {
15 tomcat.getService().addConnector(additionalConnector);
16 }
17 prepareContext(tomcat.getHost(), initializers);
18 return getTomcatEmbeddedServletContainer(tomcat);
19}
Copy the code
Lines 8 through 16 complete the addition of tomcat’s connector. The Tomcat connector is mainly used to process HTTP requests. The specific principle can be seen in the tomcat source code, not mentioned here.
The method on line 17 is a bit long, so focus on a few lines:
1if (isRegisterDefaultServlet()) {
2 addDefaultServlet(context);
3}
4if (isRegisterJspServlet() && ClassUtils.isPresent(getJspServletClassName(),
5 getClass().getClassLoader())) {
6 addJspServlet(context);
7 addJasperInitializer(context);
8 context.addLifecycleListener(new StoreMergedWebXmlListener());
9}
10ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
11configureContext(context, initializersToUse);
Copy the code
The first two branch judgments add the default servlet class and the JSP-related servlet class.
For all ServletContextInitializer after the merger, using the combined initialization configuration on the context.
Line 18, follow the method all the way down to officially start Tomcat.
1private synchronized void initialize(a) throws EmbeddedServletContainerException {
2 TomcatEmbeddedServletContainer.logger
3 .info("Tomcat initialized with port(s): " + getPortsDescription(false));
4 try {
5 addInstanceIdToEngineName();
6
7 // Remove service connectors to that protocol binding doesn't happen yet
8 removeServiceConnectors();
9
10 // Start the server to trigger initialization listeners
11 this.tomcat.start();
12
13 // We can re-throw failure exception directly in the main thread
14 rethrowDeferredStartupExceptions();
15
16 // Unlike Jetty, all Tomcat threads are daemon threads. We create a
17 // blocking non-daemon to stop immediate shutdown
18 startDaemonAwaitThread();
19 }
20 catch (Exception ex) {
21 throw new EmbeddedServletContainerException("Unable to start embedded Tomcat".
22 ex);
23 }
24}
Copy the code
Line 11 officially starts Tomcat.
Now let’s go back to the getSelfInitializer method:
1private ServletContextInitializer getSelfInitializer(a) {
2 return new ServletContextInitializer() {
3 @Override
4 public void onStartup(ServletContext servletContext) throws ServletException {
5 selfInitialize(servletContext);
6 }
7 };
8}
Copy the code
1private void selfInitialize(ServletContext servletContext) throws ServletException {
2 prepareEmbeddedWebApplicationContext(servletContext);
3 ConfigurableListableBeanFactory beanFactory = getBeanFactory();
4 ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
5 beanFactory);
6 WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
7 getServletContext());
8 existingScopes.restore();
9 WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
10 getServletContext());
11 for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
12 beans.onStartup(servletContext);
13 }
14}
Copy the code
In line 2 prepareEmbeddedWebApplicationContext method mainly set EmbeddedWebApplicationContext to rootContext.
Line 4 allows the user to store custom scopes.
Line 6 is mainly used to register web-specific scopes into the BeanFactory, such as (“request”, “session”, “globalSession”, “application”).
Line 9 registers the Web-specific environment bean (such as (“contextParameters”, “contextAttributes”)) into the given BeanFactory.
Lines 11 and 12, important, are used to configure servlets, filters, listeners, context-param, and some of the necessary attributes for initialization.
With an implementation class ServletContextInitializer describe a case of:
1@Override
2public void onStartup(ServletContext servletContext) throws ServletException {
3 Assert.notNull(this.servlet, "Servlet must not be null");
4 String name = getServletName();
5 if(! isEnabled()) {
6 logger.info("Servlet " + name + " was not registered (disabled)");
7 return;
8 }
9 logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);
10 Dynamic added = servletContext.addServlet(name, this.servlet);
11 if (added == null) {
12 logger.info("Servlet " + name + " was not registered "
13 + "(possibly already registered?) ");
14 return;
15 }
16 configure(added);
17}
Copy the code
See the print on line 9: this is where the servlet-to-URlmapping is implemented.
conclusion
This article analyzes the main context of why Tomcat is not configured in SpringBoot, how the built-in container is started, and by the way, we found the implementation of urlMapping mapping Servlet commonly used in the process of analysis.