• spring IOC


      一直想阅读一下spring的源码,今天终于大致理清了spring IOC容器的初始化整个的脉络,做个记录。。。

      在开源的世界里,spring无疑是典范级别的,在项目中也是经常使用,所以学习spring优雅的设计对于提升做项目的能力,以及自我能力的提升都是极好的,在网上也看了很多相关的材料,所以就结合spring在web中的运用,逐步分析一下spring IOC容器初始化的整个过程。

      废话不多说,上干货。。。。

      在web项目的web.xml中,我们已经非常熟悉如何引用spring容器,代码如下。

      

        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:applicationContext.xml</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>

      所以我学习spring IOC时就以此处为入口,去查看ContextLoaderListener。该类继承于ContextLoader,实现了ServletContextListener,ServletContextListener是J2EE中ServletContext生命周期事件监听接口,所以在web应用启动后,ContextLoaderListener监听到相关事件(容器启动事件),调用contextInitialized()方法,执行初始化,这也是spring IOC接管的开始。

      

      下面就具体讲讲spring如何完成初始化

      首先我们看看ContextLoaderListener的contextInitialized()方法, 上代码

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

      该方法中首先初始化了contextLoader,然后调用contextLoader的initWebApplicationContext方法,所以spring IOC容器的初始化工作主要交给了ContextLoaderListener的父类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) {
                    //创建上下文
                    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);
                    }
                }
                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.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;
            }
        }

      在此方法中,做了两件事,如代码中注释的两部分,1、初始化执行环境上下文context , 2、初始化。

      下面我们分别来稍微仔细看下spring如何做这两件事,首先是创建上下文context,同样上代码

    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
            Class<?> contextClass = determineContextClass(sc);
            if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
                throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                        "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
            }
            return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        }
    protected Class<?> determineContextClass(ServletContext servletContext) {
            String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
            if (contextClassName != null) {
                try {
                    return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
                }
                catch (ClassNotFoundException ex) {
                    throw new ApplicationContextException(
                            "Failed to load custom context class [" + contextClassName + "]", ex);
                }
            }
            else {
                contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
                try {
                    return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
                }
                catch (ClassNotFoundException ex) {
                    throw new ApplicationContextException(
                            "Failed to load default context class [" + contextClassName + "]", ex);
                }
            }
        }

      这两段代码就是spring主要用来创建上下文的, 当我们未曾提供上下文类时,spring通过defaultStrategies加载默认上下文,默认的上下文信息配置在ContextLoader.properties文件中,通过该文件我们可以看到,spring默认的上下文环境就是XmlWebApplicationContext的实例,所以spring在默认情况下通过

    ContextLoader.properties文件初始化上下文为XmlWebApplicationContext的实例。

      spring已经完成了第一件事创建上下文实例,那我们来看第二件事,也是spring IOC容器初始化过程中最重要的事,在看之前先上一张XmlWebApplicationContext类的继承关系图,便于后面理解spring IOC的初始化过程

      

      话接前面,上面说过spring首先创建了上下文context,为XmlWebApplicationContext的实例,XmlWebApplicationContext的继承关系如上图,下面我们就来看spring最重要的一件事初始化,首先上前面initWebApplicationContext方法调用的configureAndRefreshWebApplicationContext方法:

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
            if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
                // The application context id is still set to its original default value
                // -> assign a more useful id based on available information
                String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
                if (idParam != null) {
                    wac.setId(idParam);
                }
                else {
                    // Generate default id...
                    if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
                        // Servlet <= 2.4: resort to name specified in web.xml, if any.
                        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                                ObjectUtils.getDisplayString(sc.getServletContextName()));
                    }
                    else {
                        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                                ObjectUtils.getDisplayString(sc.getContextPath()));
                    }
                }
            }
    
            wac.setServletContext(sc);
         //配置信息 String initParameter
    = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (initParameter != null) { wac.setConfigLocation(initParameter); } customizeContext(sc, wac);
         //委托给上下文执行初始化 wac.refresh(); }

      通过这段代码可以看出,spring实例化上下文后,开始配置上下文信息,最重要的就是读取配置文件位置,然后委托给上下文执行初始化,spring执行初始化的过程采用的模版方法进行初始化的,如上面代码标红的refresh方法定义在AbstractApplicationContext,通过上面的类继承关系可以看到该类。下面我们来看看该模版方法

    public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                // Prepare this context for refreshing.
                prepareRefresh();
    
                // Tell the subclass to refresh the internal bean factory.
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
                // Prepare the bean factory for use in this context.
                prepareBeanFactory(beanFactory);
    
                try {
                    // Allows post-processing of the bean factory in context subclasses.
                    postProcessBeanFactory(beanFactory);
    
                    // Invoke factory processors registered as beans in the context.
                    invokeBeanFactoryPostProcessors(beanFactory);
    
                    // Register bean processors that intercept bean creation.
                    registerBeanPostProcessors(beanFactory);
    
                    // Initialize message source for this context.
                    initMessageSource();
    
                    // Initialize event multicaster for this context.
                    initApplicationEventMulticaster();
    
                    // Initialize other special beans in specific context subclasses.
                    onRefresh();
    
                    // Check for listener beans and register them.
                    registerListeners();
    
                    // Instantiate all remaining (non-lazy-init) singletons.
                    finishBeanFactoryInitialization(beanFactory);
    
                    // Last step: publish corresponding event.
                    finishRefresh();
                }
    
                catch (BeansException ex) {
                    // Destroy already created singletons to avoid dangling resources.
                    destroyBeans();
    
                    // Reset 'active' flag.
                    cancelRefresh(ex);
    
                    // Propagate exception to caller.
                    throw ex;
                }
            }
        }

      该方法定义了spring IOC容器初始化的整个流程,稍微说一句,给方法取个好名字多重要呀,看着spring这些方法取的名字就大概知道这个方法要做什么事情了,

    为了从宏观层面去了解spring IOC容器初始化的过程,这里就不对spring初始化的模版方法里面的每个方法进行说明了,只是主要说明一下读取配置文件中Bean定义文件的解析。

      看到上面代码中标红的obtainFreshBeanFactory方法,该方法源码如下:

    /**
         * Tell the subclass to refresh the internal bean factory.
         * @return the fresh BeanFactory instance
         * @see #refreshBeanFactory()
         * @see #getBeanFactory()
         */
        protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
            refreshBeanFactory();
            ConfigurableListableBeanFactory beanFactory = getBeanFactory();
            if (logger.isDebugEnabled()) {
                logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
            }
            return beanFactory;
        }

      该方法中调用的refreshBeanFactory方法,refreshBeanFactory方法是一个抽象方法,定义在AbstractApplicationContext类中,然后AbstractRefreshableApplicationContext进行了实现,通过名称也就知道该方法是做什么事情的了吧,看下该方法的源码

    @Override
        protected final void refreshBeanFactory() throws BeansException {
            if (hasBeanFactory()) {
                destroyBeans();
                closeBeanFactory();
            }
            try {
                DefaultListableBeanFactory beanFactory = createBeanFactory();
                beanFactory.setSerializationId(getId());
                customizeBeanFactory(beanFactory);
                loadBeanDefinitions(beanFactory);
                synchronized (this.beanFactoryMonitor) {
                    this.beanFactory = beanFactory;
                }
            }
            catch (IOException ex) {
                throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
            }
        }

      在该方法中我们看到有如下标红的一句代码,这也是spring IOC读取Bean定义的正真入口,在初始化spring IOC容器后,读取Bean定义文件并解析,看下该方法的源码

    @Override
        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
            // Create a new XmlBeanDefinitionReader for the given BeanFactory.
            XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
            // Configure the bean definition reader with this context's
            // resource loading environment.
            beanDefinitionReader.setEnvironment(this.getEnvironment());
            beanDefinitionReader.setResourceLoader(this);
            beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    
            // Allow a subclass to provide custom initialization of the reader,
            // then proceed with actually loading the bean definitions.
            initBeanDefinitionReader(beanDefinitionReader);
            loadBeanDefinitions(beanDefinitionReader);
        }

      在该方法中初始化了一个XmlBeanDefinitionReader 读取类,设置好该类的相关属性后,通过该类的loadBeanDefinitions方法读取bean定义,该方法委托给doLoadBeanDefinitions方法,doLoadBeanDefinitions又委托给registerBeanDefinitions方法,然后由该方法进行具体的bean定义读取,registerBeanDefinitions方法源码如下

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
            BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
            documentReader.setEnvironment(this.getEnvironment());
            int countBefore = getRegistry().getBeanDefinitionCount();
            documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
            return getRegistry().getBeanDefinitionCount() - countBefore;
        }

      在该方法中初始化了一个BeanDefinitionDocumentReader类,该类主要真正负责的读取bean定义文件,将读取到的bean定义信息委托给BeanDefinitionParserDelegate去解析,下面先看下初始化BeanDefinitionDocumentReader后,交由BeanDefinitionDocumentReader做了哪些事,看下上图中标红方法

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
            this.readerContext = readerContext;
            logger.debug("Loading bean definitions");
            Element root = doc.getDocumentElement();
            doRegisterBeanDefinitions(root);
        }

      可以看到该方法委托给了doRegisterBeanDefinitions方法来做读取bean定义文件的信息,接着看doRegisterBeanDefinitions

    protected void doRegisterBeanDefinitions(Element root) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!this.environment.acceptsProfiles(specifiedProfiles)) {
                    return;
                }
            }
    
            BeanDefinitionParserDelegate parent = this.delegate;
            this.delegate = createHelper(this.readerContext, root, parent);
    
            preProcessXml(root);
            parseBeanDefinitions(root, this.delegate);
            postProcessXml(root);
    
            this.delegate = parent;
        }

      该方法通过三个模版方法preProcessXml,parseBeanDefinitions,postProcessXml对配置文件进行处理,其中preProcessXml,postProcessXml为钩子方法,我们可以在子类重写这两个方法进行配置文件预处理与后处理,解析bean定义文件的工作真正由parseBeanDefinitions来完成,查看该方法源码

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
            if (delegate.isDefaultNamespace(root)) {
                NodeList nl = root.getChildNodes();
                for (int i = 0; i < nl.getLength(); i++) {
                    Node node = nl.item(i);
                    if (node instanceof Element) {
                        Element ele = (Element) node;
                        if (delegate.isDefaultNamespace(ele)) {
                            parseDefaultElement(ele, delegate);
                        }
                        else {
                            delegate.parseCustomElement(ele);
                        }
                    }
                }
            }
            else {
                delegate.parseCustomElement(root);
            }
        }

    该方法读取配置文件的每个结点,然后委托给BeanDefinitionParserDelegate类,来具体进行Bean的定义具体解析,通过递归调用首先读取import引入文件中的bean定义。整个spring读取bean定义的过程也就到此结束。最后上一下BeanDefinitionParserDelegate类解析bean定义的方法parseBeanDefinitionElement

    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
            String id = ele.getAttribute(ID_ATTRIBUTE);
            String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
    
            List<String> aliases = new ArrayList<String>();
            if (StringUtils.hasLength(nameAttr)) {
                String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                aliases.addAll(Arrays.asList(nameArr));
            }
    
            String beanName = id;
            if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
                beanName = aliases.remove(0);
                if (logger.isDebugEnabled()) {
                    logger.debug("No XML 'id' specified - using '" + beanName +
                            "' as bean name and " + aliases + " as aliases");
                }
            }
    
            if (containingBean == null) {
                checkNameUniqueness(beanName, aliases, ele);
            }
    
            AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
            if (beanDefinition != null) {
                if (!StringUtils.hasText(beanName)) {
                    try {
                        if (containingBean != null) {
                            beanName = BeanDefinitionReaderUtils.generateBeanName(
                                    beanDefinition, this.readerContext.getRegistry(), true);
                        }
                        else {
                            beanName = this.readerContext.generateBeanName(beanDefinition);
                            // Register an alias for the plain bean class name, if still possible,
                            // if the generator returned the class name plus a suffix.
                            // This is expected for Spring 1.2/2.0 backwards compatibility.
                            String beanClassName = beanDefinition.getBeanClassName();
                            if (beanClassName != null &&
                                    beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                    !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                                aliases.add(beanClassName);
                            }
                        }
                        if (logger.isDebugEnabled()) {
                            logger.debug("Neither XML 'id' nor 'name' specified - " +
                                    "using generated bean name [" + beanName + "]");
                        }
                    }
                    catch (Exception ex) {
                        error(ex.getMessage(), ele);
                        return null;
                    }
                }
                String[] aliasesArray = StringUtils.toStringArray(aliases);
                return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
            }
    
            return null;
        }

    总结:

      spring IOC容器初始化过程主要通过BeanDefinitionReader完成Bean定义的解析工作,同时将真正解析bean定义文档的工作委托给BeanDefinitionDocumentReader来完成,然后BeanDefinitionDocumentReader读取文件结束后将bean定义注册工作交由BeanDefinitionParserDelegate来具体完成,BeanDefinitionParserDelegate完成验证bean定义相关操作后,通过回调容器的注册接口将所有的Bean定义完成注册,至此Bean定义解析工作完成,spring IOC容器的初始化工作也将完成。

  • 相关阅读:
    ios 读取通讯录
    隐藏多余的分割线
    Cell高亮时设置cell内容
    iOS录音
    iOS发送信息功能(生成信息内容)
    iOS颜色选择器
    iOS缓存
    二维码扫描
    梵讯笔记
    微信开发后台库
  • 原文地址:https://www.cnblogs.com/binger/p/3861356.html
Copyright © 2020-2023  润新知