Spring 的 Core Technology - IoC 文档阅读笔记

2020/03/15 18:00

因为需要开发一个 GORM data service 的自动注册功能,今天重新第N次阅读Spring官方的 Spring Core 文档 并记录下其中的重点。

1. The IoC Container

1.1. Introduction to the Spring IoC Container and Beans

org.springframework.context.ApplicationContext interface 代表了 Spring IoC 容器,它负责 初始化, 配置, 和组装 beans。

ApplicationContext 有几个实现,分别是基于 xml 配置文件的 ClassPathXmlApplicationContext 和 用groovy脚本来进行配置的 GenericGroovyApplicationContext

Spring 用 BeanFactory 来实现最基础的bean容器功能,用 ApplicationContext 来实现更丰富的企业应用所需功能,例如 Message resources、国际化等。

In short, the BeanFactory provides the configuration framework and basic functionality adds more enterprise-specific functionality.

1.2. Container Overview

Your application classes are combined with configuration metadata so that, after the ApplicationContext is created and initialized, you have a fully configured and executable system or application.

配置元信息就是描述如何配置 spring bean 的配置信息。

1.2.1. Configuration Metadata

配置元信息通常是 xml 格式的。但也可以是基于Java注解的配置(Annotation-based configuration) 或者 基于Java 类的配置(Java-based configuration)。

基于Java注解的配置 是在你的业务类中用注解进行匹配的描述。而 基于Java类的配置 是在你的Java业务类以外,用 Java 类来进行配置的描述。

XML-based configuration metadata configures these beans as elements inside a top-level element.

Java configuration typically uses @Bean-annotated methods within a @Configuration class.

例如,基于 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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>
    <!-- 用 groovy 脚本定义一个 bean -->
    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>
    <!-- more bean definitions go here -->
</beans>

基于 Java 的配置定义像这样:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

基于 Groovy 的配置定义,像这样:

beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

这里 dataSource 是bean的名字,方法参数是bean类型,方法体(其实是一个groovy闭包)里面的幅值语句是给bean的属性幅值。

要注意的是,用groovy定义形式,顺序是非常重要的,因为它是一个程序片段,所以前面的代码不能引用后面定义的bean,只有后面的代码才可以引用前面的定义bean。

1.2.2. Instantiating a Container

初始化一个xml配置的容器像这样:

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

要初始化一个基于 Groovy 的配置容器,像这样:

static void initSpringContext() {
    if (!springContext) {
        springContext = new GenericGroovyApplicationContext(
        "conf/spring-application.xml", "conf/spring-application.groovy")
        // 扫描指定的包,以便支持 auto-wire 依赖注入
        // scan package for components - Annotaion-Based Configuration
        new ClassPathBeanDefinitionScanner(springContext).scan('com.telecwin.gcl')
        // GenericGroovyApplicatonContext 会自动刷新,所以不能再调用 springContext.refresh() 了
        springContext
    }
}

1.2.3. Using the Container

使用一个容器,像这样:

// create and configure beans
// ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
// List<String> userList = service.getUsernameList();

1.3. Bean Overview

Bean 对象是由容器管理的,容器用配置信息来创建bean对象,这个配置信息称为 “bean definition (bean定义)”。bean 定义在容器内是用类 BeanDefinition 来表示的,它包含下面的信息:

  • class name,包括包名。
  • 行为相关的配置信息(Bean behavioral configuration elements),state how the bean should behave in the container (scope, lifecycle callbacks, and so forth).
  • collaborators or dependencies . 引用其他bean. 这种引用被称为“协作者”或“依赖” collaborators or dependencies.
  • bean的其他属性值,例如一个线程池的线程最大数量等。

一种奇特的技术

In addition to bean definitions that contain information on how to create a specific bean, the ApplicationContext implementations also permit the registration of existing objects that are created outside the container (by users). This is done by accessing the ApplicationContext’s BeanFactory through the getBeanFactory() method, which returns the BeanFactory DefaultListableBeanFactory implementation. DefaultListableBeanFactory supports this registration through the registerSingleton(…) and registerBeanDefinition(…) methods.

好像 grails 就是利用这种方式实现的让 spring 管理 service、controller 对象。

但是 spring 警告说:

Bean metadata and manually supplied singleton instances need to be registered as early as possible, in order for the container to properly reason about them during autowiring and other introspection steps. While overriding existing metadata and existing singleton instances is supported to some degree, the registration of new beans at runtime (concurrently with live access to the factory) is not officially supported and may lead to concurrent access exceptions, inconsistent state in the bean container, or both.

上面的警告,总结起来就是:

  1. 外部提供的单例对象和bean metadata,要越早越好。
  2. 重载已经存在的 bean metadata 和 单例对象支持是有限的。
  3. 在运作时动态注册 新的 bean,同时又访问 factory,例如取一个 bean,官方是不支持的,可能导致访问异常、容器状态错误等问题。

1.3.1. Naming Beans

一个 bean 可以有一个或多个 id,多出来的叫 alias 别名。
<bean id="beanId"> 定义一个 id,用 <bean name="beanName, beanAlias"> 来定义一个或多个名字和别名。

也可以在外部定义别名,像这样:

<alias name="fromName" alias="toName"/>

Java-based configuration 是这样定义别名的:

@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

有了 id 和 name,就可以被其他的 bean 引用了。

1.3.2. Instantiating Beans

用 Bean definition 这个东西(就像厨师的食谱)来创建 Bean。通常在 Bean Definition 中会有 class 这个属性,容器就会用这个 class 来创建新的 bean,但也有例外,那就是使用 Instantiation by Using an Instance Factory Method and Bean Definition Inheritance 来创建 Bean。

用 class 属性提供的 类名 创建 bean,可以有两种方法:

  • 用 Java 的构造函数 new 一个对象,当然这里用的是反射技术来调用构造函数。
  • 用 类的 static factory method,静态工厂函数,来创建 bean。

… 此处省略很多字,以后再补…

1.8. Container Extension Points

如果我们要扩展一个容器的功能,不用继承 ApplicationContext 的实现类,而只需要提供实现了特殊接口的插件对象即可。这些特殊的接口有:

  • BPP 接口 BeanPostProcessor,它可以用来修改容器创建的 bean,例如给已经生产的 bean 设置某些属性,也可以将整个 bean 给替换成别的对象。
  • BFPP BeanFactoryPostProcessor(比 BPP 多一个F),是用来修改 配置元信息(Configuration Metadata) 的。也就是说 Spring 容器让 BFPP 先修改 bean 的配置元信息,然后再用来初始化 bean。

Spring IoC container lets a BeanFactoryPostProcessor read the configuration metadata and potentially change it before the container instantiates any beans other than BeanFactoryPostProcessor instances.

这里我们就容易理解为什么BFPP要叫 BeanFactory-PostProcessor 了,因为配置元信息就像一个对象工厂,是用来生产bean对象的,因此对BeanFactory进行后续处理的接口,当然就叫 BeanFactory-PostProcessor 了。

Spring 提供了一些 BeanFactory Post Processor,例如 PropertySourcesPlaceholderConfigurer,就是用来实现属性值变量替换的,例如:

<bean class="xxxx">
  <property name="password" value="${password}"/>
</bean>
  • BeanDefinitionRegistryPostProcessor BDRPP 接口,它继承自 BFPP

Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in. In particular, BeanDefinitionRegistryPostProcessor may register further bean definitions which in turn define BeanFactoryPostProcessor instances.

允许在常规的 BFPP 介入前注册更多的 Bean Definitions,具体来说,BeanDefinitionRegistryPostProcessor 可能会注册更多的 bean definition,这些定义会定义 BFPP 即 BeanFactoryPostProcessor 实例。

它的定义如下:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

	/**
	 * Modify the application context's internal bean definition registry after its
	 * standard initialization. All regular bean definitions will have been loaded,
	 * but no beans will have been instantiated yet. This allows for adding further
	 * bean definitions before the next post-processing phase kicks in.
	 * @param registry the bean definition registry used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}

MyBatis 的 MapperScannerConfigurer 就实现了这个接口。

发布了63 篇原创文章 · 获赞 25 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/yangbo_hr/article/details/104881562