0x11.Spring MVC框架启动原理简述

您所在的位置:网站首页 servlet和spring框架的关系 0x11.Spring MVC框架启动原理简述

0x11.Spring MVC框架启动原理简述

2023-03-26 17:25| 来源: 网络整理| 查看: 265

Based on Spring Framework 4.3.8 RELEASE

[TOC]

Spring MVC的入口

由servlet规范可知,servlet容器对一个java web应用解析的入口有以下几种:

WEB-INFO/web.xml servlet 3.0提供的注解 web.xml与jar中web-fragment.xml jar中的ServletContainerInitializer接口实现类

servlet容器通过解析以上的一个或多个配置来获取servlet等组件的信息,并将这些组件注册到servletcontext中,以支撑web工作。

Spring MVC作为web的框架,提供由servlet容器解析的配置,有两种方案,一种是使用ServletContextListener的事件监听机制来完成框架的初始化,另一种方式是实现ServletContainerInitializer接口。第一种可以借助web.xml注册listener或者使用注解来注册listener,第二种与web.xml无关,是经由Jar service API来发现的。两种机制是不同的,后者的执行时机是在所有的Listener触发之前。这些都是servlet规范的实现,具体的逻辑都在内部的接口方法实现中。

ServletContextListener 接口定义 public interface ServletContextListener extends EventListener { public void contextInitialized(ServletContextEvent sce); public void contextDestroyed(ServletContextEvent sce); }

ServletContextListener是容器生命周期的监听器,启动会触发此监听器的contextInitialized()方法,容器销毁时会触发contextDestroyed()方法。Spring提供了此接口的实现:ContextLoaderListener:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } /** * Initialize the root web application context. */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } /** * Close the root web application context. */ @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }

使用ContextLoaderListener方式,只需将此listener注册到ServletContext上即可,即在web.xml中配置此listener。

org.springframework.web.context.ContextLoaderListener contextConfigLocation classpath:spring.xml

如果想使用注解来摒弃web.xml配置文件,可以使用如下方式(因为spring的jar包不好直接修改代码加上注解,但是可以通过继承来使用):

@WebListener public class MyContextListener extends ContextLoaderListener { @Override public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // 这里指定配置文件的路径 servletContext.setInitParameter("contextConfigLocation", "classpath:spring.xml"); return super.initWebApplicationContext(servletContext); } }

在此方式下,ContextLoaderListener#contextInitialized()是Spring MVC的启动入口。

contextInitialized方法内部调用的是父类ContextLoader的initWebApplicationContext()方法:

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!"); } Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext"); 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) { // 这里会创建WebApplicationContext对象,是Spring mvc中的ROOT IOC容器 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); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } // 把spring上下文放到ServletContext属性中,这样可以作为全局可访问的变量 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); //... return this.context; } // ... }

这一步主要逻辑是创建root IOC容器并通过读取配置的spring.xml配置文件初始化,这一步是Spring IOC 容器的启动和初始化,实际上和MVC的关系不大。在实际使用的时候也可以不使用这个root WebApplicationContext,而仅仅使用后面的DispatcherServlet中创建的子容器。之所以使用父子容器,一方面是将不同的bean放到不同的容器中,将如DataSource等通用Bean放在父容器,web专用的bean放在子容器中,一方面结构清晰,也便于更换web框架,另外由于父子容器的访问机制,可以隔离不同的层次。

创建完成后将此WebApplicationContext作为ServletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性置于全局的环境。

由笔记《Servlet和Spring MVC》可知,Listener注册之后是filter的注册,最后是最为核心的Servlet:DispatcherServlet的注册了,web.xml方式下的配置如下:

spring org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:spring.xml 1 spring *.do

和前面使用注解来取代此方式的想法一样,我们自定义一个类继承DispatcherServlet:

@WebServlet(value = "*.do", loadOnStartup = 1, initParams = { @WebInitParam(name = "contextConfigLocation", value = "classpath:spring.xml") }) public class CustomDispatcherServlet extends DispatcherServlet { private static final long serialVersionUID = 1L; }

实际上也可以在前面的listener里面使用ServletContext接口的addServlet()方法来注册servlet。

既然DispatcherServlet也是Servlet,那么必然遵循Servlet规范,在容器启动后会调用其init方法初始化,将其注册到ServletContext上。请求到达时触发service方法来响应请求(通过HttpServlet,一般实现是doGet等方法)。 那么DispatcherServlet的入口也是init方法,先看下它的类结构层次:

public class DispatcherServlet extends FrameworkServlet ; public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware; public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware;

从类层次来看,DispatcherServlet间接实现了ApplicationContextAware、EnvironmentCapable、EnvironmentAware接口,说明它能够获取这些aware对象,中间设计的抽象类FrameworkServlet、HttpServletBean填充了具体的通用逻辑,看下init方法(在HttpServletBean中):

// final方法,不可重写 public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. try { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } // Let subclasses do whatever initialization they like. // 初始化Servlet initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }

可以看出逻辑就是读取配置的init-param,然后调用模板方法initServletBean()来实现具体的初始化逻辑: 子类FrameWorkServlet的实现:

protected final void initServletBean() throws ServletException { // ... try { // 核心逻辑入口 this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } // .. } protected WebApplicationContext initWebApplicationContext() { // 这里是获取在ContextLoaderListener中创建的ROOT context WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; // ... if (wac == null) { // 创建一个子context wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { onRefresh(wac); } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); // 把DispatcherServlet的IOC容器也注册成ServletContext的属性 // 属性名为org.springframework.web.servlet.FrameworkServlet.CONTEXT. getServletContext().setAttribute(attrName, wac); } return wac; } // 创建WebApplicationContext protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { // 获取配置的ApplicationContext的class,默认是 // org.springframework.web.context.support.XmlWebApplicationContext Class contextClass = getContextClass(); if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "'" + ", using parent context [" + parent + "]"); } if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } // 反射创建context对象 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); // 设置父容器为ContextLoaderListener中创建的root容器 wac.setParent(parent); // 设置配置location(即在DispatcherServlet上配置的configConfiguration参数的值) wac.setConfigLocation(getContextConfigLocation()); // refresh,就是初始化IOC容器的逻辑了 configureAndRefreshWebApplicationContext(wac); return wac; }

从DispatcherServlet的初始化过程看,这一步的主要逻辑除了注册servlet外,还有的就是和Spring IOC的融合,创建子容器的逻辑。从Spring MVC的整个流程看,就是在普通的servlet等组件的注册上,结合了Spring IOC的特性,IOC是Spring MVC的基石,很多其他功能都是基于IOC来完成的。

DispatcherServlet的url-pattern匹配一系列的请求url,在内部处理请求的时候会再次分发给各个对应的controller的方法,这部分具体逻辑后面再描述。

ServletContainerInitializer

此方式是基于jar service API的方法,需要在jar文件内包含/META-INF/services/javax.servlet.ServletContainerInitializer,且该文件内容为ServletContainerInitializer的实现类的全限定名。 在spring-web--RELEASE.jar内包含该文件,文件内容为org.springframework.web.SpringServletContainerInitializer。

此接口的onStartup方法的触发是在ServletContextListener触发之前的,且与web.xml互相独立。也就是初始化流程是:

执行ServletContainerInitializer的onStartup()方法 执行初始化所有的listener 执行ServletContextListener的contextInitialized()方法 实例化filter并执行init方法 按照定义Servlet是设置的load-on-startup的数字从小到大顺序实例化Servlet,调用init方法 ServletContainerInitializer接口定义

javadoc中注明了使用规范:

/** * Interface which allows a library/runtime to be notified of a web * application's startup phase and perform any required programmatic * registration of servlets, filters, and listeners in response to it. * *

Implementations of this interface may be annotated with * {@link javax.servlet.annotation.HandlesTypes HandlesTypes}, in order to * receive (at their {@link #onStartup} method) the Set of application * classes that implement, extend, or have been annotated with the class * types specified by the annotation. * *

If an implementation of this interface does not use HandlesTypes * annotation, or none of the application classes match the ones specified * by the annotation, the container must pass a null Set of classes * to {@link #onStartup}. * *

When examining the classes of an application to see if they match * any of the criteria specified by the HandlesTypes annontation * of a ServletContainerInitializer, the container may run into * classloading problems if any of the application's optional JAR * files are missing. Because the container is not in a position to decide * whether these types of classloading failures will prevent * the application from working correctly, it must ignore them, * while at the same time providing a configuration option that would * log them. * *

Implementations of this interface must be declared by a JAR file * resource located inside the META-INF/services directory and * named for the fully qualified class name of this interface, and will be * discovered using the runtime's service provider lookup mechanism * or a container specific mechanism that is semantically equivalent to * it. In either case, ServletContainerInitializer services from web * fragment JAR files excluded from an absolute ordering must be ignored, * and the order in which these services are discovered must follow the * application's classloading delegation model. * * @see javax.servlet.annotation.HandlesTypes * * @since Servlet 3.0 */ public interface ServletContainerInitializer { public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List initializers = new LinkedList(); // 找到所有实现WebApplicationInitializer的类,筛去接口和抽象类后把剩下的放入list中 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) 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); // 调用web应用中的初始化器来执行初始化逻辑,执行初始化器的onStartup()方法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }

onStartup()方法的内部逻辑很简单,就是找到web中定义的所有WebApplicationInitializer实现类,执行这些初始化类的onStartup()方法来执行初始化逻辑。

WebApplicationInitializer接口 public interface WebApplicationInitializer { void onStartup(ServletContext servletContext) throws ServletException; }

接口定义和ServletContainerInitializer相似,Spring中类层次:

从上往下有名字可以确定,Spring提供的三个initializer都是抽象类,所以是没法直接使用的,如果使用此方式来配置使用Spring MVC,需要我们实现(间接)WebApplicationInitializer来完成ServletContainerInitializer中的初始化逻辑。这种方式可以完全摒弃xml配置文件,包括web.xml和spring的xml,可以完全使用JavabBased方式配置。

另外从三个抽象类的名字可以看出,AbstractContextLoaderInitializer主要做的事情是初始化ContextLoaderListener, AbstractDispatcherServletInitializer则是初始化DispatcherServlet;前面两个是主要的初始化器,而且做得事情和之前使用web.xml或者注解来做的注册组件的事情一样,初始化ContextLoaderListener和DispatcherServlet。

AbstractAnnotationConfigDispatcherServletInitializer依然是初始化DispatcherServlet,是给前面两个抽象类的抽象方法的实现,是和@Configuration配置类协作的初始化类,它是使用Java-Based方式使用configClass和注解来配置spring容器的抽象实现(结合AnnotationConfigWebApplicationContext上下文实现)。

简要了解各个实现类的方法:

AbstractContextLoaderInitializer @Override public void onStartup(ServletContext servletContext) throws ServletException { registerContextLoaderListener(servletContext); } // 核心逻辑 protected void registerContextLoaderListener(ServletContext servletContext) { // 创建根ApplicationContext WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { // ContextLoaderListener是ServletContextListener的实现类 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); // Spring 4.2中新增的方法,提供context初始化器来介入Context的初始化 listener.setContextInitializers(getRootApplicationContextInitializers()); // 注册ServletContextListener监听器到ServletContext上 servletContext.addListener(listener); } else { logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not return an application context"); } }

留给子类实现的抽象方法(模板模式的使用):

protected abstract WebApplicationContext createRootApplicationContext(); /** * Specify application context initializers to be applied to the root application * context that the {@code ContextLoaderListener} is being created with. * @since 4.2 * @see #createRootApplicationContext() * @see ContextLoaderListener#setContextInitializers */ protected ApplicationContextInitializer[] getRootApplicationContextInitializers() { return null; }

可见此类的主要逻辑是创建根Spring容器,然后注册ServletContextListener(ContextLoaderListener)到ServletContext上,和使用ContextLoaderListener作为入口的方式相似,只是创建IOC容器的逻辑在listener的外面。

AbstractDispatcherServletInitializer @Override public void onStartup(ServletContext servletContext) throws ServletException { // 执行父类逻辑 super.onStartup(servletContext); // 注册DispatcherServlet registerDispatcherServlet(servletContext); }

抽象方法:

// 创建WebApplicationContext protected abstract WebApplicationContext createServletApplicationContext(); // 获取映射规则 protected abstract String[] getServletMappings();

其实类中的其他protected方法都是留给子类重写来自定义逻辑的,是模板方法的大量应用。 如:

// 自定义注册逻辑 protected void customizeRegistration(ServletRegistration.Dynamic registration) { } // 返回Filter,实际上是返回到方法中注册的filter protected Filter[] getServletFilters() { return null; } /** * Specify application context initializers to be applied to the servlet-specific * application context that the {@code DispatcherServlet} is being created with. * @since 4.2 * @see #createServletApplicationContext() * @see DispatcherServlet#setContextInitializers * @see #getRootApplicationContextInitializers() */ protected ApplicationContextInitializer[] getServletApplicationContextInitializers() { return null; }

此类的主要功能是在父类注册ContextLoaderListener(实际上就是创建RootApplicationContext和设置Listener)的基础上,注册DispaerServlet,以及提供配置映射规则和配置filter和其他自定义注册逻辑,且在这一步创建了DispatcherServlet的IOC容器,和前面web.xml中在DispatcherServlet的init逻辑中创建IOC容器的逻辑类似。

AbstractAnnotationConfigDispatcherServletInitializer

它实现了父类中的两个抽象方法:createRootApplicationContext(),createServletApplicationContext(),也就是Spring MVC中的两个ApplicationContext,可以发现创建的内部逻辑借助了getRootConfigClasses()和getServletConfigClasses()来获取配置class,然后通过AnnotationConfigWebApplicationContext类来创建Context,这些配置类肯定是需要开发者自定义的,自然得交给子类来填充的,且必须填充,很自然的是抽象方法。

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer { /** * {@inheritDoc} *

This implementation creates an {@link AnnotationConfigWebApplicationContext}, * providing it the annotated classes returned by {@link #getRootConfigClasses()}. * Returns {@code null} if {@link #getRootConfigClasses()} returns {@code null}. */ @Override protected WebApplicationContext createRootApplicationContext() { // 获取根ApplicationContext的配置类 Class[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { // 创建根IOC容器 AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); // 注册配置类 rootAppContext.register(configClasses); return rootAppContext; } else { return null; } } /** * {@inheritDoc} *

This implementation creates an {@link AnnotationConfigWebApplicationContext}, * providing it the annotated classes returned by {@link #getServletConfigClasses()}. */ @Override protected WebApplicationContext createServletApplicationContext() { // 创建DispatcherServlet类的IOC容器,也就是子容器 AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext(); Class[] configClasses = getServletConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { servletAppContext.register(configClasses); } return servletAppContext; } // 交给子类通过配置类的信息 protected abstract Class[] getRootConfigClasses(); protected abstract Class[] getServletConfigClasses(); } 自定义Initializer实现类

自定义实现类一般是实现第三层的抽象类AbstractAnnotationConfigDispatcherServletInitializer,最基本的就是实现父类的两个抽象方法和AbstractDispatcherServletInitializer的getServletMappings() 即可,这些都是需要依据实际的web应用来设置的,如下示例:

public class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // 也可以不使用父IOC容器直接return null即可。 // 是ContextLoaderListener初始化的模板方法实现 @Override protected Class[] getRootConfigClasses() { return new Class[] { RootConfig.class }; } // 是AbstractAnnotationConfigDispatcherServletInitializer的模板方法的实现 @Override protected Class[] getServletConfigClasses() { return new Class[] { WebConfig.class }; } // AbstractDispatcherServletInitializer的模板方法实现 @Override protected String[] getServletMappings() { return new String[] { "*.do" }; } }

一般除此之外可能要添加过滤器或者listener,只需要重写上面的抽象类的某些方法就可以了。除上面必须实现的三个方法之外,还可以重写的方法有:

这些方法的作用都从名字可以明显看出来。

另外如果不想使用AnnotationConfigWebApplicationContext类作为IOC容器的实现类,可以继承AbstractDispatcherServletInitializer实现createServletApplicationContext()和createRootApplicationContext(),在内部实现具体的ApplicationContext创建逻辑。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3