0
点赞
收藏
分享

微信扫一扫

[Spring MVC]Spring MVC与Servlet标准及总体设计思想

Tomcat容器

spring整合Tomcat经常看到的web.xml

<web-app>

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

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

Servler3.0后,提供了注解+SPI的机制来配置servlet.

ServletContainerInitializer
WebApplicationInitializer和SpringServletContainerInitializer
  • org.springframework.web.SpringServletContainerInitializer#onStartup
@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);
    // 激活WebApplicationInitializer#onStartup
    for (WebApplicationInitializer initializer : initializers) {
        initializer.onStartup(servletContext);
    }
}

SpringServletContainerInitializer实现了ServletContainerInitializer,在其onStartup方法中,通过@HandlesTypes(WebApplicationInitializer.class)注入WebApplicationInitializer到Tomcat容器.激活WebApplicationInitializer#onStartup.所以实现了WebApplicationInitializer的类都会被加载.

WebApplicationInitializer家族成员

Servlet WebApplicationContext 和 Root WebApplicationContext

  • org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    // 获取配置类并且注入到容器中
    Class<?>[] configClasses = getServletConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        context.register(configClasses);
    }
    return context;
}
  • org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            context.register(configClasses);
            return context;
        }
        else {
            return null;
        }
    }

Servlet WebApplicationContext:
将Controller、ViewResolver、HandleMapping相关的类扫描进Servlet的web容器上下文中.
Root WebApplicationContext:
将Service、Repositories.这类对象交由Root WebApplicationContext管理.

Spring MVC的大致流程

  • 建立请求(RequestMapping)和Controller方法的映射集合的流程
  • 根据请求(RequestURI)查找对应的Controller方法的流程
  • 请求参数绑定到方法形参,执行方法处理请求,渲染视图

流程图

注解配置的容器入口-AbstractDispatcherServletInitializer

注解的配置会优于XML的配置执行.SpringServletContainerInitializer实现了ServletContainerInitializer接口,通过@HandlesTypes(WebApplicationInitializer.class)的SPI发现机制,可以将实现该接口的Class对象传递到onStartup中.

  • org.springframework.web.SpringServletContainerInitializer#onStartup
@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);
    // 激活WebApplicationInitializer#onStartup
    for (WebApplicationInitializer initializer : initializers) {
        initializer.onStartup(servletContext);
    }
}

  • org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    super.onStartup(servletContext);
    registerDispatcherServlet(servletContext);
}
  • org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#registerDispatcherServlet
protected void registerDispatcherServlet(ServletContext servletContext) {
    String servletName = getServletName();
    Assert.hasLength(servletName, "getServletName() must not return null or empty");
    // 创建spring web IoC 容器
    WebApplicationContext servletAppContext = createServletApplicationContext();
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
    // 创建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.");
    }

    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);
}

XML配置的容器入口-ContextLoaderListener

ContextLoaderListener实现了Servlet的ServletContextListener,Tomcat在启动的时候,会优先加载Servlet的监听器组件,以确保在Servlet被创建之前,调用监听器的contextInitialized方法,Spring正是在ContextLoaderListener中进行了initWebApplicationContext的调用,即启动的时候,进行容器的refresh操作.

  • org.springframework.web.context.ContextLoaderListener#contextInitialized
@Override
public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext());
}
  • org.springframework.web.context.ContextLoader#initWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    // 从servletContext中查找,是否存在以WebApplicationContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为key的值
    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.
        // 在AbstractContextLoaderInitializer#onStartup  
        // ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
        // 进行了初始化
        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);
                }
                // 配置并刷新容器
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 将WebApplicationContext放入servletContext中
        // 其key为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
        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;
    }
}

又是refresh

Spring会依次建立两个上下文,一个是Root WebApplicationContext.另一个是WebApplicationContext For Dispatcher-servlet.无论哪个,最终还是会回到org.springframework.context.support.AbstractApplicationContext#refresh这个方法中.

  • 第一次refresh是Root WebApplicationContext

  • 第二次refresh是Servlet WebApplicationContext

HttpServletBean实现了HttpServlet接口,就可以重写其init方法,在init方法里面调用一个钩子方法initServletBean(HttpServletBean不负责实现,由子类实现).
FrameworkServlet重写了initServletBean方法,着手初始化容器的事项.
注意,这里提到的FrameworkSerlvetHttpSerlvetBean都是抽象类,真正的实例是-DispatcherSerlvet.

举报

相关推荐

0 条评论