美文网首页我爱编程
Spring(MVC)启动浅析与父子Context

Spring(MVC)启动浅析与父子Context

作者: UUID | 来源:发表于2018-05-24 18:38 被阅读52次

    现在在Java web 的世界中,很多都用spring的,因为它的高度解耦,使得开发人员能够快速构建出高可用和高扩展的应用。我们看下spring在web项目中的启动过程和上下文的使用情况。web 应用启动过程可以参考上一篇文章。https://www.jianshu.com/p/a9babadb5f4b
    我一般都用xml或者注解的方式使用spring,先说下xml的

    1、xml based configuration

    在XML based configuration的项目中,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>
    

    ContextLoaderListener 如下:

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
        public ContextLoaderListener() {
        }
    
        public ContextLoaderListener(WebApplicationContext context) {
            super(context);
        }
    
        public void contextInitialized(ServletContextEvent event) {
            this.initWebApplicationContext(event.getServletContext());
        }
    
        public void contextDestroyed(ServletContextEvent event) {
            this.closeWebApplicationContext(event.getServletContext());
            ContextCleanupListener.cleanupAttributes(event.getServletContext());
        }
    }
    

    在应用启动的时候,会调用contextInitialized,里面调用ContextLoader 的initWebApplicationContext 方法(servlet context 已经由web容器创建好了),方法中会创建 WebApplicationContext 并把它注册到servlet context中:
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    WebApplicationContext 熟悉吧?xml用的XmlWebApplicationContext、groovy用的GroovyWebApplicationContext、基于注解的AnnotationConfigWebApplicationContext 都是它的实现类。它就是作为IOC容器的上下文,也是root context。

    看下initWebApplicationContext方法的两个关键点:

    this.context = this.createWebApplicationContext(servletContext);//创建WebApplicationContext对象
    ...
    if(this.context instanceof ConfigurableWebApplicationContext) {
          ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
          if(!cwac.isActive()) {
              if(cwac.getParent() == null) {
                       ApplicationContext parent = this.loadParentContext(servletContext);
                             cwac.setParent(parent);//将web application context 的父容器设置为servletContext
               }
               this.configureAndRefreshWebApplicationContext(cwac, servletContext);
          }
    }
    

    root context初始化完了以后,按照定义的顺序开始加载初始化servlet。这里是DispatcherServlet。DispatcherServlet在初始化的时候,会创建自己的IOC context(servlet application context),并且从servlet context中取出属性名为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 的context,也就是root context,并根据创建出来的context创建DispatcherServlet,注册过滤器,最后将DispatcherServlet加入到servlet context(root context)中。servletApplicationContext 就作为WebApplicationContext的子容器了。这个不太直观,我们看下基于注解的配置的代码就很清楚了。

    2、annotation based configuration

    web application context:

    public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return null;
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class<?>[] { MyWebConfig.class };
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[] { "/" };
        }
    }
    

    web mvc context:

    class MyWebConfig extends WebMvcConfigurationSupport {
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        ...
         }
    }
    

    我们看看AbstractAnnotationConfigDispatcherServletInitializer:

    public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
        public AbstractAnnotationConfigDispatcherServletInitializer() {
        }
    
        @Nullable
        protected WebApplicationContext createRootApplicationContext() {
            Class<?>[] configClasses = this.getRootConfigClasses();
            if(!ObjectUtils.isEmpty(configClasses)) {
                AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
                rootAppContext.register(configClasses);
                return rootAppContext;
            } else {
                return null;
            }
        }
    
        protected WebApplicationContext createServletApplicationContext() {
            AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
            Class<?>[] configClasses = this.getServletConfigClasses();
            if(!ObjectUtils.isEmpty(configClasses)) {
                servletAppContext.register(configClasses);
            }
    
            return servletAppContext;
        }
    
        @Nullable
        protected abstract Class<?>[] getRootConfigClasses();
    
        @Nullable
        protected abstract Class<?>[] getServletConfigClasses();
    }
    

    主要就是创建rootApplicationContext 和 servletApplicationContext,也就是两个父子容器了。
    再来看看AbstractDispatcherServletInitializer,主要代码如下:

    public void onStartup(ServletContext servletContext) throws ServletException {
            super.onStartup(servletContext);
            this.registerDispatcherServlet(servletContext);
        }
    
        protected void registerDispatcherServlet(ServletContext servletContext) {
            String servletName = this.getServletName();
            WebApplicationContext servletAppContext = this.createServletApplicationContext();
            FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);
            dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers());
            Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
            registration.setLoadOnStartup(1);
            registration.addMapping(this.getServletMappings());
            registration.setAsyncSupported(this.isAsyncSupported());
            Filter[] filters = this.getServletFilters();
            if(!ObjectUtils.isEmpty(filters)) {
                Filter[] var7 = filters;
                int var8 = filters.length;
    
                for(int var9 = 0; var9 < var8; ++var9) {
                    Filter filter = var7[var9];
                    this.registerServletFilter(servletContext, filter);
                }
            }
    
            this.customizeRegistration(registration);
        }
    
        protected String getServletName() {
            return "dispatcher";
        }
    
        protected abstract WebApplicationContext createServletApplicationContext();
    
        protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
            return new DispatcherServlet(servletAppContext);
        }
    

    看到代码了,就是那么实现的。哈!

    父子容器(父:WebApplicationContext,子:ServletApplicationContext),在子容器中可以访问父容器中的bean,但是,父容器中却不能够访问子容器中的bean,这个是很自然的事情。。父容器中的bean主要是 DAO、service、repository,也就是业务逻辑层和数据持久化层;子容器中主要就是controller,也就是ACTION相关的东西。这个是很自然的事情,mvc嘛,子容器访问父容器中的bean,不正是mvc中的c调用m?

    一个不标准的时序和调用图如下:


    Jin.png

    这种采用父子容器的方式是很正规,中规中矩的方式,其实也可以直接都在同一个context中,不过不建议这样做。

    相关文章

      网友评论

        本文标题:Spring(MVC)启动浅析与父子Context

        本文链接:https://www.haomeiwen.com/subject/qcnsjftx.html