一、概述
IOC(Inversion of control)控制反转,所谓控制反转就是原先需要我们自己实现的对象创建、依赖的代码,反转给容器来帮忙实现(获得依赖对象的过程由主动变成被动,控制权反转)。
那这样就需要一个容器,同事需要一种描述来让容器知道需要创建的对象与对象的关系,这个描述的具体表现就是项目中常见的配置文件。
DI(Dependency Injection)依赖注入,就是指对象不是从容器中查找它依赖的类,而且容器在实例化的时候主动将它依赖的类注入。
二、让我们自己来设计这样的视角考虑
因为我们工作中更多的是业务需求,如果把技术需求当做业务需求,这样思考有利于提升我们的技术思维。
1、对象与对象的关键怎么表示?
可以使用xml、properties、yml等配置文件表示
2、文件放在哪里?
classpath,filesystem,或者远程服务器中,Context等。
3、不同的文件格式对象的描述不一样,如何标准化统一?
需要内部有一个统一的关于对象的定义(BeanDefinition),所有的外部描述都必须转化为统一的描述定义。
4、如何解析不同的配置文件
需要采用不同的解析器解析对应的配置文件
三、Spring 核心容器类
1、BeanFactory
Spring Bean的创建是工厂模式,即IOC容器为开发者管理对象间的依赖关系提供很多基础服务,在Spring中有许多IOC容器的实现供用户选择。
BeanFactory作为最顶层的接口类,有三个重要的接口子类:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。最终的默认实现类是DefaultListableBeanFactory它实现了所有接口。
那为什么要定义这么多接口呢?这个就是因为源码中最核心的思想就是单一职责原则、接口隔离原则、开闭原则等。这三个接口都有它使用的场合,主要是为了区分Spring内部在操作的过程中对象的转化和传递过程,对对象的访问所做的限制。
这三个接口共同定义了Bean的集合、Bean与Bean的关系以及Bean的行为。
BeanFactory源码如下:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
//需要得到工厂本身,需要转义
String FACTORY_BEAN_PREFIX = "&";
//根据Bean的名字获取IOC容器中的bean实例
Object getBean(String var1) throws BeansException;
//根据Bean的名字和class类型获取IOC容器中的bean实例
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
//根据Bean的名字 检索IOC容器是否有这个bean
boolean containsBean(String var1);
//根据Bean的名字 判断bean是否是单例
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
//根据Bean的名字 判断bean是否是原型
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
//根据Bean的名字,获取Class类型
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;
String[] getAliases(String var1);
}
BeanFactory 定义了IOC容器的基本行为,不关心bean是如何定义及加载的,就像我们只关心工厂中的产品,至于工厂是怎么生产的我们不关心。
而要知道工厂如何产生对象,就需要看具体的IOC容器实现,Spring提供了很多IOC容器的实现,比如ClassPathXmlApplicationContext、GenericApplicationContext等。
ApplicationContext是Spring提供的一个高级IOC容器,除了IOC基本功能外,还提供了附加服务:
2、BeanDefinition
Bean对象在Spring容器中是以BeanDefinition来描述的
3、BeanDefinitionReader
bean的解析很复杂,功能分的很细,因为需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。bean的解析主要就是对配置文件的解析,这个解析配置文件的过程就是通过BeanDefinitionReader完成的。
四 、基于xml的IOC容器初始化
IOC容器的初始化包括BeanDefinition的Resource定位、加载、注册IOC 这三个过程。这里以最常见的ApplicationContext容器为例,因为web项目使用的 XmlWebApplicationContext、ClasspathXmlApplicationContext都是属于这个继承体系。
ApplicationContext 允许上下文嵌套即父子容器,对于Bean的查找首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的Spring应用提供一个共享的Bean的定义环境。
1、入口
我们常见的ClassPathXmlApplicationContext,实际调用的构造函数为:
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
还有像AnnotationConfigApplicationContext、FileSystemXmlApplicationContext、XmlWebApplicationContext 等都是继承父容器AbstractApplicationContext,主要就是装饰器模式和策略模式,最终调用的都是refresh()方法。
2、获取配置路径
上面的源码发现,
super(parent);
调用父类的构造函数为容器设置好Bean的资源加载器。this.setConfigLocations(configLocations);
设置Bean的配置信息定位路径。第一步,通过追踪ClassPathXmlApplicationContext继承体系其父容器AbstractApplicationContext中初始化IOC容器主要源码如下:
public AbstractApplicationContext() {
..........
this.resourcePatternResolver = this.getResourcePatternResolver();
}
public AbstractApplicationContext(ApplicationContext parent) {
this();
this.setParent(parent);
}
//获取一个Spring Source的加载器用于读取Spring Bean的配置信息(配置文件)
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
AbstractApplicationContext构造函数调用PathMatchingResourcePatternResolver的构造函数,创建Spring 的资源加载器
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
第二步, 就是执行 this.setConfigLocations(configLocations);
设置Bean的配置信息定位路径。方法通过调用父类AbstractRefreshableConfigApplicationContext的方法对Bean的配置信息进行定位,源码如下:
public void setConfigLocation(String location) {
this.setConfigLocations(StringUtils.tokenizeToStringArray(location, ",; \t\n"));
}
public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for(int i = 0; i < locations.length; ++i) {
this.configLocations[i] = this.resolvePath(locations[i]).trim();
}
} else {
this.configLocations = null;
}
}
可以看出可使用一个字符串来配置多个SpringBean的信息,也可以使用字符串数组,到此Spring IOC容器在初始化时,将Bean的配置信息定位为Spring封装的Resource。
3、开始启动
Spring IOC 容器对Bean的配置资源的加载是从refresh()
方法开始的,refresh是一个模板方法,规定了IOC容器的的启动流程,有些特殊逻辑交给子类实现,通过调用父类AbstractApplicationContext的refresh方法启动整个IOC容器对Bean的加载过程。refresh源码如下:
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
//调用容器准备的刷新方法,获取容器当前时间,同时给容器设置同步标识
this.prepareRefresh();
//告诉子类启动refreshBeanFactory(),beand 定义资源的加载从子类refreshBeanFactory方法启动
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
//为BeanFactory配置容器特性,如何类加载器、事件处理器
this.prepareBeanFactory(beanFactory);
try {
//为容器的某些子类指定特殊的BeanPost事件处理器
this.postProcessBeanFactory(beanFactory);
//调用所有注册的BeanFactoryPostProcessor的bean
this.invokeBeanFactoryPostProcessors(beanFactory);
//BeanFactoryPostProcessor是bean的后置处理器,用于监听容器触发的事件
this.registerBeanPostProcessors(beanFactory);
//初始化信息源、国际化
this.initMessageSource();
//初始化容器事件传播器
this.initApplicationEventMulticaster();
//调用子类的特殊bean的初始化方法
this.onRefresh();
//为事件传播器注册事件监听器
this.registerListeners();
//初始化所有剩余的单例bean
this.finishBeanFactoryInitialization(beanFactory);
//初始化容器的生命周期事件处理器,并发布容器的生命周期事件
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
//销毁已创建bean
this.destroyBeans();
//取消refresh操作,重置容器同步标识
this.cancelRefresh(var9);
throw var9;
} finally {
//重设公共缓存
this.resetCommonCaches();
}
}
}
refresh方法主要为IOC容器bean的生命周期管理提供条件,SpringIOC容器加载bean的配置信息从其子类容器的refreshBeanFactory方法启动,所以整个ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();后面的代码都是注册容器的信息源和生命周期事件。
refresh方法主要作用就是在创建IOC容器前,如果容器已经存在,则需要把已有的容器销毁和关闭,以保证在refresh方法之后使用新创建的IOC容器。
4、创建容器
obtainFreshBeanFactory方法调用子类容器的refreshBeanFactory方法,在容器启动加载bean的配置信息,源码如下:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//这里使用委派模式、父类定义了抽象的refreshBeanFactory方法,具体由子类实现
this.refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
AbstractApplicationContext类只是定义了抽象的refreshBeanFactory方法,容器真正调用的是子类实现的refreshBeanFactory方法,方法源码如下:
protected final void refreshBeanFactory() throws BeansException {
//如果容器已有,则销毁容器中的bean和关闭容器
if (this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
//创建IOC容器
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
//对IOC容器进行定制化,如设置启动参数,开启注解启动自动装配
this.customizeBeanFactory(beanFactory);
//调用加载bean定义的方法,这里是策略模式,当前类只是抽象了loadBeanDefinitions方法,具体的调用子类实现
this.loadBeanDefinitions(beanFactory);
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
在这个方法中,先判断BeanFactory是否存在,如果存在则先销毁和关闭BeanFactory,接着创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)加载bean定义。
5、加载配置路径
上面讲了父类只是抽象了loadBeanDefinitions方法,具体的调用子类AbstractXmlApplicationContext的实现,源码如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException {
//创建XmlBeanDefinitionReader,即bean读取器,并通过回调设置到容器去
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
//被bean读取器设置SAX xml解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//读取bean定义的xml资源文件时候,启动xml 的校验机制
this.initBeanDefinitionReader(beanDefinitionReader);
//bean读取器真正实现加载的方法
this.loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取bean配置文件定位
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//如果子类中获取的资源定位为空,则获取classpathxmlApplicationContext
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
以xmlBean 读取器的其中一种策略 XmlBeanDefinitionReader 为例。程序执行的具体分支是reader.loadBeanDefinitions(configLocations);
6、分配路径处理策略
在XmlBeanDefinitionReader 父类中定义了加载过程reader.loadBeanDefinitions(configLocations);
源码如下:
public int loadBeanDefinitions(String location, Set<Resource> actualResources)
throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = this.getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
int loadCount;
if (!(resourceLoader instanceof ResourcePatternResolver)) {
Resource resource = resourceLoader.getResource(location);
loadCount = this.loadBeanDefinitions((Resource)resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
} else {
try {
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
loadCount = this.loadBeanDefinitions(resources);
if (actualResources != null) {
Resource[] var6 = resources;
int var7 = resources.length;
for(int var8 = 0; var8 < var7; ++var8) {
Resource resource = var6[var8];
actualResources.add(resource);
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
} catch (IOException var10) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10);
}
}
}
}
可以看出loadBeanDefinitions方法做了两件事:
第一,调用资源加载器获取资源方法resourceLoader.getResource(location);
获取要加载的资源。
第二,真正执行加载的是ResourcePatternResolver的getResource() 方法。
7、解析配置文件
XmlBeanDefinitionReader 通过调用ClassPathXmlApplicationContext的父类DefaultResourceLoader 的getResource() 方法获取要加载的资源:
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
Iterator var2 = this.protocolResolvers.iterator();
Resource resource;
do {
if (!var2.hasNext()) {
if (location.startsWith("/")) {
return this.getResourceByPath(location);
}
if (location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
}
try {
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException var5) {
return this.getResourceByPath(location);
}
}
ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
resource = protocolResolver.resolve(location, this);
} while(resource == null);
return resource;
}
8、 开始读取配置文件
XmlBeanDefinitionReader 的loadBeanDefinitions()方法,看到bean配置文件的加载过程
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (this.logger.isInfoEnabled()) {
this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!((Set)currentResources).add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var5;
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException var15) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
} finally {
((Set)currentResources).remove(encodedResource);
if (((Set)currentResources).isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
return var5;
}
}
该过程由documentLoader()方法实现。
9、创建Document对象,解析
第一,通过xml 解析器将bean的配置信息转换成Document对象;
第二,按照bean的定义对Document进行解析,BeanDefinitionDocumentReader中的实现类DefaultBeanDefinitionDocumentReader中实现。
10、将配置加载到内存
BeanDefinitionDocumentReader对Document解析,我们使用Spring时,在配置文件中可以使用 11、加载Bean元素、property子元素 通过对配置文件中Bean配置信息转换成Document,然后层层解析成IOC容器所识别的BeanDefinition对象。至此IOC容器 完成了管理Bean对象的准备工作,即初始化过程,但是最重要的DI 依赖注入还没有发生,现在IOC容器只是存储一些静态信息,接下来需要向容器中注册Bean定义的信息才能全部完成IOC容器初始化过程。 12、分配注册策略 跟踪源码发现是调用BeanDefinitionReaderUtils的registerBeanDefinition()方法向IOC容器注册。当调用此方法时,真正完成注册的是DefaultListableBeanFactory。 13、向IOC容器注册 至此已经注册到了IOC容器中,被容器中的beanDefinitionMap管理起来了,真正的完成了IOC容器初始化工作。这些BeanDefinition信息已经可使用了,并且可以被检索。正是有了这些BeanDefinition注册信息,容器才可以依赖注入。 public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;
for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
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 var9) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", var9);
}
}
BeanDefinition oldBeanDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!this.isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + "': There is already [" + oldBeanDefinition + "] bound.");
}
if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
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 (this.hasBeanCreationStarted()) {
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 {
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (oldBeanDefinition != null || this.containsSingleton(beanName)) {
this.resetBeanDefinition(beanName);
}
}