zoukankan      html  css  js  c++  java
  • Spring IOC原理分析

    IOC

    IOC(Inversion of Control)控制反转:所谓的控制反转,就是把原先需要我们代码自己实现对象的创建和依赖,反转给容器来实现。那么必然Spring需要创建一个容器,同时需要创建一种描述对象与对象之间的依赖关系,这个描述就是Spring的配置文件。

    假设我们自己设计Spring框架我们应该怎样考虑:

    1.怎么表示对象与对象之间的关系

    可以用 xml,properties 文件等语义化配置文件表示。

    2.描述对象和对象之间关系的文件存放在哪里

    可能是classpath、filesystem、URL网络资源、ServletContext等。

    3.不同的配置文件对于对象的描述不一样应该如何统一

    内部需要一个统一的关于对象的定义,所有外部描述都需要转化为统一的描述定义

    4.如何对不同的配置文件解析

    需要对不同的配置文件语法,采用不同的解析器

    Spring核心容器

    1.BeanFactory

    spring Bean的创建是典型的工厂模式,这一系列的Bean工厂,也即IOC容器为开发者管理对象间的依赖关系提供了很多便利和基础服务,在Spring中有许多IOC容器的实现供用户选择和使用

    其相关关系如下:

    BeanFactory作为最顶层接口,定义了IOC容器的基本功能规范,它有三个重要的子类ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory都是对它本身的扩展,从类图中我们可以发现最终的默认实现类是 DefaultListableBeanFactory,它实现了所有的接口

    那为何要定义这么多层次的接口呢?查阅这些接口的源码和说明发现,每个接口都有它使用的场合,它主要是为了区分在 Spring 内部在操作过程中对象的传递和转化过程时,对对象的数据访问所做的限制。例如 ListableBeanFactory 接口表示这些 Bean 是可列表化的,而 HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个 Bean 有可能有父 Bean。AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。这三个接口共同定义了 Bean 的集合、Bean 之间的关系、以及 Bean 行为。最基本的 IOC 容器接口BeanFactory,来看一下它的源码(Spring源码自行下载):

    public interface BeanFactory {
    
       //对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,
       //如果需要得到工厂本身,需要转义
       String FACTORY_BEAN_PREFIX = "&";
      
       //根据bean的名字,获取在IOC容器中得到bean实例
       Object getBean(String name) throws BeansException;
    
       //根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。
       <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
    
       Object getBean(String name, Object... args) throws BeansException;
    
       <T> T getBean(Class<T> requiredType) throws BeansException;
    
       <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
    
       //提供对bean的检索,看看是否在IOC容器有这个名字的bean
       boolean containsBean(String name);
    
       //根据bean名字得到bean实例,并同时判断这个bean是不是单例
       boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    
       boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    
       boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
      
       boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
       
    //得到bean实例的Class类型
      @Nullable
       Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    
    //得到bean的别名,如果根据别名检索,那么其原名也会被检索出来
       String[] getAliases(String name);
    }

     在BeanFactory 里只对 IOC 容器的基本行为作了定义,根本不关心你的 Bean 是如何定义怎样加载的。正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心。

    而要知道工厂如何产生对象的,我们需要看具体的IOC容器实现,Spring提供许多的IOC容器实现,比如GenericApplicationContext、ClassPathXmlApplicationContext等

    2.BeanDefinition

    SpringIOC 容器管理了我们定义的各种 Bean 对象及其相互的关系,Bean 对象在 Spring 实现中是以 BeanDefinition 来描述的,其继承体系如下: BeanDefinition里面记录了Bean的信息beanClassName、FactoryBeanNameLazyInit等等

    3.BeanDefinitionReader

    Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过BeanDefintionReader 来完成,最后看看 Spring 中 BeanDefintionReader 的类结构图:


     可以发现xml、properties等解析Bean的方法都是基于BeanDefintionReader

    Web IOC 容器

    我们从最熟悉的DispatcherServlet来看起,我们最先想到的是Servlet的init方法,因为DispatcherServlet是基于Servlet实现的所以我们要找到DispatcherServlet里的init方法。通过查找我们找到在DispatcherServlet的父类HttpServletBean找到init方法,一下是方法源码

        @Override
        public final void init() throws ServletException {
            if (logger.isDebugEnabled()) {
                logger.debug("Initializing servlet '" + getServletName() + "'");
            }
    
            // Set bean properties from init parameters.
            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;
                }
            }
    
            // Let subclasses do whatever initialization they like.
            initServletBean();
    
            if (logger.isDebugEnabled()) {
                logger.debug("Servlet '" + getServletName() + "' configured successfully");
            }
        }    

    可以看到在init()方法中真正完成容器初始化的方法是initServletBean在FrameworkServlet里实现,继续跟进我们可以发现initWebApplicationContext方法,继续跟进发现configureAndRefreshWebApplicationContext方法调用 refresh()方法,这个是真正启动 IOC 容器的入口。IOC 容器初始化以后,最后调用了DispatcherServlet 的 onRefresh()方法,在 onRefresh()方法中又是直接调用 initStrategies()方法初始化 SpringMVC 的九大组件:

        @Override
        protected void onRefresh(ApplicationContext context) {
            initStrategies(context);
        }
    
        protected void initStrategies(ApplicationContext context) {
            //多文件上传组件
            initMultipartResolver(context);
            //初始化本地语言环境
            initLocaleResolver(context);
            //初始化模板处理器
            initThemeResolver(context);
            //初始化handlerMapping
            initHandlerMappings(context);
            //初始化参数适配器
            initHandlerAdapters(context);
            //初始化异常拦截器
            initHandlerExceptionResolvers(context);
            //初始化视图预处理器
            initRequestToViewNameTranslator(context);
            //初始化视图转化器
            initViewResolvers(context);
            //初始化缓存组件
            initFlashMapManager(context);
        }

    Xml IOC容器

    IOC容器的初始化包括定位、加载和注册这三个基本过程。我们以ApplicationContext为例,ApplicationContext 系列容器也许是我们最熟悉的,因为 Web 项目中使用的 XmlWebApplicationContext 就属于这个继承体系,还有 ClasspathXmlApplicationContext等,其继承体系如下图所示:

    ApplicationContext里是允许上下文嵌套的,通过保持一个父上下文可以维持一个上下文体系。可以在这个上下文体系里查找Bean,查找时首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的 Spring应用提供了一个共享的 Bean 定义环境。

    1.寻找入口

    通过一个使用比较多的类入手ClassPathXmlApplicationContext,我们会使用main方法来使用,至少我这样做过

    ApplicationContext app = new ClassPathXmlApplicationContext("application.xml"); 

    看一下其构造函数

    public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
            this(configLocations, true, null);
        }

    实际调用

    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
                throws BeansException {
    
            super(parent);
            setConfigLocations(configLocations);
            if (refresh) {
                refresh();
            }
        }

    还有 像 AnnotationConfigApplicationContext 、 FileSystemXmlApplicationContext 、XmlWebApplicationContext 等都继承自父容器 AbstractApplicationContext主要用到了装饰器模式
    和策略模式,最终都是调用 refresh()方法。

    2.获取配置路径

     ClassPathXmlApplicationContext构造函数可以看到首先调用父类super(parent)方法设置好Bean的资源加载器,然后再调用父类AbstractRefreshableConfigApplicationContext的setConfigLocations方法设置Bean的定位路径

    通过查看源码发现SpringIOC 容器在初始化时将配置的 Bean 配置信息定位为 Spring 封装的 Resource

    3.开始启动

    Spring IOC 容器对 Bean 配置资源的载入是从 refresh()函数开始的,refresh()是一个模板方法,规定了IOC 容 器 的 启 动 流 程 , 有 些 逻 辑 要 交 给 其 子 类 去 实 现 。 它 对 Bean 配 置 资 源 进 行 载 入,ClassPathXmlApplicationContext 通过调用其父类 AbstractApplicationContext 的 refresh()函数启动整个 IOC 容器对 Bean 定义的载入过程,现在我们来详细看看 refresh()中的逻辑处理:

        @Override
        public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                // Prepare this context for refreshing.
                //调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
                prepareRefresh();
    
                // Tell the subclass to refresh the internal bean factory.
                //告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从
                //子类的refreshBeanFactory()方法启动
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
                // Prepare the bean factory for use in this context.
                //为BeanFactory配置容器特性,例如类加载器、事件处理器等
                prepareBeanFactory(beanFactory);
    
                try {
                    // Allows post-processing of the bean factory in context subclasses.
                    //为容器的某些子类指定特殊的BeanPost事件处理器
                    postProcessBeanFactory(beanFactory);
    
                    // Invoke factory processors registered as beans in the context.
                    //调用所有注册的BeanFactoryPostProcessor的Bean
                    invokeBeanFactoryPostProcessors(beanFactory);
    
                    // Register bean processors that intercept bean creation.
                    //为BeanFactory注册BeanPost事件处理器.
                    //BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件
                    registerBeanPostProcessors(beanFactory);
    
                    // Initialize message source for this context.
                    //初始化信息源,和国际化相关.
                    initMessageSource();
    
                    // Initialize event multicaster for this context.
                    //初始化容器事件传播器.
                    initApplicationEventMulticaster();
    
                    // Initialize other special beans in specific context subclasses.
                    //调用子类的某些特殊Bean初始化方法
                    onRefresh();
    
                    // Check for listener beans and register them.
                    //为事件传播器注册事件监听器.
                    registerListeners();
    
                    // Instantiate all remaining (non-lazy-init) singletons.
                    //初始化所有剩余的单例Bean
                    finishBeanFactoryInitialization(beanFactory);
    
                    // Last step: publish corresponding event.
                    //初始化容器的生命周期事件处理器,并发布容器的生命周期事件
                    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.
                    //销毁已创建的Bean
                    destroyBeans();
    
                    // Reset 'active' flag.
                    //取消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...
                    resetCommonCaches();
                }
            }
        }

    refresh()方法主要为 IOC 容器 Bean 的生命周期管理提供条件,Spring IOC 容器载入 Bean 配置信息从 其 子 类 容 器 的 refreshBeanFactory() 方 法 启 动 , 所 以 整 个 refresh() 中
    “ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()”这句以后代码的都是注册容器的信息源和生命周期事件,我们前面说的载入就是从这句代码开始启动

    refresh()方法的主要作用是:在创建 IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在 refresh 之后使用的是新建立起来的 IOC 容器。它类似于对 IOC 容器的重启,在新建立好的容器中对容器进行初始化,对 Bean 配置资源进行载入。

    4.创建容器

    obtainFreshBeanFactory()方法调用子类容器的 refreshBeanFactory()方法,启动容器载入 Bean 配置信息的过程,代码如下

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
            //这里使用了委派设计模式,父类定义了抽象的refreshBeanFactory()方法,具体实现调用子类容器的refreshBeanFactory()方法
            refreshBeanFactory();
            ConfigurableListableBeanFactory beanFactory = getBeanFactory();
            if (logger.isDebugEnabled()) {
                logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
            }
            return beanFactory;
        }

    AbstractApplicationContext 类中只抽象定义了 refreshBeanFactory()方法, 容器真正调用的是其子类 AbstractRefreshableApplicationContext 实现的 refreshBeanFactory()方法, 方法的源
    码如下:

    @Override
        protected final void refreshBeanFactory() throws BeansException {
            //如果已经有容器,销毁容器中的bean,关闭容器
            if (hasBeanFactory()) {
                destroyBeans();
                closeBeanFactory();
            }
            try {
                //创建IOC容器
                DefaultListableBeanFactory beanFactory = createBeanFactory();
                beanFactory.setSerializationId(getId());
                //对IOC容器进行定制化,如设置启动参数,开启注解的自动装配等
                customizeBeanFactory(beanFactory);
                //调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器
                loadBeanDefinitions(beanFactory);
                synchronized (this.beanFactoryMonitor) {
                    this.beanFactory = beanFactory;
                }
            }
            catch (IOException ex) {
                throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
            }
        }

    在这个方法中,先判断 BeanFactory 是否存在,如果存在则先销毁 beans 并关闭 beanFactory,接着创建 DefaultListableBeanFactory,并调用 loadBeanDefinitions(beanFactory)装载 bean 定义。

    5.载入配置路径

    AbstractRefreshableApplicationContext 中只定义了抽象的 loadBeanDefinitions 方法,容器真正调用的是其子类 AbstractXmlApplicationContext 对该方法的实现,AbstractXmlApplicationContext的主要源码如下:

       @Override
        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
            // Create a new XmlBeanDefinitionReader for the given BeanFactory.
            //创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容器使用该读取器读取Bean定义资源
            XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
            // Configure the bean definition reader with this context's
            // resource loading environment.
            //为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的
            //祖先父类AbstractApplicationContext继承DefaultResourceLoader,因此,容器本身也是一个资源加载器
            beanDefinitionReader.setEnvironment(this.getEnvironment());
            beanDefinitionReader.setResourceLoader(this);
            //为Bean读取器设置SAX xml解析器
            beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    
            // Allow a subclass to provide custom initialization of the reader,
            // then proceed with actually loading the bean definitions.
            //当Bean读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制
            initBeanDefinitionReader(beanDefinitionReader);
            //Bean读取器真正实现加载的方法
            loadBeanDefinitions(beanDefinitionReader);
        }
    
        protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
            reader.setValidating(this.validating);
        }
    
        //Xml Bean读取器加载Bean定义资源
        protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
            //获取Bean定义资源的定位
            Resource[] configResources = getConfigResources();
            if (configResources != null) {
                //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
                //的Bean定义资源
                reader.loadBeanDefinitions(configResources);
            }
            //如果子类中获取的Bean定义资源定位为空,则获取FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源
            String[] configLocations = getConfigLocations();
            if (configLocations != null) {
                //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
                //的Bean定义资源
                reader.loadBeanDefinitions(configLocations);
            }
        }

    以XmlBean 读取器的其中一种策略 XmlBeanDefinitionReader 为例。XmlBeanDefinitionReader 调用其父类AbstractBeanDefinitionReader的 reader.loadBeanDefinitions()方法读取Bean配置资源。由于我们使用 ClassPathXmlApplicationContext 作为例子分析,因此 getConfigResources 的返回值为 null,因此程序执行 reader.loadBeanDefinitions(configLocations)分支

    6.分配路径处理策略

    在 AbstractBeanDefinitionReader 的抽象父类 AbstractBeanDefinitionReader 中定义了载入过程。AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源码如下:

      @Override
        public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
            Assert.notNull(resources, "Resource array must not be null");
            int counter = 0;
            for (Resource resource : resources) {
                counter += loadBeanDefinitions(resource);
            }
            return counter;
        }
        //重载方法,调用下面的loadBeanDefinitions(String, Set<Resource>);方法
        @Override
        public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
            return loadBeanDefinitions(location, null);
        }
    
        public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
            //获取在IoC容器初始化过程中设置的资源加载器
            ResourceLoader resourceLoader = getResourceLoader();
            if (resourceLoader == null) {
                throw new BeanDefinitionStoreException(
                        "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
            }
    
            if (resourceLoader instanceof ResourcePatternResolver) {
                // Resource pattern matching available.
                try {
                    //将指定位置的Bean定义资源文件解析为Spring IOC容器封装的资源
                    //加载多个指定位置的Bean定义资源文件
                    Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                    //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能
                    int loadCount = loadBeanDefinitions(resources);
                    if (actualResources != null) {
                        for (Resource resource : resources) {
                            actualResources.add(resource);
                        }
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                    }
                    return loadCount;
                }
                catch (IOException ex) {
                    throw new BeanDefinitionStoreException(
                            "Could not resolve bean definition resource pattern [" + location + "]", ex);
                }
            }
            else {
                // Can only load single resources by absolute URL.
                //将指定位置的Bean定义资源文件解析为Spring IOC容器封装的资源
                //加载单个指定位置的Bean定义资源文件
                Resource resource = resourceLoader.getResource(location);
                //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能
                int loadCount = loadBeanDefinitions(resource);
                if (actualResources != null) {
                    actualResources.add(resource);
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
                }
                return loadCount;
            }
        }
    
        //重载方法,调用loadBeanDefinitions(String);
        @Override
        public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
            Assert.notNull(locations, "Location array must not be null");
            int counter = 0;
            for (String location : locations) {
                counter += loadBeanDefinitions(location);
            }
            return counter;
        }

    AbstractRefreshableConfigApplicationContext 的 loadBeanDefinitions(Resource...resources) 方法实际上是调用 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法。从对 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源码分析可以看出该方法就做了两件事:首先,调用资源加载器的获取资源方法 resourceLoader.getResource(location),获取到要加载的资源。其次,真正执行加载功能是其子类 XmlBeanDefinitionReader 的 loadBeanDefinitions()方法。在loadBeanDefinitions()方法中调用了 AbstractApplicationContext 的getResources()方法,跟进去之后发现 getResources()方法其实定义在 ResourcePatternResolver 中,此时,我们有必要来看一下ResourcePatternResolver 的全类图:

    从上面可以看到 ResourceLoader 与 ApplicationContext 的继承关系,可以看出其实际调用的是DefaultResourceLoader 中 的 getSource() 方 法 定 位 Resource , 因 为ClassPathXmlApplicationContext 本身就是 DefaultResourceLoader 的实现类,所以此时又回到了ClassPathXmlApplicationContext 中来。

    7.解析配置文件路径

    XmlBeanDefinitionReader 通 过 调 用 ClassPathXmlApplicationContext 的 父 类DefaultResourceLoader 的 getResource()方法获取要加载的资源,其源码如下

      @Override
        public Resource getResource(String location) {
            Assert.notNull(location, "Location must not be null");
    
            for (ProtocolResolver protocolResolver : this.protocolResolvers) {
                Resource resource = protocolResolver.resolve(location, this);
                if (resource != null) {
                    return resource;
                }
            }
            //如果是类路径的方式,那需要使用ClassPathResource 来得到bean 文件的资源对象
            if (location.startsWith("/")) {
                return getResourceByPath(location);
            }
            else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
                return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
            }
            else {
                try {
                    // Try to parse the location as a URL...
                    // 如果是URL 方式,使用UrlResource 作为bean 文件的资源对象
                    URL url = new URL(location);
                    return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
                }
                catch (MalformedURLException ex) {
                    // No URL -> resolve as resource path.
                    //如果既不是classpath标识,又不是URL标识的Resource定位,则调用
                    //容器本身的getResourceByPath方法获取Resource
                    return getResourceByPath(location);
                }
            }
        }
    
        protected Resource getResourceByPath(String path) {
            return new ClassPathContextResource(path, getClassLoader());
        }

    DefaultResourceLoader 提供了 getResourceByPath()方法的实现,就是为了处理既不是 classpath标识,又不是 URL 标识的 Resource 定位这种情况

    在ClassPathResource 中完成了对整个路径的解析。这样,就可以从类路径上对 IOC 配置文件进行加载,当然我们可以按照这个逻辑从任何地方加载,在 Spring 中我们看到它提供的各种资源抽象,比如ClassPathResource、URLResource、FileSystemResource 等来供我们使用。上面我们看到的是定位Resource 的一个过程,而这只是加载过程的一部分。例如 FileSystemXmlApplication 容器就重写了getResourceByPath()方法:

    @Override
        protected Resource getResourceByPath(String path) {
            if (path.startsWith("/")) {
                path = path.substring(1);
            }
            return new FileSystemContextResource(path);
        }

    8.开始读取配置内容

    继续回到 XmlBeanDefinitionReader 的 loadBeanDefinitions(Resource …)方法看到代表 bean 文件的资源定义以后的载入过程

    //这里是载入XML形式Bean定义资源文件方法
        public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
            Assert.notNull(encodedResource, "EncodedResource must not be null");
            if (logger.isInfoEnabled()) {
                logger.info("Loading XML bean definitions from " + encodedResource.getResource());
            }
    
            Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
            if (currentResources == null) {
                currentResources = new HashSet<>(4);
                this.resourcesCurrentlyBeingLoaded.set(currentResources);
            }
            if (!currentResources.add(encodedResource)) {
                throw new BeanDefinitionStoreException(
                        "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
            }
            try {
                //将资源文件转为InputStream的IO流
                InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                    //从InputStream中得到XML的解析源
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }
                    //这里是具体的读取过程
                    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                }
                finally {
                    //关闭从Resource中得到的IO流
                    inputStream.close();
                }
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "IOException parsing XML document from " + encodedResource.getResource(), ex);
            }
            finally {
                currentResources.remove(encodedResource);
                if (currentResources.isEmpty()) {
                    this.resourcesCurrentlyBeingLoaded.remove();
                }
            }
        }
      //从特定XML文件中实际载入Bean定义资源的方法
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
          throws BeanDefinitionStoreException {
       try {
          //将XML文件转换为DOM对象,解析过程由documentLoader实现
          Document doc = doLoadDocument(inputSource, resource);
          //这里是启动对Bean定义解析的详细过程,该解析过程会用到Spring的Bean配置规则
          return registerBeanDefinitions(doc, resource);
       }
       catch (BeanDefinitionStoreException ex) {
          throw ex;
       }
      ...
    }

    通过源码分析,载入 Bean 配置信息的最后一步是将 Bean 配置信息转换为 Document 对象,该过程由documentLoader()方法实现 ,下面就是如何解析配置文件这边暂时不做分析了,把解析出的Bean以BeanDefinition形式存在,最后注册到IOC容器中,看下DefaultListableBeanFactory类registerBeanDefinition方法

    //存储注册信息的BeanDefinition
        private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);   
         @Override
        public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
                throws BeanDefinitionStoreException {
    
            Assert.hasText(beanName, "Bean name must not be empty");
            Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    
            //校验解析的BeanDefiniton
            if (beanDefinition instanceof AbstractBeanDefinition) {
                try {
                    ((AbstractBeanDefinition) beanDefinition).validate();
                }
                catch (BeanDefinitionValidationException ex) {
                    throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                            "Validation of bean definition failed", ex);
                }
            }
    
            BeanDefinition oldBeanDefinition;
    
            oldBeanDefinition = this.beanDefinitionMap.get(beanName);
    
            if (oldBeanDefinition != null) {
                if (!isAllowBeanDefinitionOverriding()) {
                    throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                            "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                            "': There is already [" + oldBeanDefinition + "] bound.");
                }
                else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
                    // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                    if (this.logger.isWarnEnabled()) {
                        this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                                "' with a framework-generated bean definition: replacing [" +
                                oldBeanDefinition + "] with [" + beanDefinition + "]");
                    }
                }
                else if (!beanDefinition.equals(oldBeanDefinition)) {
                    if (this.logger.isInfoEnabled()) {
                        this.logger.info("Overriding bean definition for bean '" + beanName +
                                "' with a different definition: replacing [" + oldBeanDefinition +
                                "] with [" + beanDefinition + "]");
                    }
                }
                else {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Overriding bean definition for bean '" + beanName +
                                "' with an equivalent definition: replacing [" + oldBeanDefinition +
                                "] with [" + beanDefinition + "]");
                    }
                }
                this.beanDefinitionMap.put(beanName, beanDefinition);
            }
            else {
                if (hasBeanCreationStarted()) {
                    // Cannot modify startup-time collection elements anymore (for stable iteration)
                    //注册的过程中需要线程同步,以保证数据的一致性
                    synchronized (this.beanDefinitionMap) {
                        this.beanDefinitionMap.put(beanName, beanDefinition);
                        List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                        updatedDefinitions.addAll(this.beanDefinitionNames);
                        updatedDefinitions.add(beanName);
                        this.beanDefinitionNames = updatedDefinitions;
                        if (this.manualSingletonNames.contains(beanName)) {
                            Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                            updatedSingletons.remove(beanName);
                            this.manualSingletonNames = updatedSingletons;
                        }
                    }
                }
                else {
                    // Still in startup registration phase
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    this.beanDefinitionNames.add(beanName);
                    this.manualSingletonNames.remove(beanName);
                }
                this.frozenBeanDefinitionNames = null;
            }
    
            //检查是否有同名的BeanDefinition已经在IOC容器中注册
            if (oldBeanDefinition != null || containsSingleton(beanName)) {
                //重置所有已经注册过的BeanDefinition的缓存
                resetBeanDefinition(beanName);
            }
        }

    IOC 容器初始化小结
    现在通过上面的代码,总结一下 IOC 容器初始化的基本步骤:
    1、初始化的入口在容器实现中的 refresh()调用来完成。
    2、对 Bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition(),
    其中的大致过程如下:通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader是默认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统,URL 等方式来定为资源位置。如果是 XmlBeanFactory 作为 IOC 容器,那么需要为它指定 Bean 定义的资源,也 就 是 说 Bean 定 义 文 件 时 通 过 抽 象 成 Resource 来 被 IOC 容 器 处 理 的 ,容 器 通 过BeanDefinitionReader 来 完 成 定 义 信 息 的 解 析 和 Bean 信 息 的 注 册 , 往 往 使 用 的 是XmlBeanDefinitionReader 来 解 析 Bean 的 XML 定 义 文 件 - 实 际 的 处 理 过 程 是 委 托 给BeanDefinitionParserDelegate 来完成的,从而得到 bean 的定义信息,这些信息在 Spring 中使用BeanDefinition对象来表示-这个名字可以让我们想到loadBeanDefinition(),registerBeanDefinition()这些相关方法。它们都是为处理 BeanDefinitin 服务的,容器解析得到 BeanDefinition 以后,需要把它在 IOC 容器中注册,这由 IOC 实现BeanDefinitionRegistry 接口来实现。注册过程就是在 IOC 容器内部维护的一个 HashMap 来保存得到的 BeanDefinition 的过程。这个 HashMap 是 IOC 容器持有Bean 信息的场所,以后对 Bean 的操作都是围绕这个 HashMap 来实现的。然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了,在使用 IOC容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IOC 风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。Spring本身提供了对声明式载入 web 应用程序用法的应用程序上下文,并将其存储在 ServletContext 中的框架实现

    最后是Spring IOC 运行时序图

     

     

     

  • 相关阅读:
    IDEA 中直接连接远程服务器
    浙江大学软件学院2020年保研上机
    PAT甲级近五年题一览表
    浙江大学计算机与软件学院2021年考研复试上机
    浙江大学计算机与软件学院2019年保研上机
    PAT(甲级)2021年春季考试
    PAT(甲级)2020年冬季考试
    PAT(甲级)2020年秋季考试
    PAT(甲级)2020年春季考试
    PAT(甲级)2019年冬季考试
  • 原文地址:https://www.cnblogs.com/gudazhi/p/11088413.html
Copyright © 2011-2022 走看看