目录
1.4 AutowireCapableBeanFactory
一、IOC架构
Spring通过一个配置文件描述Bean及Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。 Spring的IoC容器在完成这些底层工作的基础上,还提供了Bean实例缓存、生命周期管理、 Bean实例代理、事件发布、资源装载等高级服务。
Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。
二、核心容器
1、BeanFactory
BeanFactory是Spring框架的基础设施,面向Spring本身。Spring中Bean的创建是典型的工厂模式,BeanFactory及其子接口和子接口的实现类构成了一系列的Bean工厂,即IoC容器。
BeanFactory的继承关系:
子接口和实现类详解:
1.1 BeanFactory
BeanFactory是所有接口的父接口,定义了IoC容器的基本功能规范。
主要方法:
- getBean:用于根据名称、类型等信息,从容器中返回Bean;
- getBeanProvider:根据类型获取Bean的ObjectProvider;
- isSingleton:判断Bean是否是单例;
- isPrototype:是否是原型;
- isTypeMatch:判断给定名称的Bean是否匹配指定的类型;
- getType:获取指定名称的Bean的类型。
1.2 ListableBeanFactory
BeanFactory的子接口。接口定义了访问容器中Bean基本信息的若干方法,如查看Bean的个数、获取某一类型Bean的配置名、查看容器中是否包括某一Bean等方法。
主要方法:
1、BeanDefinition相关的方法:
- containsBeanDefinition:判断该BeanFactory是否包含指定名称的BeanDefinition;
- getBeanDefinitionCount:获取BeanDefinition的数量;
- getBeanDefinitionNames:获取BeanDefinition的名称全量;
2、获取Bean相关的方法:
- getBeanNamesForType:根据类型、是否单例、饥饿加载还是懒加载等信息,查询BeanName的数组;
- getBeansOfType:根据类型、是否单例、饥饿加载还是懒加载等信息,查询Bean的数组;
3、注解相关的方法:
- getBeanNamesForAnnotation:根据注解类型查找BeanName的数组;
- getBeansWithAnnotation:根据注解类型查找Bean的数组;
- findAnnotationOnBean:根据beanName和注解类型,查找注解。
1.3 HierarchicalBeanFactory
HierarchicalBeanFactory是BeanFactory的子类。是一种父子级联IoC容器的接口,使得Spring的IoC容器可以建立父子层级关联的容器体系,子容器可以通过接口方法访问父容器,但父容器不能访问子容器的Bean。
Spring使用父子级联容器实现了很多功能,比如在Spring MVC中,表现层Bean位于一个子容器中,而业务层和持久层的Bean位于父容器中。这样,表现层 Bean就可以引用业务层和持久层的Bean,而业务层和持久层的Bean则看不到展现层的Bean。
主要方法:
- getParentBeanFactory:返回父BeanFactory
- containsLocalBean:是否包含指定名称的Bean,查询范围不包括父容器。
1.4 AutowireCapableBeanFactory
AutowireCapableBeanFactory是BeanFactory的子接口,定义了将容器中的Bean按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法。
主要方法:
- createBean:根据给定类型、装配模式(懒加载、延迟加载等)等创建新的Bean;
- autowireBean:对给定的Bean的属性进行自动装配;
- configureBean:对给定的Bean的属性进行自动装配;
- autowire:根据给定的类型、加载模式等实例化一个Bean;
- autowireBeanProperties:装配Bean的属性;
- destroyBean:删除Bean;
- resolveNamedBean:解析唯一匹配给定类型的Bean实例;
- resolveDependency:根据此工厂中定义的bean解析指定的依赖关系。
1.5 ConfigurableBeanFactory
ConfigurableBeanFactory是HierarchicalBeanFactory的子接口。是一个重要的接口,增强了IoC容器的可定制性。它定义了设置类加载器、属性编辑器、容器初始化后置处理器等方法。
2、BeanDefinition
BeanDefinition相当于Spring的类。Java通过类来创建实例,同理,Spring通过BeanDefinition来创建Bean。Spring管理Bean组件需要经过实例化的过程,这个实例化过程除了Java类所拥有的信息,还要解决作用域、实例化条件、组件依赖等一系列问题,Spring将这些信息封装在了BeanDefinition对象中。可以这样来理解:Bean是高级的实例,BeanDefinition是高级的class。
3、BeanDefinitionReader
Bean的解析过程非常复杂,功能被划分的非常详细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。这个解析过程,主要是通过对BeanDefinitionReader来完成。
4、ApplicationContext
ApplicationContext是在BeanFactory基础上定义的,实际上ApplicationContext是BeanFactory的一个超全集。ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合我们都直接使用ApplicationContext而非底层的BeanFactory。
实际上,在BeanFactory的基础上,ApplicationContext还通过继承其他接口,扩展了一些功能:从属性文件从解析文本信息和将事件传递给所指定的监听器。
ApplicationContext的继承关系:
4.1 资源加载:ResourceLoader
ResourceLoader接口:
public interface ResourceLoader {
/** 从class path 路径加载时的伪资源URL: "classpath:" 。web.xml配置中指定类路径下资源时便要加此前缀*/
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/**返回指定path的资源*/
Resource getResource(String location);
/**需要直接访问ClassLoader的客户端可以这样做,即以与ResourceLoader统一的方式,而不是依赖线程上下文ClassLoader*/
ClassLoader getClassLoader();
}
应用示例:
ApplicationContext appCtx=FileSystemXMLApplication("applicationContext.xml");
Resource resource = appCtx.getResource("file_in_resources.properties");
//使用Resource对象获取文件
File file = resource.getFile();
4.2 常用的实现类
常用的ApplicationContext接口实现类包括:
- FileSystemXmlApplicationContext:该容器从XML文件中加载已被定义的bean,要求给构造器提供XML文件的完整路径;
- ClassPathXmlApplicationContext:该容器从XML文件中加载已被定义的 bean,不需要提供XML文件的路径,只需正确配置 CLASSPATH环境变量,容器会从CLASSPATH中搜索bean配置文件;
- WebXmlApplicationContext:该容器会在一个web应用程序的范围内加载在XML文件中已被定义的bean。
5、WebApplicationContext
5.1 继承关系
WebApplicationContext是ApplicationContext的子接口,是专门为Web服务准备的。WebApplicationContext的继承关系图:
5.2 和ServletContext的关系
WebApplicationContext是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化工作。从WebApplicationContext中可以获得ServletContext的引用,而整个Web应用上下文对象也将作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。从ServletContext中获取WebApplicationContext的方式:
WebApplicationContext context = (WebApplicationContext) servletContext.
getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
WebApplicationContext和ServletContext的关系示意图:
5.3 初始化
WebApplicationContext的初始化需要ServletContext实例,必须在拥有Web容器的前提下才能完成启动的工作。可以在web.xml中配置自启动的Servlet或定义Web容器监听器(ServletContextListener),借助这两者中的任何一个就可以完成启动Spring Web应用上下文的工作。
Spring提供了用于启动WebApplicationContext的Servlet和Web容器监听器:
- org.springframework.web.context.ContextLoaderServlet
- org.springframework.web.context.ContextLoaderListener
由于WebApplicationContext需要使用日志功能,比如日志框架使用Log4J,用户可以将Log4J的配置文件放置到类路径WEB-INF/classes下,这时Log4J引擎即可顺利启动。如果Log4J配置文件放置在其他位置,还需要在web.xml指定Log4J配置文件位置。
四、Bean的生命周期
1、作用域
Bean的作用域使用scope来配置:
<bean id="role" class="spring.chapter2.maryGame.Role" scope="singleton"/>
1.1 singleton
singleton表示单例。当一个bean的作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。这个单一实例会被存储到单例缓存(singleton cache)中。
这里的singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个ClassLoader中该class只有一个实例,而这里的singleton则表示一个容器中该BeanDefinition只对应一个bean。
1.2 prototype
prototype表示原型。prototype作用域部署的bean,每一次请求都会产生一个新的bean实例。
Spring不能对一个prototype作用域的bean的整个生命周期负责,容器在初始化、配置、装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。清除prototype作用域的对象并释放任何prototype bean所持有的资源,都是客户端代码的职责。
1.3 request
专用于Web应用程序上下文的一种作用域。
request作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效。使用的时候首先要在初始化web的web.xml中配置ContextListener。
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
1.4 session
专用于Web应用程序上下文的一种作用域。
session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效。和request配置实例的前提一样,配置好web启动文件即可。
1.5 global session
专用于Web应用程序上下文的一种作用域。
global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。如果在web中使用global session作用域来标识bean,那么web会自动当成session类型来使用。和request配置实例的前提一样,配置好web启动文件即可。
1.6 自定义bean装配作用域
spring支持用户自定义作用域,甚至可以重新定义已有的作用域,只要不覆盖singleton和prototype即可。spring的作用域由接口org.springframework.beans.factory.config.Scope来定义,自定义自己的作用域只要实现该接口。
2、生命周期
Spring只帮我们管理单例模式Bean的完整生命周期,对于prototype的bean,Spring在创建好交给使用者之后则不会再管理后续的生命周期。
2.1 Bean的创建
Bean创建的主要流程:
- 实例化Bean;
- 填充属性;
- 设置Bean的基本配置:BeanName、BeanFactory、ApplicationContext;
- 调用InitializingBean的afterPropertiesSet方法;
- 实例化Bean:用构造器或调用定制的初始化方法。Bean配置中,可以通过@Bean的属性initMethod指定初始化的名称;
- 实例化Bean的属性:用构造器或调用定制的初始化方法;
- 调用初始化前处理器:BeanPostProcess.postProcessBeforeInitialization;
- 调用定制的初始化方法。Bean配置中,可以通过@Bean的属性initMethod指定初始化的名称;
- 调用初始化后处理器:BeanPostProcess.postProcessAfterInitialization;
- 调用@PostConstruct注解标识的方法。
2.2 Bean的销毁
- 调用DispostbleBean的destroy方法;
- 调用定制的销毁方法。
五、基于注解的IoC初始化
1、相关注解
1.1 类级别的注解
类级别的注解包括:Spring的 @Component、@Repository、@Controller、@Service,以及Java EE 6的@ManagedBean、@Named。
Spring IoC容器根据注解的过滤规则扫描读取注解Bean定义类,并将其注册到Spring IoC容器中。
1.2 类内部的注解
类内部的注解包括:@Autowire、@Value、@Resource,以及EJB和WebService相关注解等,都是添加到类内部的字段或方法上的。
Spring IoC通过Bean后置注解处理解析器分析Bean内部的注解。
2、流程
2.1 定位Bean扫描路径
Spring中管理注解的Bean定义的容器有两个:AnnotationConfigApplicationContext和AnnotationConfigWebApplicationContext。这两个是专门用来处理Spring注解方式配置的容器,直接依赖于将注解作为容器配置信息来源的IoC容器。其中,后者是前者的web版本,两者用法和对注解的处理方式基本没有差别。这里以AnnotationConfigApplicationContext为例。
Spring对注解的处理方式有两种:
- 直接将注解Bean注册到容器中:可以在初始化容器时注册;也可以在容器创建后手动调用注册方法向容器注册,然后通过手动刷新容器,使得容器对注册的注解Bean进行处理;
- 通过扫描指定的包及其子包下的所有类处理:在初始化注解容器时指定要自动扫描的路径,如果容器创建以后向给定路径动态添加了注解Bean,则需要手动调用容器扫描的方法手动刷新容器,使容器对所注册的注解Bean进行处理。
2.2 读取注解的元数据
AnnotationConfigApplicationContext通过调用注解Bean定义读取器注册注解Bean。
AnnotatedBeanDefinitionReader的register()方法向容器注册指定的注解Bean定义类的基本步骤:
- 解析作用域:使用元数据解析器解析注解Bean中关于作用域的配置:原型还是单例;
- 处理通用注解:使用AnnotationConfigUtils.processCommonDefinitionAnnotations()方法处理注解Bean定义类中通用的注解:@Lazy(懒加载)、@Primary(依赖注入时优先选择)、@DependsOn(当前Bean依赖另一个Bean)、@Role、@Description、@Qualifier(指定注入Bean的名称);
- 代理Bean定义类数据:使用AnnotationConfigUtils.applyScopedProxyMode()方法根据作用域代理类型创建代理对象,作用域代理类型包括NO(默认值)、INTERFACES(JDK动态代理)、TARGET_CLASS(CGLib)。作用域代理通过@Scope注解的proxyMode设置;
- 注册Bean:通过BeanDefinitionReaderUtils向容器中注册Bean,即放入到BeanDefinitionRegistry的Map中。
2.3 扫描指定包并解析为BeanDefinition
当创建注解处理容器时,如果传入的初始参数是注解Bean定义类所在的包,注解容器将扫描给定的包及其子包,将扫描到的注解Bean定义载入并进行注册。
1、扫描给定的包及其子包
AnnotationConfigApplicationContext通过调用类路径Bean定义扫描器ClassPathBeanDefinitionScanner扫描给定包及其子包下的所有类。
主要流程:
- 组件扫描:根据包名拼接所有class文件的全限定名的正则表达式,classpath*:包名/**/*.class,同时根据注解类型过滤,找出全部候选组件,得到Set<BeanDefinition>;
- 设置每个BeanDefinition的基础属性和通用注解,基础包括:beanName、作用域、自动依赖注入等;
- 代理Bean定义类数据;
- 注册Bean:向容器中注册扫描到的Bean。
六、依赖注入
1、依赖注入的时机
依赖注入发生在以下两种情况:
- 延迟加载:用户第一次调用getBean()方法时,IoC容器触发依赖注入;
- 饥饿加载:当用户在配置文件中将<bean>元素配置了lazy-init=false属性时,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。
2、Bean的创建
Bean的创建在AbstractAutowireCapableBeanFactory的createBean()方法中进行创建。如果Bean配置了初始化前处理器和初始化后处理器,则直接返回一个需要创建的Bean的代理对象;
2.1 Bean实例化的策略:
- 如果RootBeanDefinition包含工厂方法,则使用工厂方法实例化;
- 如果配置了自动装配属性,则使用容器的自动装配进行实例化,自动装配根据参数类型匹配Bean的构造方法;
- 没有配置自动装配属性,则使用默认的无参构造器进行实例化,使用默认的无参构造器需要使用相应的初始化策略(JDK的反射机制,或者CGLib)。
2.2 默认无参构造器进行实例化
如果Bean的方法被覆盖了,就使用CGLib进行实例化,否则使用JDK的反射机制,调用构造器.newInstance()进行实例化。
CGLib是一个常用的字节码生成器的类库,它提供了一系列API实现Java字节码的生成和转换功能。
3、依赖注入
3.1 准备依赖注入
对Bean的依赖注入主要分成两个步骤:
- 调用createBeanInstance()方法生成Bean所包含的Java对象实例;
- 调用populateBean()方法对Bean属性的依赖注入进行处理。
属性的依赖注入的过程分为两种情况:
- 属性值类型不需要强制转换时,不需要解析属性值,直接进行依赖注入;
- 属性值类型需要强制转换时,如归其他对象的引用,首先需要解析属性值,然后对解析后的属性值进行依赖注入。
属性值类型需要强制转换的情况,例如xml中Bean的配置是另一个Bean实例对象的引用,这时就需要容器先根据属性值解析出所引用的的对象,然后才能将该引用对象注入到目标实例对象的属性上。
1、对引用类型进行解析
- 如果引用类型在父类容器中,则从父类容器中获取指定的引用类型;
- 如果引用类型不在父类容器中,则从当前容器中获取引用;
- 如果当前容器中指定的Bean没有被实例化,则递归触发Bean的初始化和依赖注入。
2、对内部类进行解析
调用内部类的解析方法进行解析。
3、对数组类型进行解析
- 获取数组类型;
- 获取数组元素类型;
- 使用反射获取指定类型的对象;
- 如果没有获取到数组类型和数组元素类型,则将数组类型设置为Object数组。
4、对集合类型进行解析
- 创建指定类型的数组,用于存放和返回解析后的数组;
- 递归解析集合的每个元素,并将结果放入到上面的数组中;
- 将上面的数组中的结果存入到指定的集合中。
3.2 注入赋值
BeanWrapperImpl类负责对容器中完成初始化的Bean实例对象进行属性的依赖注入,即把Bean对象设置到它所依赖的另一个Bean的属性上。
Spring IoC容器将属性值注入到Bean实例对象的方式:
- 对于集合类型的属性,将属性值解析为目标类型的集合后直接赋值给属性;
- 对于非集合类型的属性,大量使用JDK的反射机制,通过属性的getter()方法获取指定属性注入前的值,同时调用setter()方法为属性设置注入后的值。
3.3 管理Bean依赖关系的方式
Spring IoC容器提供了两种管理Bean依赖关系的方式:
- 显示管理:通过BeanDefinition的属性值和构造方法实现Bean依赖关系的管理。例如在XML中定义的Bean中,定义属性的值为其他的Bean实例;
- autowiring:自动装配,不需要对Bean属性的依赖关系做显式的声明,只需要配置好autowiring属性,IoC容器会自动反射查找属性的类型和名称,然后给予属性的类型或者名称来自动匹配容器中的Bean,完成自动注入。例如使用注解定义的Bean。
autowiring自动装配的实现过程:
- 对Bean属性调用getBean()方法,完成依赖Bean的初始化和依赖注入;
- 将依赖Bean的属性引用设置到被依赖的Bean属性上;
- 将依赖Bean的名称和被依赖Bean的名称存储到IoC容器的集合上。存储的形式是Map<String, Set<String>>,key是依赖Bean的名称,Set<String>是被依赖Bean的名称的集合。
七、Bean加载过程综述
1、加载配置信息
ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源;
2、解析并存储BeanDefinition
BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;
3、用BeanFactoryPostProcessor处理BeanDefinition
容器扫描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射机制自动识别出Bean工厂后处理后器(实现BeanFactoryPostProcessor接口)的Bean,然后调用这些Bean工厂后处理器对BeanDefinitionRegistry中的BeanDefinition进行加工处理。主要完成以下两项工作:
- 对使用到占位符的元素标签进行解析,得到最终的配置值,这意味对一些半成品式的BeanDefinition对象进行加工处理并得到成品的BeanDefinition对象;
- 对BeanDefinitionRegistry中的BeanDefinition进行扫描,通过Java反射机制找出所有属性编辑器的Bean(实现java.beans.PropertyEditor接口的Bean),并自动将它们注册到Spring容器的属性编辑器注册表中(PropertyEditorRegistry);
4、 实例化Bean
Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy着手进行Bean实例化的工作。
5、设置Bean的属性
在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作。
6、用BeanPostProcessor处理Bean
利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。
八、其他概念
1、Bean定义注册表
Spring通过BeanDefinitionRegistry的实例来存储Bean定义信息BeanDefinition的实例,以Map的形式保存。
2、Bean单例缓存池
Spring在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例Bean的缓存器,它是一个用HashMap实现的缓存器,单实例的Bean以beanName为键保存在这个HashMap中。Bean直接的依赖关系,则通过Map<String, Set<String>>的形式保存,里面的字符串都是Bean的名称。