Spring Bean 加载过程

一、Spring 容器高层视图

 

Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。

IoC文英全称Inversion of Control,即控制反转,可以这么理解IoC容器:把某些业务对象的的控制权交给一个平台或者框架来同一管理,这个同一管理的平台可以称为IoC容器。(https://blog.csdn.net/h_xiao_x/article/details/72774496

示例代码(详见):

 //在传统 XML 方法中,使用 ClassPathXmlApplicationContext 类来加载外部 XML 上下文文件
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        TestService testService = (TestService) context.getBean("testService");

        System.out.println("testService.name = " + testService.getName());

        //基于 Java 的配置 或者注解(AnnotatedBeanDefinitionReader)、类路径(ClassPathBeanDefinitionScanner)
        context = new AnnotationConfigApplicationContext(TestService.class);
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println("BeanDefinitionNames:" + beanName);
        }
        testService = context.getBean(TestService.class);
        System.out.println("testService.age = " + testService.getAge());

        //此处的配置类是使用 @Configuration 注释声明的 AppContext
        context = new AnnotationConfigApplicationContext(AppContext.class);
        Course course = context.getBean(Course.class);
        System.out.println("Course.name = " + course.getName());
        Module module = context.getBean(Module.class);
        System.out.println("module.assignment = " + module.getAssignment());

上面代码中,在创建ApplicationContext实例对象过程中会创建一个spring容器,该容器会读取配置文件"beans.xml",并统一管理由该文件中定义好的所有bean实例对象,如果要获取某个bean实例,使用getBean方法就行了。例如我们只需要将TestService 提前配置在beans.xml文件中(可以理解为注入),之后我们可以不需使用new TestService ()的方式创建实例,而是通过容器来获取TestService 实例,这就相当于将TestService 的控制权交由spring容器了,差不多这就是控制反转的概念。

那在创建IoC容器时经历了哪些呢?为此,先来了解下Spring中IoC容器分类,继而根据一个具体的容器来讲解IoC容器初始化的过程。
Spring中有两个主要的容器系列:

  1. 实现BeanFactory接口的简单容器;
  2. 实现ApplicationContext接口的高级容器。

我们可以认为直接的BeanFactory实现是IoC容器的基本形式,而各种ApplicationContext的实现是IoC容器的高级表现形式。ApplicationContext比较复杂,它不但继承了BeanFactory的大部分属性,还继承其它可扩展接口,扩展的了许多高级的属性,其接口定义如下:

public interface ApplicationContext extends EnvironmentCapable, 
                  ListableBeanFactory,    //继承于BeanFactory
                  HierarchicalBeanFactory,//继承于BeanFactory
                  MessageSource,            //
                  ApplicationEventPublisher,//
                  ResourcePatternResolver   //继承ResourceLoader,用于获取resource对象

在BeanFactory子类中有一个DefaultListableBeanFactory类,它包含了基本Spirng IoC容器所具有的重要功能,开发时不论是使用BeanFactory系列还是ApplicationContext系列来创建容器基本都会使用到DefaultListableBeanFactory类,可以这么说,在spring中实际上把它当成默认的IoC容器来使用。下文在源码实例分析时你将会看到这个类。

关于Spirng IoC容器的初始化过程在《Spirng技术内幕:深入解析Spring架构与设计原理》一书中有明确的指出,IoC容器的初始化过程可以分为三步:

  1. Resource定位(Bean的定义文件定位):Resouce定位指的是BeanDefinition的资源定位,它由ResouceLoader通过统一的Resouce接口完成,Resouce对各种形式的BeanDefinition提供了统一接口。比如:在文件系统中Bean的定义信息可以使用FileSystemResouce来进行抽象,在类路径中的Bean的定义信息可以使用ClassPathResouce来使用等。
  2. 将Resource定位好的资源载入到BeanDefinition:这个过程是把用户定义好的Bean表示成IOC容器内部的数据结构,而这个容器内部数据结构就是BeanDefinition。BeanDefinition实际上就是POJO对象在IOC容器中的抽象,通过对BeanDefinition定义的数据结构,使IOC容器能够方便地对POJO对象进行管理。
  3. 将BeanDefiniton注册到容器中:这个过程通过调用BeanDefinitionRepository接口的实现来完成。这个注册过程把载入过程中解析得到的BeanDefinition向IOC容器进行注册,在IOC容器的内部将BeanDefinition注入到ConcurrentHashMap对象中,通过KEY获取指定Bean的信息。
  • 第一步 Resource定位

Resource是Sping中用于封装I/O操作的接口。正如前面所见,在创建spring容器时,通常要访问XML配置文件,除此之外还可以通过访问文件类型、二进制流等方式访问资源,还有当需要网络上的资源时可以通过访问URL,Spring把这些文件统称为Resource,Resource的体系结构如下:

常用的resource资源类型如下:
  FileSystemResource:以文件的绝对路径方式进行访问资源,效果类似于Java中的File;
  ClassPathResourcee:以类路径的方式访问资源,效果类似于this.getClass().getResource("/").getPath();
  ServletContextResource:web应用根目录的方式访问资源,效果类似于request.getServletContext().getRealPath("");
  UrlResource:访问网络资源的实现类。例如file: http: ftp:等前缀的资源对象;
  ByteArrayResource: 访问字节数组资源的实现类。

那如何获取上图中对应的各种Resource对象呢?
Spring提供了ResourceLoader接口用于实现不同的Resource加载策略,该接口的实例对象中可以获取一个resource对象,也就是说将不同Resource实例的创建交给ResourceLoader的实现类来处理。ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源。ResourceLoader接口中只定义了两个方法:

Resource getResource(String location); //通过提供的资源location参数获取Resource实例
ClassLoader getClassLoader(); // 获取ClassLoader,通过ClassLoader可将资源载入JVM

注:ApplicationContext的所有实现类都实现ResourceLoader接口,因此可以直接调用getResource(参数)获取Resoure对象不同的ApplicatonContext实现类使用getResource方法取得的资源类型不同,例如:FileSystemXmlApplicationContext.getResource获取的就是FileSystemResource实例;ClassPathXmlApplicationContext.gerResource获取的就是ClassPathResource实例;

XmlWebApplicationContext.getResource获取的就是ServletContextResource实例,不需要通过xml直接使用注解@Configuation方式加载资源的AnnotationConfigApplicationContext等等。

在资源定位过程完成以后,就为资源文件中的bean的载入创造了I/O操作的条件,如何读取资源中的数据将会在下一步介绍的BeanDefinition的载入过程中描述。

  • 第二步 通过返回的resource对象,进行BeanDefinition的载入

1、什么是BeanDefinition? BeanDefinition与Resource的联系呢?官方文档中对BeanDefinition的解释如下:
  A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.
它们之间的联系从官方文档描述的一句话:Load bean definitions from the specified resource 中可见一斑。BeanDefinition是通过BeanDefinitionReader进行载入,BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个<bean>解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中:

	/**
	 * Load bean definitions from the specified resource.
	 * @param resource the resource descriptor
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

总之,BeanDefinition相当于一个数据结构,这个数据结构的生成过程是根据定位的resource资源对象中的bean而来的,这些bean在Spirng IoC容器内部表示成了的BeanDefintion这样的数据结构,IoC容器对bean的管理和依赖注入的实现都是通过操作BeanDefinition来进行的。

2、如何将BeanDefinition载入到容器?
  在Spring中配置文件主要格式是XML,对于用来读取XML型资源文件来进行初始化的IoC 容器而言,该类容器会使用到AbstractXmlApplicationContext类,该类定义了一个名为loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用于获取BeanDefinition:

// 该方法属于AbstractXmlApplicationContect类
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    this.initBeanDefinitionReader(beanDefinitionReader);
    // 用于获取BeanDefinition
    this.loadBeanDefinitions(beanDefinitionReader);
}

此方法在具体执行过程中首先会new一个与容器对应的BeanDefinitionReader型实例对象,然后将生成的BeanDefintionReader实例作为参数传入loadBeanDefintions(XmlBeanDefinitionReader),继续往下执行载入BeanDefintion的过程。例如AbstractXmlApplicationContext有两个实现类:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext,这些容器在调用此方法时会创建一个XmlBeanDefinitionReader类(AnnotationConfigApplicationContext 容器对应AnnotatedBeanDefinitionReader类)对象专门用来载入所有的BeanDefinition。

下面以XmlBeanDefinitionReader对象载入BeanDefinition为例,使用源码说明载入BeanDefinition的过程:

// 该方法属于AbstractXmlApplicationContect类
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources();//获取所有定位到的resource资源位置(用户定义)
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);//载入resources
        }
        String[] configLocations = getConfigLocations();//获取所有本地配置文件的位置(容器自身)
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);//载入resources
        }
}

经过了一次重载的方法,我们最终可以看到这个方法: AbstractBeanDefinitionReader的loadBeanDefinitions方法

/**
	 * Load bean definitions from the specified resource location.
	 */
	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
        //这里得到当前定义的ResourceLoader,默认的使用DefaultResourceLoader
		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容器封装的资源(Resource)
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				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.这里通过ResourceLoader来完成位置定位
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
			}
			return loadCount;
		}
	}

那么对于取得Resource的具体过程,((ResourcePatternResolver) resourceLoader).getResources(location)这个方法大家点开看看就可以知道,是PathMatchingResourcePatternResolver中的实现的,它具体里面就是针对我们配置的是否以“classpath*:” 开头分别处理。对于resourceLoader.getResource(location)方法,具体是交给继承ResourceLoader的子类完成的。这里不再多述了。

将xml文件转换成DOM对象

通过getResouce()方法得到Resouce之后,那么就开始了真正加载Bean资源。因为Spring可以对应不同形式的BeanDefition,我们这里讲的是xml的形式,所以就需要到xmlBeanDefinitonReader的实现中去看源码。

按照Spring的Bean规则对Document对象进行解析

走主线:得到dom对象后,开始启动对bean定义的详细解析,来看registerBeanDefinition()方法

                     XmlBeanDefinitionReader的loadBeanDefinitions()方法

  • 第三步,将BeanDefiniton注册到容器中

   最终Bean配置会被解析成BeanDefinition并与beanName,Alias一同封装到BeanDefinitionHolder类中, 之后beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),注册到DefaultListableBeanFactory.beanDefinitionMap中。之后客户端如果要获取Bean对象,Spring容器会根据注册的BeanDefinition信息进行实例化。DefaultListableBeanFactory就是所谓我们平常所说的 bean工厂,其父类就是 BeanFactory,BeanFactory有很多子类,DefaultListableBeanFactory就是其中一个子类。BeanDefinitionReaderUtils类:

/**
	 * Register the given bean definition with the given bean factory.
	 * @param definitionHolder the bean definition including name and aliases
	 * @param registry the bean factory to register with
	 * @throws BeanDefinitionStoreException if registration failed
	 */
public static void registerBeanDefinition(
            BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory) throws BeansException {

        // Register bean definition under primary name.
        String beanName = bdHolder.getBeanName();
     // 注册beanDefinition!!!
        beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition());

        // 如果有别名的话也注册进去,Register aliases for bean name, if any.
        String[] aliases = bdHolder.getAliases();
        if (aliases != null) {
            for (int i = 0; i < aliases.length; i++) {
                beanFactory.registerAlias(beanName, aliases[i]);
            }
        }
    }

DefaultListableBeanFactory实现了上面调用BeanDefinitionRegistry接口的 registerBeanDefinition( beanName,  bdHolder.getBeanDefinition())方法,这一部分的主要逻辑是向DefaultListableBeanFactory对象的beanDefinitionMap中存放beanDefinition,当初始化容器进行bean初始化时,在bean的生命周期分析里必然会在这个beanDefinitionMap中获取beanDefition实例,有机会成文分析一下bean的生命周期,到时可以分析一下如何使用这个beanDefinitionMap。bean的整个生命周期是围绕AbstractApplicationContext.refresh() 来进行的。

    /** Map of bean definition objects, keyed by bean name */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

registerBeanDefinition( beanName,  bdHolder.getBeanDefinition() )方法具体方法如下:

    //---------------------------------------------------------------------
    // Implementation of BeanDefinitionRegistry interface
    //---------------------------------------------------------------------
    @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");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                ((AbstractBeanDefinition) beanDefinition).validate();
            } catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }
        // beanDefinitionMap是个ConcurrentHashMap类型数据,用于存放beanDefinition,它的key值是beanName
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) {
            /*...此处去掉了各种IF ELSE 校验... */
            //覆盖
            this.beanDefinitionMap.put(beanName, beanDefinition);
        } else {
            if (hasBeanCreationStarted()) {
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                synchronized (this.beanDefinitionMap) {
                    // 将获取到的BeanDefinition放入Map中,容器操作使用bean时通过这个HashMap找到具体的BeanDefinition
                    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;
        }

        if (existingDefinition != null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        }
    }

 

容器的初始化是通过AbstractApplicationContext的refresh()实现的。整个过程可以理解为容器的初始化过程。bean的整个生命周期是围绕refresh() 来进行的,那就先来看下代码吧:

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      //1.容器预先准备好刷新上下文,记录容器启动时间和标记等.
      prepareRefresh();

      //2.创建内部Bean工厂,如果已有则销毁,没有则创建;里面实现对beanDefinition的装载,
      //Bean定义资源的Resource定位、载入解析和注册
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      //3.配置bean工厂的标准上下文特性,如类装载器、PostProcessor等以备使用。
      prepareBeanFactory(beanFactory);

      try {
         //4.模板方法,在所有beanDefinition被装载后,提供一个修改beanFactory的入口
         postProcessBeanFactory(beanFactory);

         //5.在spring的环境中去执行已经被注册的 Factory processors,设置执行自定义的
         //postProcessBeanFactory和spring内部自己定义的,常见的PropertyPlaceholderConfigurer就是在这里被调用的
         invokeBeanFactoryPostProcessors(beanFactory);

         //6.注册用于拦截bean 创建过程的postProcessor
         registerBeanPostProcessors(beanFactory);

         //7.初始化MessageSource(国际化资源文件等)上下文的消息源。
         initMessageSource();

         //8.初始化此上下文的事件多播程序。
         initApplicationEventMulticaster();

         //9.在特定上下文子类中初始化其他特殊bean。
         onRefresh();

         //10.检查侦听器bean并注册它们。
         registerListeners();

         //11.完成实例化所有剩余的(非懒加载)单例。
         //里面的beanFactory.preInstantiateSingletons()方法完成单例对象的创建
         finishBeanFactoryInitialization(beanFactory);

         //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.
         destroyBeans();

         // Reset 'active' flag.
         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();
      }
   }
}

该图描述了Spring容器从加载配置文件到创建出一个完整Bean的作业流程:

1、ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源;

2、BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个<bean>解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;

3、容器扫描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射机制自动识别出Bean工厂后处理后器(实现BeanFactoryPostProcessor接口)的Bean,然后调用这些Bean工厂后处理器对BeanDefinitionRegistry中的BeanDefinition进行加工处理。主要完成以下两项工作:

    1)对使用到占位符的<bean>元素标签进行解析,得到最终的配置值,这意味对一些半成品式的BeanDefinition对象进行加工处理并得到成品的BeanDefinition对象;

    2)对BeanDefinitionRegistry中的BeanDefinition进行扫描,通过Java反射机制找出所有属性编辑器的Bean(实现java.beans.PropertyEditor接口的Bean),并自动将它们注册到Spring容器的属性编辑器注册表中(PropertyEditorRegistry)

4.Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy着手进行Bean实例化的工作;

5.在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作;

6.利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。

Spring容器确实堪称一部设计精密的机器,其内部拥有众多的组件和装置。Spring的高明之处在于,它使用众多接口描绘出了所有装置的蓝图,构建好Spring的骨架,继而通过继承体系层层推演,不断丰富,最终让Spring成为有血有肉的完整的框架。所以查看Spring框架的源码时,有两条清晰可见的脉络:

1)接口层描述了容器的重要组件及组件间的协作关系;

2)继承体系逐步实现组件的各项功能。

接口层清晰地勾勒出Spring框架的高层功能,框架脉络呼之欲出。有了接口层抽象的描述后,不但Spring自己可以提供具体的实现,任何第三方组织也可以提供不同实现, 可以说Spring完善的接口层使框架的扩展性得到了很好的保证。纵向继承体系的逐步扩展,分步骤地实现框架的功能,这种实现方案保证了框架功能不会堆积在某些类的身上,造成过重的代码逻辑负载,框架的复杂度被完美地分解开了。

Spring组件按其所承担的角色可以划分为两类:

1)物料组件:Resource、BeanDefinition、PropertyEditor以及最终的Bean等,它们是加工流程中被加工、被消费的组件,就像流水线上被加工的物料;

2)加工设备组件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrapper等组件像是流水线上不同环节的加工设备,对物料组件进行加工处理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值