The spring MVC annotation starts the servlet

Tip: after the article is written, the directory can be generated automatically. Please refer to the help document on the right for how to generate it

preface

Learning record: understanding of tomcat

Tip: the following is the main content of this article. The following cases can be used for reference

1, XML configuration Servlet

ContextLoaderListener implements the contextInitialized method of ServletContextListener interface. This is the key to starting spring.

Dispatcher servlet is the key to start spring MVC. The inheritance relationship of dispatcher servlet is as follows
HttpServlet<-HttpServletBean<-FrameworkServlet<-DispatcherServlet;

The above two classes are indispensable.
In the early days, we loaded two classes through xml configuration. As follows:

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>spring-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-dispatcher.xml</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
  </servlet>

How does tomcat integrate with spring? Next, let's take a look at the spi mechanism of tomcat

2, Tomcat loading mechanism: SPI

Let's take an example to see how tomcat implements SPI.

public class SpringApplication {

    public static void main(String[] args) throws LifecycleException, ServletException {
        run(SpringApplication.class, args);
    }


    public static void run(Object source, String... args) {
        try {
            // Tomcat container
            Tomcat tomcatServer = new Tomcat();
            // Port number
            tomcatServer.setPort(9090);
            // Read project path and load static resources
            StandardContext ctx = (StandardContext) tomcatServer.addWebapp("/", new File("src/main").getAbsolutePath());
            // Reload prohibited
            ctx.setReloadable(false);
            // class file read address
            File additionWebInfClasses = new File("spring-source/target/classes");
            // Create WebRoot
            WebResourceRoot resources = new StandardRoot(ctx);
            // tomcat internal read Class execution
            resources.addPreResources(
                    new DirResourceSet(resources, "/spring-demo/WEB-INF/classes", additionWebInfClasses.getAbsolutePath(), "/"));
            tomcatServer.start();
            // Wait for synchronization request
            tomcatServer.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }
}

So far, a tomcat startup is completed. Next, we need to remove the servlet and listener from the configuration file.

  1. When Tomcat starts, it will automatically load / resources / meta-inf / services / javax servlet. Servletcontainerinitializer file, equivalent to an SPI
//tomcat will automatically load all classes that implement LoadServlet and put them in the set container. And call the onStartup method
@HandlesTypes(LoadServlet.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        Iterator var4;
        if (set != null) {
            var4 = set.iterator();
            while (var4.hasNext()) {
                Class<?> clazz = (Class<?>) var4.next();
                if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && LoadServlet.class.isAssignableFrom(clazz)) {
                    try {
                    	//Here, we call the method of loadOnstarp implemented by ourselves
                        ((LoadServlet) clazz.newInstance()).loadOnstarp(servletContext);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

public interface LoadServlet {
    void loadOnstarp(ServletContext servletContext);
}
public class LoadServletImpl implements LoadServlet {
    @Override
    public void loadOnstarp(ServletContext servletContext) {
        ServletRegistration.Dynamic initServlet = servletContext.addServlet("initServlet", "com.test.servlet.InitServlet");
        initServlet.setLoadOnStartup(1);
        initServlet.addMapping("/init");
    }
}

In LoadServletImpl, we inject servlet

3, Implementation principle of Spring mvc loading


Spring web also has a configuration file: meta-inf / services / javax servlet. Servletcontainerinitializer. Spring servletcontainerinitializer will be loaded when tomcat starts. Let's see what this class does.

//tomcat will automatically load all classes that implement WebApplicationInitializer and put them in the set container. And call the onStartup method
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

To put it simply, this class actually loads all classes that implement the WebApplicationInitializer interface and then calls the onStartUp method. Let's first look at the class AbstractContextLoaderInitializer

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}
protected void registerContextLoaderListener(ServletContext servletContext) {

		//Create spring context and register spring container
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
			//Create listener
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			listener.setContextInitializers(getRootApplicationContextInitializers());
			servletContext.addListener(listener);
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}
}

You can see that this class actually registers listeners for us. Next, let's take a look at AbstractDispatcherServletInitializer

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		super.onStartup(servletContext);
		//Register DispatcherServlet
		registerDispatcherServlet(servletContext);
	}
protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");

		//Create the context of springmvc and register the MvcContainer class
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

		//Create DispatcherServlet
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
					"Check if there is another servlet registered under the same name.");
		}

		/*
		* If the value of the element is negative or not set, the container will reload when the Servlet is requested.
			If the value is a positive integer or 0, it means that the container loads and initializes the servlet when the application starts,
			The smaller the value, the higher the priority of the servlet, and the earlier it is loaded
		* */
		registration.setLoadOnStartup(1);
		registration.addMapping(getServletMappings());
		registration.setAsyncSupported(isAsyncSupported());

		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}

		customizeRegistration(registration);
	}
}

You can see that this class actually registers the dispatcher servlet for us.

Now that we have registered Listener and servlet, let's see where to load our spring container.

4, Load spring container

After tomcat initialization is completed, contextInitialized in ContextLoaderListener will be called.
The call chain is:
ContextLoaderListener -> contextInitialized
-> initWebApplicationContext -> configureAndRefreshWebApplicationContext -> onrefresh
Finally, we will call the spring refresh method to initialize the spring container. Here are some important process codes.

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					//Important, this is actually initializing the spring container
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			//Put the spring container in the serveretcontext
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}

In fact, this is to call the spring refresh method, initialize the spring container and put it in the ServletContext container for subsequent calls. Next, let's take a look at the DispatchServlet. As we all know, the init method will be called by tomcat after the Servlet is instantiated,
The call chain is as follows:
init -> initServletBean -> initWebApplicationContext -> configureAndRefreshWebApplicationContext -> refresh()

protected WebApplicationContext initWebApplicationContext() {
		//Here, we will get the parent container from the servletContext, which is the container loaded through the listener
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						//If the parent container is empty, set the container loaded by the listener as the parent container.
						cwac.setParent(rootContext);
					}
					//Container loading
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

It should be noted here that ServletContextListener helps us instantiate an IOC container, and dispatcher servlet actually instantiates another IOC container. listener's spring mvc container mainly manages our controller class, and servlet is the spring container mainly manages service and repo classes. There is a father son relationship between them. spring mvc is the child and spring is the parent. The child can call the parent bean.

summary

This article mainly explains how spring registers listeners and servlets without xml files. Here, tomcat's spi mechanism is mainly used. The second is to explain how to load the spring container into the Servlet Context.
The relationship between servletContext, spring and spring MVC is as follows
servletContext owns spring mvc and spring mvc owns spring.

Keywords: Java Tomcat

Added by ravravid on Wed, 02 Feb 2022 11:05:26 +0200