zoukankan      html  css  js  c++  java
  • spring源码:web容器启动

      web项目中可以集成spring的ApplicationContext进行bean的管理,这样使用起来bean更加便捷,能够利用到很多spring的特性。我们比较常用的web容器有jetty,tomcat,jboss等,以jetty为例,我们看一下web容器是如何初始化和启动spring的context。

    一、Spring容器的加载

      在web工程中都有一个web.xml文件,jetty在启动的时候会加载这个配置文件,并且对文件中的各个listener进行加载。ContextLoaderListener继承了ServletContextListener,ServletContextListener作为ServletContext的监听者,会在ServletContext创建、销毁等过程中监听ServletContextEvent事件,然后进行相应处理。关于这一块可以参考Spring的事件发布和监听机制:(Spring源码中的ApplicationContext的增强功能)中关于ApplicationContext作为事件发布者部分。所有的扩展点都在接受到ServletContextEvent事件时,具体的ContextLoaderListener处理ServletContextEvent代码如下:

    /**
        * Initialize the root web application context.
    */
    public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = createContextLoader();
        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }

    这里创建了一个contextLoader对象,ContextLoader顾名思义就是context的加载器,由它来完成context的加载:

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
                throws IllegalStateException, BeansException {
     
            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");
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }
            long startTime = System.currentTimeMillis();
     
            try {
                // Determine parent for root web application context, if any.
                ApplicationContext parent = loadParentContext(servletContext);
     
                // Store context in local instance variable, to guarantee that
                // it is available on ServletContext shutdown.
                this.context = createWebApplicationContext(servletContext, parent);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);
     
                if (logger.isDebugEnabled()) {
                    logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
                }
                if (logger.isInfoEnabled()) {
                    long elapsedTime = System.currentTimeMillis() - startTime;
                    logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
                }
     
                return this.context;
            }
            catch (RuntimeException ex) {
                logger.error("Context initialization failed", ex);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
                throw ex;
            }
            catch (Error err) {
                logger.error("Context initialization failed", err);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
                throw err;
            }
        }

    初始化web的context做了两件事情:1.查看是否指定了父容器,如果存在父容器则获取父容器;2.创建webContext,并指定父容器。laputa也使用了父子容器的指派特性,二方库plouto中负责bean加载的context作为父容器,laputa应用自己作为子容器,这样laputa就能够使用到了plouto中声明的bean(如在plouto中声明的widgetagContext,在laputa中的tagList可以顺利完成依赖注入)。之前做一个需求时,为了在tag中暴露cmsTemplateService给组件接入,把cmsTemplateService声明放到了plouto中,这样在laputa中能够更加方便引用。创建的webContext,默认给出的是XmlWebApplicationContext,关于这个类大家肯定不会陌生,学习ApplicationContext的例子中会经常使用这个容器来加载xml形式的bean配置。到此,我们获得了一个ApplicationContext,通过这个context我们可以获取当前容器的bean以及父容器的bean。

    二、如何在应用中使用context

    上述获取context后进行context存放的代码中有一段非常重要:

    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);

      这两行代码告知了我们如何去获取context:1.从servletContext中去拿;2.从当前的线程Map中去拿。

      A.servletContext中获取spring上下文。Spring对这一种获取方式做了封装:WebApplicationContextUtils.getRequiredWebApplicationContext(ServletContext sc)方法来得到WebApplicationContext:

    public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
            return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        }
    ————————————————————————————————————————————————————————————————————————————————————————————————————————————
    public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
            Assert.notNull(sc, "ServletContext must not be null");
            Object attr = sc.getAttribute(attrName);
            if (attr == null) {
                return null;
            }
            if (attr instanceof RuntimeException) {
                throw (RuntimeException) attr;
            }
            if (attr instanceof Error) {
                throw (Error) attr;
            }
            if (attr instanceof Exception) {
                IllegalStateException ex = new IllegalStateException();
                ex.initCause((Exception) attr);
                throw ex;
            }
            if (!(attr instanceof WebApplicationContext)) {
                throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
            }
            return (WebApplicationContext) attr;
        }

    通过servletContext获取上下文,不足之处在于开发者必须能够先获得servletContext作为入参传入,所以使用起来不是很方便。

    从线程Map中获取spring上下文。在ContextLoader中有静态方法来获取spring上下文:

    public static WebApplicationContext getCurrentWebApplicationContext() {
        return (WebApplicationContext) currentContextPerThread.get(Thread.currentThread().getContextClassLoader());
    }

    这种方法类比与上述方式更加便捷,不再需要感知到servletcontext的存在。

    spring获取上下文方式------实现ApplicationContextAware接口。这种方式最通用,不仅仅局限于web应用。我们仅需要在用到的类中,让其继承ApplicationContextAwar接口,并实现set方法,就能够让容器在启动的时候把上下文注入到当前对象中。举例如流程引擎中的“开始节点”,获取spring容器上下文并存入PE的context中,方便后续节点能够使用spring容器:

    public class InitParamNode implements  ApplicationContextAware{
        private static final String PARAM_PLUGS = "param.plugs";
        private static final String PARAM_EXTENDS = "param.extends";
        private static final String PARAM_SIGNS = "param.signs";
        private static final String SPRING_CONTEXT = "springContext";
        private ApplicationContext applicationContext;
     
        @Override
        public void setApplicationContext(ApplicationContext applicationContext)
                throws BeansException {
            this.applicationContext=applicationContext;
             
        }
         
        public Map<String,Object> execute(PostParam postParam){
            Map<String,Object> context=new HashMap<String,Object>();
             
            context.put(SPRING_CONTEXT, this.applicationContext);
             
            if(postParam.getCommodityExtParam()!=null){
                context.put(PARAM_SIGNS, postParam.getCommodityExtParam().getSignParam());
                context.put(PARAM_EXTENDS, postParam.getCommodityExtParam().getExtendParam());
                context.put(PARAM_PLUGS, postParam.getCommodityExtParam().getPluginParam());
            }
         
             
             
            return context;
             
        }
     
    }

    三、

  • 相关阅读:
    vue-cli-service: command not found
    parted 大容量硬盘分区 格式化
    RAC集群安装错误集合
    印象笔记 Windows 客户端“未响应”怎么办?
    split,splice,slice 三者的用法
    JQuery 2.0.3 源码结构
    数据库字段备注信息声明语法 CDL (Comment Declaration Language)
    mysql底层原理解析(一)之日志
    ES底层原理解析
    aspnetCore 3.1网站部署到IIS
  • 原文地址:https://www.cnblogs.com/RunForLove/p/5829012.html
Copyright © 2011-2022 走看看