Spring IOC 源码分析(一)

开始之前扯一些题外话。

有时候也在想,学源码到底是为了什么?不学似乎也没事,反正工作也用不到,似乎只有面试有可能被问到(背一背也能应付过去)。

但是,在反复阅读一些源码后,可以发现能够更容易理解一些技术实现原理,能够更自信更从容的应付面试,最终要的是能够让自己的代码看起来尽量的美观。

看源码只是为了能够更好理解技术,而不是单纯的死记硬背。只有理解了,才能建立自身对技术的认知,而非人云亦云。

为什么要分析源码

  1. 理解其内部实现原理。(任何技术从外层去看这么多功能,这么好用,这么牛。再一看源码,似乎也这么回事)

  2. 学习其设计思路,好处优点。

  3. 知识储备。

  4. 自我认知。

开始

学会看堆栈

看源码一定要注意堆栈信息。里边每一步都很清楚,一边调试,一边理解会轻松很多。如下图 Spring IOC 步骤图。

image.png

Spring IOC 核心流程 (基于XML)

Spring的IOC主要是为了实现对Bean的统一管理,将Bean的初始化、创建,销毁都交由Spring的IOC容器来实现。

上边是Spring IOC的核心功能,完成Bean的初始化创建,依赖注入,管理Bean的生命周期。

那么可以想两个问题。

  1. 我们再项目中使用Spring都做了哪些功能?

    a. 定义并配置Spring的XML文件

    b. 在Web.xml中配置解析SpringXml文件

  2. 如果要完成bean的创建我们应该如何做?

    a. 知道要创建哪些bean。(扫描bean)

    b. 创建完了之后要有容器来存放。(加载bean)

忘记了Spring XML配置的同学,可以参考这个文档,回忆一下。 Spring MVC 配置

简单来说分为以下几步。

  1. jar包引入。

  2. 配置Web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
  <!-- 全局初始化方式,正常只需要配置 DispatcherServlet 初始化方式即可  -->
  <context-param>
    <!-- 初始化过程中回加载 contextConfigLocation 这个参数 路径 -->
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <!-- web 容器初始化监听 实际事从这里开始 初始化 ApplicationContext -->
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- DispatcherServlet 初始化方式  -->
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <!-- 这个参数可以省略,Spring 默认加载 WEB-INF下servlet名称-servlet.xml。即dispatcher-servlet.xml -->
    <init-param>
            <!--配置 dispatcher-servlet.xml作为mvc的配置文件-->
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
        </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>*.form</url-pattern>
  </servlet-mapping>
</web-app>

这里简单做一个说明:

初始化有两种方式,一种是ContextLoaderListener,一种是DispatcherServlet ,这两种建议使用DispatcherServlet。因为已经是老技术了(现在没人用这两种初始化方式了),这里不做深究,有兴趣的同学可以自行百度一下区别。目前了解到的是,配置两种方式,会进行两次初始化。如下方日志,initialization started 出现两次。一般单独使用DispatcherServlet即可。下边分析源码也会以DispatcherServlet启动过程为主。

[2021-11-14 04:03:15,384] Artifact fishbone-springmvc-study:war: Artifact is being deployed, please wait...
14-Nov-2021 16:03:15.861 警告 [RMI TCP Connection(3)-127.0.0.1] org.apache.tomcat.util.descriptor.web.WebXml.setVersion Unknown version string [4.0]. Default version will be used.
14-Nov-2021 16:03:18.019 信息 [RMI TCP Connection(3)-127.0.0.1] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
14-Nov-2021 16:03:18.118 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization started
14-Nov-2021 16:03:23.148 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.context.support.AbstractApplicationContext.prepareRefresh Refreshing Root WebApplicationContext: startup date [Sun Nov 14 16:03:23 CST 2021]; root of context hierarchy
14-Nov-2021 16:03:25.468 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [D:\Program Files (x86)\apache-tomcat-8.5.35\webapps\manager]
14-Nov-2021 16:03:25.797 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/applicationContext.xml]
14-Nov-2021 16:03:28.337 信息 [localhost-startStop-1] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
14-Nov-2021 16:03:28.756 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [D:\Program Files (x86)\apache-tomcat-8.5.35\webapps\manager] has finished in [3,287] ms
14-Nov-2021 16:03:33.519 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization completed in 15400 ms
14-Nov-2021 16:03:35.161 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.FrameworkServlet.initServletBean FrameworkServlet 'dispatcher': initialization started
14-Nov-2021 16:03:36.865 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.context.support.AbstractApplicationContext.prepareRefresh Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Sun Nov 14 16:03:36 CST 2021]; parent: Root WebApplicationContext
14-Nov-2021 16:03:38.218 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/dispatcher-servlet.xml]
14-Nov-2021 16:03:50.068 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.register Mapped "{[/test/test],methods=[GET]}" onto public java.lang.String com.fishbone.spring.TestController.test()
14-Nov-2021 16:03:50.368 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.initControllerAdviceCache Looking for @ControllerAdvice: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Sun Nov 14 16:03:36 CST 2021]; parent: Root WebApplicationContext
14-Nov-2021 16:03:50.507 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.initControllerAdviceCache Looking for @ControllerAdvice: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Sun Nov 14 16:03:36 CST 2021]; parent: Root WebApplicationContext
14-Nov-2021 16:03:50.662 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler Mapped URL path [/css/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
14-Nov-2021 16:03:50.667 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler Mapped URL path [/js/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#1'
14-Nov-2021 16:03:50.678 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler Mapped URL path [/image/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#2'
14-Nov-2021 16:03:50.694 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler Mapped URL path [/**] onto handler 'org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0'
14-Nov-2021 16:03:50.977 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.FrameworkServlet.initServletBean FrameworkServlet 'dispatcher': initialization completed in 15815 ms
[2021-11-14 04:03:50,996] Artifact fishbone-springmvc-study:war: Artifact is deployed successfully
[2021-11-14 04:03:50,997] Artifact fishbone-springmvc-study:war: Deploy took 35,613 milliseconds
  1. 配置 dispatcher-servlet.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
    <!--此文件负责整个mvc中的配置-->

    <!-- 自动扫描装配 -->
    <context:component-scan base-package="com.fishbone.spring"/>
    <!--启用spring的一些annotation -->
    <context:annotation-config/>

    <!-- 配置注解驱动 可以将request参数与绑定到controller参数上 -->
    <mvc:annotation-driven/>

    <!--静态资源映射-->
    <!--本项目把静态资源放在了webapp的statics目录下,资源映射如下-->
    <mvc:resources mapping="/css/**" location="/static/css/"/>
    <mvc:resources mapping="/js/**" location="/static/js/"/>
    <mvc:resources mapping="/image/**" location="/static/images/"/>
    <mvc:default-servlet-handler />  <!--这句要加上,要不然可能会访问不到静态资源,具体作用自行百度-->

    <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀(如果最后一个还是表示文件夹,则最后的斜杠不要漏了) 使用JSP-->
    <!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- -->
    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/><!--设置JSP文件的目录位置-->
        <property name="suffix" value=".jsp"/>
        <property name="exposeContextBeansAsAttributes" value="true"/>
    </bean>
</beans>
  1. 最后就是在 test.SpringMVC包下边创建自己的Controller、Service 等类。
包结构.png

以上就是我们再使用Spring框架时的步骤。总结一下有几个点:

  1. 通过web.xml配置DispatcherServlet作为入口,由于这里初始化参数配置了Spring的xml文件地址,因此这里启动后,一定会去加载Spring的配置文件。

  2. Spring 的配置文件中,定义了Spring要管理的Bean位置(包路径),因此Spring会扫描加载包下边的所有类文件,找到Spring需要解析的类(@Controller、@Service等Spring相关的注解)

  3. 扫描到以后,根据其注解类型采用不同的解析方式。完成bean的解析,并存放到IOC容器中。

接下来我们就按照这三个步骤,一步一步的来看其加载过程。

流程图

Spring IOC.png

贴代码实在太费劲了,直接搞一个流程图比较方便。下边会对一些关键部分代码做一下解析。主要会涉及到一些比较容易迷路的地方。

建议先根据流程图点一遍源码。

DispatcherServlet

DispatcherServlet.png

首先我们都知道,这里会调用init()方法,根据继承关系,我们可以找到init()方法是在DispatcherServlet的父类,HttpSevletBean中。

    @Override
    public final void init() throws ServletException {
        // 其实前边这一堆东西 都没什么用, pvs 正常情况都是 null  直接看 initServletBean
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                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) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        // 调用FrameWork初始化Bean的过程,完成Spring的初始化
        initServletBean();
    }

FrameworkServlet

protected WebApplicationContext initWebApplicationContext() {
        // 这里会初始化ApplicationContext,实际返回的是Null
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
        // 如果在web.xml 中配置了 ContextLoadListener 此处会不为Null
        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                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 -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            // 单配置DispatcherServlet情况会走这里
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            onRefresh(wac);
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        // 这里会返回一个 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");
        }
        // 进行实例化 XmlWebApplicationContext
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        // 如果web.xml DispatcherServlet 标签下 配置了 <init-param> 且<param-name> 为contextConfigLocation 
        // 那么此处可以获取到 配置的值,我们的代码中没有配置因此此处为null
        String configLocation = getContextConfigLocation();
        if (configLocation != null) {
            wac.setConfigLocation(configLocation);
        }
        // 准备开始调用 refresh 方法
        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }

AbstractApplicationContext

    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            //1、调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            // 2、告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从
            // 子类的refreshBeanFactory()方法启动,  IOC 容器初始化
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            //3、为BeanFactory配置容器特性,例如类加载器、事件处理器等
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                //4、为容器的某些子类指定特殊的BeanPost事件处理器
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                //5、调用所有注册的BeanFactoryPostProcessor的Bean
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                //6、为BeanFactory注册BeanPost事件处理器.
                //BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                //7、初始化信息源,和国际化相关.
                initMessageSource();

                // Initialize event multicaster for this context.
                //8、初始化容器事件传播器.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                //9、调用子类的某些特殊Bean初始化方法
                onRefresh();

                // Check for listener beans and register them.
                //10、为事件传播器注册事件监听器.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                //11、初始化所有剩余的单例Bean
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                //12、初始化容器的生命周期事件处理器,并发布容器的生命周期事件
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                //13、销毁已创建的Bean
                destroyBeans();

                // Reset 'active' flag.
                //14、取消refresh操作,重置容器的同步标识。
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                //15、重设公共缓存
                resetCommonCaches();
            }
        }
    }
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        // 关于此处应该到哪个类 可以查看类关系图  使用快捷键 ctrl + H
        // 由于上放 FrameworkServlet#createWebApplicationContext方法中得知返回的对象为 XmlWebApplicationContext
        // 因此结合类关系图得知 这里应该为 AbstractRefreshableApplicationContext  如下图
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }
对象关系图.png

AbstractRefreshableApplicationContext

    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            // 关注一下这里,返回了一个  DefaultListableBeanFactory
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            // 这里同上查询类关系图可知 应该走 XmlWebApplicationContext
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

XmlWebApplicationContext

    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        // 关注一下这里,直接将 DefaultListableBeanFactory 作为 AbstractBeanDefinitionReader 类的 registry
        // PathMatchingResourcePatternResolver 作为 resourceLoader 。StandardEnvironment  作为 environment
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(getEnvironment());
        // 重新设置 resourceLoader值
        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);
    }
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
        // 这里获取 默认需要加载的配置文件 也就是 /WEB-INF/dispatcher-servlet.xml 
        // 这里点进去之后 不要迷路哈,先调用父类,实际最后调用的还是 子类的实现。XmlWebApplicationContext
        // 支持配置多个配置文件
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                reader.loadBeanDefinitions(configLocation);
            }
        }
    }

XmlBeanDefinitionReader

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        // 创建并实例化 DefaultBeanDefinitionDocumentReader 类
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
        // 初始化 标签的解析类,createReaderContext() 方法 需要点进去看一下
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

    public XmlReaderContext createReaderContext(Resource resource) {
        // 初始化 XmlReaderContext 类
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                this.sourceExtractor, this, getNamespaceHandlerResolver());
    }

    /**
     * Lazily create a default NamespaceHandlerResolver, if not set before.
     * @see #createDefaultNamespaceHandlerResolver()
     */
    public NamespaceHandlerResolver getNamespaceHandlerResolver() {
        if (this.namespaceHandlerResolver == null) {
            // 记一下这个变量,解析的时候会用到这个变量 
            this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
        }
        return this.namespaceHandlerResolver;
    }

    protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
        ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
        // 注意这里 初始化了 DefaultNamespaceHandlerResolver 类 后续解析context 标签会用到
        // 同时设置了 handlerMappingsLocation 属性值为 META-INF/spring.handlers
        // 后边解析标签时会扫描这个文件,解析实例化其中的类作为标签解析类
        // Spring XML配置方式的一个关键扩展点,包括Dubbo等整合Spring通过配置文件方式实现场景都会用到这个扩展点 NamespaceHandler
        return new DefaultNamespaceHandlerResolver(cl);
    }
    
    // 这里 DEFAULT_HANDLER_MAPPINGS_LOCATION =  META-INF/spring.handlers
    public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
        this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
    }

DefaultBeanDefinitionDocumentReader

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        // 这里获取的是配置文件的标签对象 比如 <context:component-scan>
        // dispatcher-servlet.xml 标签是以<beans> 开头的 因此会往下走
        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) {
                    // 这里获取的是配置文件的标签对象 比如 <context:component-scan>
                    Element ele = (Element) node;
                    // 这里会判断是否为bean标签, <bean>标签直接解析,其他的通过委托类解析
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        // <context:component-scan> 会走这里
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            // 非<beans> 标签,比如dubbo、mybatis整合spring后使用的 <dubbo> 等标签就会走这里
            delegate.parseCustomElement(root);
        }
    } 

BeanDefinitionParserDelegate

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
        // 这里获取的 nameSpaceURI 就是配置文件 头信息中的定义URI 
        // 比如  xmlns:context="http://www.springframework.org/schema/context" 
        // context 标签的URI 为 http://www.springframework.org/schema/context
        String namespaceUri = getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        }
        // 这里会通过URI 获取对应的解析类 并实例化
        // 还记得前边 namespaceHandlerResolver = new DefaultNamespaceHandlerResolver()  如果忘记往下看 DefaultNamespaceHandlerResolver 类
        // 这里还是以<context:component-scan> 标签为例,获取的 NamespaceHandler 为 ContextNamespaceHandler
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        // 调用 NamespaceHandlerSupport 的解析方法
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

DefaultNamespaceHandlerResolver

    public NamespaceHandler resolve(String namespaceUri) {
        // 尝试获取缓存中的映射关系 URI跟实体类 
        // 点进去看一下吧。初始化这里一般都没有实例化处理类,存的是类的全路径,下边会进行实例化 
        // org.springframework.context.config.ContextNamespaceHandler 
        Map<String, Object> handlerMappings = getHandlerMappings();
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }
        else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        else {
            // 注意初次加载Value 都是String类型的 全路径类名。
            String className = (String) handlerOrClassName;
            try {
                // 直接通过classforname 完成实例化 (ContextNamespaceHandler)
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                            "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
                }
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                // 实例化完成以后调用 init 方法 (ContextNamespaceHandler)
                namespaceHandler.init();
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
            catch (ClassNotFoundException ex) {
                throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "] not found", ex);
            }
            catch (LinkageError err) {
                throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "]: problem with handler class file or dependent class", err);
            }
        }
    }

    // 加载NamespaceHandler扩展类
    private Map<String, Object> getHandlerMappings() {
        Map<String, Object> handlerMappings = this.handlerMappings;
        // 初次加载这里肯定为空
        if (handlerMappings == null) {
            synchronized (this) {
                handlerMappings = this.handlerMappings;
                if (handlerMappings == null) {
                    try {
                        // 这里使用的路径 handlerMappingsLocation = META-INF/spring.handlers 
                        // 因此这里会扫描所有的 META-INF/spring.handlers 文件读取里边的属性值 key/Value结构。
                        // 以<context>标签URI为例,如下图
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                        }
                        Map<String, Object> mappingsToUse = new ConcurrentHashMap<>(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, mappingsToUse);
                        handlerMappings = mappingsToUse;
                        this.handlerMappings = handlerMappings;
                    }
                    catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return handlerMappings;
    }
image.png

ContextNamespaceHandler

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // 不同的标签 使用不同的解析处理类
        // <context:property-override>  使用  PropertyOverrideBeanDefinitionParser
        // <context:component-scan>  使用  ComponentScanBeanDefinitionParser
        // 调用 NamespaceHandlerSupport#registerBeanDefinitionParser()方法 
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }

}

NamespaceHandlerSupport

    protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
        this.parsers.put(elementName, parser);
    }

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 找到对应的标签解析类,即ContextNamespaceHandler 中注册的 ComponentScanBeanDefinitionParser
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        return (parser != null ? parser.parse(element, parserContext) : null);
    }

    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        // 这里localName为 component-scan
        String localName = parserContext.getDelegate().getLocalName(element);
        // ContextNamespaceHandler#init()方法完成类的注册
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal(
                    "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        // 返回类为 ComponentScanBeanDefinitionParser
        return parser;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容