Preface
Recently, I plan to spend some time to look at Spring's source code, but now Spring's source code has too many iterated versions, which are huge and seem tired, so I'm going to start with the original version (interface21), only for learning, understanding its design ideas, and slowly studying the content of each version change.
Starting from a typical web engineering example of interface21, petclinic covers the main functions of Spring, such as APO, IOC, JDBC, Web MVC, transaction, internationalization, theme switching, parameter checking, etc.
Following the previous article, after learning about the ContextLoader Listener (loading Spring Web Application Context), see how the Dispatcher Servlet initialization process, the key controller of Spring mvc, is ~~~~
Corresponding web.xml configuration
<servlet> <servlet-name>petclinic</servlet-name> <servlet-class>com.interface21.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>petclinic</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping>
Here's one thing to note: the configuration of load-on-startup. For the meaning of the servlet parameter, you can first look at the explanation in the web-app_2_3.dtd file. The following basically means that if you don't configure load-on-startup or the value is negative, the servlet container is free to choose when to load the servlet (instantiate and execute the init method of the servlet), if For 0 or positive number, you must load the servlet and execute its init method at container startup. The smaller the value, the higher the priority of servlet loading. Looking back at our Dispatcher Servlet, because load-on-startup is configured as 1, the init method will be executed at startup, that is, the Dispatcher Servlet initialization process that this article focuses on:
<!-- The load-on-startup element indicates that this servlet should be loaded (instantiated and have its init() called) on the startup of the web application. The optional contents of these element must be an integer indicating the order in which the servlet should be loaded. If the value is a negative integer, or the element is not present, the container is free to load the servlet whenever it chooses. If the value is a positive integer or 0, the container must load and initialize the servlet as the application is deployed. The container must guarantee that servlets marked with lower integers are loaded before servlets marked with higher integers. The container may choose the order of loading of servlets with the same load-on-start-up value. Used in: servlet -->
Execute the sequence diagram (if you can't see clearly, you can click on the original map)
Brief analysis of each step in the sequence diagram
The entry to be executed is the init method of the HttpServletBean class. Since the load-on-startup parameter of the Dispatcher Servlet is configured as 1, the init method of the Servlet is automatically invoked when the Servlet container (tomcat) is started.
Step description:
- First, the init method of the parent HttpServletBean of Dispatcher Servlet is executed.
public final void init() throws ServletException { this.identifier = "Servlet with name '" + getServletConfig().getServletName() + "' "; logger.info(getIdentifier() + "entering init..."); // Set bean properties try { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), requiredProperties); BeanWrapper bw = new BeanWrapperImpl(this); bw.setPropertyValues(pvs); logger.debug(getIdentifier() + "properties bound OK"); // Let subclasses do whatever initialization they like initServletBean(); logger.info(getIdentifier() + "configured successfully"); } catch (BeansException ex) { String mesg = getIdentifier() + ": error setting properties from ServletConfig"; logger.error(mesg, ex); throw new ServletException(mesg, ex); } catch (Throwable t) { // Let subclasses throw unchecked exceptions String mesg = getIdentifier() + ": initialization error"; logger.error(mesg, t); throw new ServletException(mesg, t); } }
- Get the initialization parameters of the Servlet, create an instance of BeanWrapperImpl, and set the attribute values.
- Execute the initServletBean method of FrameworkServlet, a subclass of HttpServletBean;
protected final void initServletBean() throws ServletException { long startTime = System.currentTimeMillis(); logger.info("Framework servlet '" + getServletName() + "' init"); this.webApplicationContext = createWebApplicationContext(); initFrameworkServlet(); long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Framework servlet '" + getServletName() + "' init completed in " + elapsedTime + " ms"); }
- Call the createWebApplicationContext method of the Framework Servlet.
private WebApplicationContext createWebApplicationContext() throws ServletException { getServletContext().log("Loading WebApplicationContext for servlet '" + getServletName() + "'"); ServletContext sc = getServletConfig().getServletContext(); WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(sc); String namespace = getNamespace(); WebApplicationContext waca = (this.contextClass != null) ? instantiateCustomWebApplicationContext(this.contextClass, parent, namespace) : new XmlWebApplicationContext(parent, namespace); logger.info("Loading WebApplicationContext for servlet '" + getServletName() + "': using context class '" + waca.getClass().getName() + "'"); waca.setServletContext(sc); if (this.publishContext) { // Publish the context as a servlet context attribute String attName = getServletContextAttributeName(); sc.setAttribute(attName, waca); logger.info("Bound context of servlet '" + getServletName() + "' in global ServletContext with name '" + attName + "'"); } return waca; }
- Enter the createWebApplicationContext method and get the WebApplicationContext from the attributes of the ServletContext loaded by ContextLoaderListener
- Create a context Xml Web Application Context whose parent context is the Web Application Context loaded by the previous Context Loader Listener. For the relationship between these two contexts and the content responsible for loading, refer to this figure, image source ( http://jinnianshilongnian.iteye.com/blog/1602617/)
- Execute the waca.setServletContext method of context Xml Web Application Context to load the petclinic-servlet.xml configuration file (internationalization, theme, Handler Mapping, Handler Adapter, view parsing, etc.). Refer to the previous article on the loading process of context of Web Application Context. Spring Web Application Context Loading Process The process is the same.
- Set this context to the ServletContext property
- The initFrameworkServlet method that enters the Dispatcher Servlet class mainly performs some initialization work
protected void initFrameworkServlet() throws ServletException { initLocaleResolver(); initThemeResolver(); initHandlerMappings(); initHandlerAdapters(); initViewResolver(); }
- Internationalization-related: Execute the initLocaleResolver method, get the localeResolver bean from the context, and use the default AcceptHeaderLocaleResolver if not
private void initLocaleResolver() throws ServletException { try { this.localeResolver = (LocaleResolver) getWebApplicationContext().getBean(LOCALE_RESOLVER_BEAN_NAME); logger.info("Loaded locale resolver [" + this.localeResolver + "]"); } catch (NoSuchBeanDefinitionException ex) { // We need to use the default this.localeResolver = new AcceptHeaderLocaleResolver(); logger.info("Unable to locate locale resolver with name '" + LOCALE_RESOLVER_BEAN_NAME + "': using default [" + this.localeResolver + "]"); } catch (BeansException ex) { // We tried and failed to load the LocaleResolver specified by a bean throw new ServletException("Fatal error loading locale resolver with name '" + LOCALE_RESOLVER_BEAN_NAME + "': using default", ex); } }
- Topic-related: Execute the initThemeResolver method, get the themeResolver bean from the context, and use the default FixedThemeResolver if not
private void initThemeResolver() throws ServletException { try { this.themeResolver = (ThemeResolver) getWebApplicationContext().getBean(THEME_RESOLVER_BEAN_NAME); logger.info("Loaded theme resolver [" + this.themeResolver + "]"); } catch (NoSuchBeanDefinitionException ex) { // We need to use the default this.themeResolver = new FixedThemeResolver(); logger.info("Unable to locate theme resolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default [" + this.themeResolver + "]"); } catch (BeansException ex) { // We tried and failed to load the ThemeResolver specified by a bean throw new ServletException("Fatal error loading theme resolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default", ex); } }
- Execute the initHandlerMapping method to get the HandlerMapping type bean from the context, and use the default BeanNameUrlHandlerMapping if not
private void initHandlerMappings() throws ServletException { this.handlerMappings = new ArrayList(); // Find all HandlerMappings in the ApplicationContext String[] hms = getWebApplicationContext().getBeanDefinitionNames(HandlerMapping.class); for (int i = 0; i < hms.length; i++) { initHandlerMapping(hms[i]); logger.info("Loaded handler mapping [" + hms[i] + "]"); } // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. if (this.handlerMappings.isEmpty()) { initDefaultHandlerMapping(); logger.info("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } else { // We keep HandlerMappings in sorted order Collections.sort(this.handlerMappings, new OrderComparator()); } }
private void initDefaultHandlerMapping() throws ServletException { try { HandlerMapping hm = new BeanNameUrlHandlerMapping(); hm.setApplicationContext(getWebApplicationContext()); this.handlerMappings.add(hm); } catch (ApplicationContextException ex) { throw new ServletException("Error initializing default HandlerMapping: " + ex.getMessage(), ex); } }
- Execute the initHandler Adapters method to get the Handler Adapter type bean from the context, and use the default SimpleController Handler Adapter if not
private void initHandlerAdapters() throws ServletException { this.handlerAdapters = new ArrayList(); String[] has = getWebApplicationContext().getBeanDefinitionNames(HandlerAdapter.class); for (int i = 0; i < has.length; i++) { initHandlerAdapter(has[i]); logger.info("Loaded handler adapter [" + has[i] + "]"); } // Ensure we have at least one HandlerAdapter, by registering // a default HandlerAdapter if no other adapters are found. if (this.handlerAdapters.isEmpty()) { initDefaultHandlerAdapter(); logger.info("No HandlerAdapters found in servlet '" + getServletName() + "': using default"); } else { // We keep HandlerAdapters in sorted order Collections.sort(this.handlerAdapters, new OrderComparator()); } }
private void initDefaultHandlerAdapter() throws ServletException { try { HandlerAdapter ha = new SimpleControllerHandlerAdapter(); ha.setApplicationContext(getWebApplicationContext()); this.handlerAdapters.add(ha); } catch (ApplicationContextException ex) { throw new ServletException("Error initializing default HandlerAdapter: " + ex.getMessage(), ex); } }
- Execute the initViewResolver method to get the viewResolver bean from the context, and use the default InternalResourceViewResolver if not
private void initViewResolver() throws ServletException { try { this.viewResolver = (ViewResolver) getWebApplicationContext().getBean(VIEW_RESOLVER_BEAN_NAME); logger.info("Loaded view resolver [" + viewResolver + "]"); } catch (NoSuchBeanDefinitionException ex) { // We need to use the default this.viewResolver = new InternalResourceViewResolver(); try { this.viewResolver.setApplicationContext(getWebApplicationContext()); } catch (ApplicationContextException ex2) { throw new ServletException("Fatal error initializing default ViewResolver"); } logger.info("Unable to locate view resolver with name '" + VIEW_RESOLVER_BEAN_NAME + "': using default [" + this.viewResolver + "]"); } catch (BeansException ex) { // We tried and failed to load the ViewResolver specified by a bean throw new ServletException("Fatal error loading view resolver: bean with name '" + VIEW_RESOLVER_BEAN_NAME + "' is required in servlet '" + getServletName() + "': using default", ex); } }
- Return to the Framework Servlet class
- Return to the HttpServletBean class
- Servlet's init method has been executed
In addition, we can pay attention to the destruction method of the Servlet. Similarly, we can also perform some operations such as resource destruction, destroy the single bean object created by the factory, publish ContextClosedEvent events, etc.
public void destroy() { getServletContext().log("Closing WebApplicationContext for servlet '" + getServletName() + "'"); getWebApplicationContext().close(); }
public void close() { logger.info("Closing application context [" + getDisplayName() + "]"); // destroy all cached singletons in this context, // invoking DisposableBean.destroy and/or "destroy-method" getBeanFactory().destroySingletons(); // publish respective event publishEvent(new ContextClosedEvent(this)); }