目录
Pt1 IoC 和 DI
Pt2 IoC容器核心类
Pt2.1 ApplicationContext
Pt2.2 BeanFactory
Pt2.3 Environment
(1) Profile
(2) Properties
Pt2.4 ResourceLoader
Pt2.5 BeanDefinition
Pt2.6 BeanDefinitionReader
Pt2.7 BeanDefinitionHolder
Pt2.8 Resource
Pt2.9 BeanDefinitionParserDelegate
Pt3 IoC容器初始化
Pt3.1 寻找入口
(1) DispatcherServlet#init()
(2) ApplicationContext入口
Pt3.2 获得配置路径
Pt3.3 开始启动容器
Pt3.4 创建容器
Pt3.5 载入配置路径
Pt3.6 分配路径处理策略
Pt3.7 解析配置文件路径
Pt3.8 开始读取配置资源
Pt3.9 准备文档对象
Pt3.10 分配解析策略
Pt3.11 将配置载入内存
Pt 3.12 解析元素
Pt3.13 解析元素
Pt3.14 分配注册策略
Pt3.15 向容器注册
Pt3.16 整体流程图
参考资料
IoC(Inversion of Control,控制反转)就是把原来代码里需要实现的对象创建、依赖,反转给容器来帮助管理。我们需要创建一个容器(FactoryBean),同时需要一种描述来让容器知道要创建的对象与对象的关系,这个描述最具体的表现就是Spring配置文件。
DI(Dependency Injection,依赖注入)就是指对象被动接受依赖类而不需要自己主动去找。换句话说,就是指对象不是从容器中查找他依赖的类,而是在容器实例化对象时主动将它依赖的类注入给他。
org.springframework.context.ApplicationContext接口代表Spring IoC容器,主要负责bean的实例化、配置、装配,简而言之,Spring IoC容器是管理这些bean的。容器如何知道哪些对象要进行实例化、配置和装配的呢?是通过读取配置文件元数据来达到这个效果的,配置文件元数据是用xml配置、Java注解和Java代码配置来表示的。程序只需要向Spring容器提供配置元数据,Spring容器就能在我们的应用中实例化、配置和装配这些对象。org.springframework.beans和org.springframework.context包是Spring IoC容器的基础。Spring提供了很多Application
接口的实现。
ApplicationContext是Spring的IoC容器,他定义了容器的基本行为(BeanFactory能力),执行Bean定义的解析和加载。除此之外,他还提供了以下附加功能:
支持信息源,可以实现国际化(实现MessageSource接口);
访问资源(实现ResourcePatternResolver接口);
支持应用事件(实现ApplicationEventPublisher接口);
ApplicationContext的类图如下:
从上图就能很清楚的看出ApplicationContext
继承的接口分为五类:
BeanFactory:提供了能够管理任何对象的高级配置机制,约定了IoC容器的行为,这个接口是Spring框架中比较重要的一个接口。
ListableBeanFactory:从该接口的名字就能知道,该接口除了拥有BeanFactory的功能外,该接口还有能列出factory中所有bean的实例的能力。
HierarchicalBeanFactory:该接口除了拥有BeanFactory的功能外,还提供了BeanFactory分层的机制,查找bean的时候,除了在自身BeanFactory查找外,如果没有查找到,还会在父级BeanFactory进行查找。
MessageSource:消息资源的处理,用于国际化。
ApplicationEventPublisher:用于处理事件发布机制。
EnvironmentCapable:提供了Environment的访问能力。
ResourceLoader:用于加载资源的策略接口(例如类路径下的资源、系统文件下的资源等等)。
ResourcePatternResolver:用于将位置模式(例如Ant风格的路径模式)解析成资源对象的策略接口。classpath*:前缀能匹配所以类路径下的资源。
ApplicationContext源码解析:
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
// 获取ApplicationContext的唯一ID
@Nullable
String getId();
// 该上下文所属的已经部署了的应用名称,默认为""
String getApplicationName();
// 展示该上下文相对友好的名称
String getDisplayName();
// 该上下文第一次加载时间
long getStartupDate();
// 获得父级ApplicationContext
@Nullable
ApplicationContext getParent();
// 获取BeanFactory的能力
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
BeanFactory(Bean工厂)是SpringIoC容器的基础,是典型的工厂模式。他为开发者管理对象之间的依赖关系提供了很多便利和基础功能,IoC则将处理事情的责任从应用程序代码转移到框架中完成。
Spring中BeanFactory类图如下所示。
BeanFactory作为顶层接口类,定义了IoC容器的基本规范,他有3个重要子类:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是从上面的类图上可以看到,最下层的实现类只有DefaultListableBeanFactory,它实现了所有接口的能力。
既然只有一个实现类,那为什么在顶层接口和底层实现之间又衍生出多个接口类呢?从下面代码分析中我们可以看出,每个接口都定义了特定的使用场景,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。他们共同定义了Bean的集合、Bean之间的关系和Bean的行为。
ListableBeanFactory表示Bean支持列表化;
HierarchicalBeanFactory表示Bean有继承关系;
AutowireCapableBeanFactory定义Bean的自动装配规则。
BeanFactory源码定义如下:
public interface BeanFactory {
/**
* 对FactoryBean的转义定义。
* 因为如果使用Bean的名字检索FactoryBean得到的对象是工厂生成的对象,如果需要得到工厂本身,需要转义。
*/
String FACTORY_BEAN_PREFIX = "&";
/**
* 根据Bean的名字,获取在IoC容器中得到的Bean的实例。
*/
Object getBean(String name) throws BeansException;
/**
* 根据Bean的名字和Class类型,获取在IoC容器中得到的Bean的实例,增加了类型安全验证机制。
*/
T getBean(String name, Class requiredType) throws BeansException;
/**
* 根据Bean的名字和参数,获取在IoC容器中得到的Bean的实例。
*/
Object getBean(String name, Object... args) throws BeansException;
/**
* 根据Class类型获取IoC容器中的实例。
*/
T getBean(Class requiredType) throws BeansException;
/**
* 根据Class类型和参数,获取在IoC容器中得到的Bean的实例。
*/
T getBean(Class requiredType, Object... args) throws BeansException;
/**
* 获取Bean的提供者。
*/
ObjectProvider getBeanProvider(Class requiredType);
ObjectProvider getBeanProvider(ResolvableType requiredType);
/**
* 提供对Bean的检索,看看在IoC容器中是否包含这个名称的Bean。
*/
boolean containsBean(String name);
/**
* 根据Bean的名称得到对应的实例,同时判断Bean是不是Singleton。
*/
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
/**
* 根据Bean的名称得到对应的实例,同时判断Bean是不是Prototype。
*/
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
/**
* 判断对应名称的Bean是否匹配给定的类型。
*/
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
/**
* 判断对应名称的Bean是否匹配给定的类型。
*/
boolean isTypeMatch(String name, Class> typeToMatch) throws NoSuchBeanDefinitionException;
/**
* 得到Bean实例的Class类型。
*/
@Nullable
Class> getType(String name) throws NoSuchBeanDefinitionException;
/**
* 得到Bean实例的Class类型。
*/
@Nullable
Class> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
/**
* 得到Bean的别名,如果根据别名进行搜索,那么其原名也会被检索出来。
*/
String[] getAliases(String name);
}
BeanFactory对IoC容器的基本行为做了定义,比如从容器中获取、判断Bean是否存在等,BeanFactory不关心Bean是如何定义和加载的,他只是直接把容器中已经加载好的Bean拿来使用。
Environment用来表示整个应用运行时的环境,为了更形象地理解Environment,你可以把Spring应用的运行时简单地想象成两个部分:一个是Spring应用本身,一个是Spring应用所处的环境,而Environment这个接口,就是对这个所处的环境的概念性建模。
Environment在容器中是一个抽象的集合,是指应用环境的2个方面:profiles和properties。
profile配置是一个被命名的、bean定义的逻辑组,这些bean只有在给定的profile配置激活时才会注册到容器。profile的使用是为了能以最简单的方式切换配置文件,通常是简化多个环境不同配置的复杂。
比如项目中经常遇到的开发、测试、生产等环境的切换。
启动是指定运行环境:
JAVA_OPTS="-Dspring.profiles.active=test"
Environment环境对象的作用,对于profiles配置来说,它能决定当前激活的是哪个profile配置,和哪个profile是默认。
一个profile就是一组Bean定义的逻辑分组。
这个分组,也就这个profile,被赋予一个命名,就是这个profile名字。
只有当一个profile处于active状态时,它对应的逻辑上组织在一起的这些Bean定义才会被注册到容器中。
Bean添加到profile可以通过XML定义方式或才annotation注解方式。
Environment对于profile所扮演的角色是用来指定哪些profile是当前活跃的缺省。
properties属性可能来源于properties文件、JVM properties、system环境变量、JNDI、servlet context parameters上下文参数、专门的properties对象,Maps等等。Environment对象的作用,对于properties来说,是提供给用户方便的服务接口、方便撰写配置、方便解析配置。
配置属性源。
从属性源中获取属性。
容器(ApplicationContext)所管理的bean如果想直接使用Environment对象访问profile状态或者获取属性,可以有两种方式
(1)实现EnvironmentAware接口。
(2)@Inject或者@Autowired一个Environment对象。
绝大数情况下,bean都不需要直接访问Environment对象,而是通过类似@Value注解的方式把属性值注入进来。
EnvironmentCapable源码如下:
// 应用运行时环境变量
public interface Environment extends PropertyResolver {
// 返回当前环境中激活状态的profiles配置的属性集合
String[] getActiveProfiles();
// 返回当前环境中默认的profiles配置的属性集合,如果当前环境没有显示的指明要激活的Profiles环境,
// 通过getDefaultProfiles()可以获取默认的Profiles属性定义。
String[] getDefaultProfiles();
// 当前环境激活的Profiles或者默认的Profiles是否包含指定的Profiles。
@Deprecated
boolean acceptsProfiles(String... profiles);
// 当前环境激活的Profiles和指定Profiles是否匹配
boolean acceptsProfiles(Profiles profiles);
}
除此之外和Environment相关的有两个接口:EnvironmentAware和EnvironmentCapable。
EnvironmentAware接口提供了访问配置信息的能力,源码如下:
/**
* 凡注册到Spring容器内的bean,实现了EnvironmentAware接口重写setEnvironment方法后,在工程启动时可以获得application.properties的配置文件配置的属性值。
*/
public interface EnvironmentAware extends Aware {
// 获取和设置配置属性
void setEnvironment(Environment environment);
}
Eg.
@Configuration
public class MyProjectc implements EnvironmentAware {
@Override
public void setEnvironment(Environment environment) {
String projectName = environment.getProperty("project.name");
}
}
EnvironmentCapable提供了访问Environment的能力,源码如下:
/**
* 实现了此接口的类有应该有一个Environment类型的域,并且可以通过getEnvironment方法取得。
*/
public interface EnvironmentCapable {
// 获取Environment对象
Environment getEnvironment();
}
ResourceLoader该接口是用来加载资源(例如类路径或者文件系统中的资源)的策略接口。
/**
* 接口是用来加载资源(例如类路径或者文件系统中的资源)的策略接口。
* 该接口只有简单明了的两个方法,一个是用来获取指定位置的资源,一个用于获取资源加载器所使用的类加载器。
* Resource是从实际类型的底层资源(例如文件、类路径资源)进行抽象的资源描述符。
*/
public interface ResourceLoader {
/** Pseudo URL prefix for loading from the class path: "classpath:". */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
// 根据指定的位置获取资源
Resource getResource(String location);
// 获取该资源加载器所使用的类加载器
@Nullable
ClassLoader getClassLoader();
}
其中,子接口ResourcePatternResolver用于将一个位置模式的字符串解析为资源。
/**
* 接口用于解析一个位置模式(例如Ant风格的路径模式)。
*/
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
// 将给定的位置模式解析成资源对象
Resource[] getResources(String locationPattern) throws IOException;
}
我们再看看Resource源码的定义:
/**
* Resource接口是对资源描述的定义
*/
public interface Resource extends InputStreamSource {
// 资源实际上是否存在
boolean exists();
// 资源是否可读
default boolean isReadable() {
return exists();
}
// 检查资源是否为打开的流
default boolean isOpen() {
return false;
}
// 资源是否为文件系统上的一个文件
default boolean isFile() {
return false;
}
// 获取url
URL getURL() throws IOException;
// 获取URI
URI getURI() throws IOException;
// 获取文件
File getFile() throws IOException;
// 获取ReadableByteChannel用于读取资源
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
// 资源的内容的长度
long contentLength() throws IOException;
// 资源的最后修改时间
long lastModified() throws IOException;
// 相对于使用当前的资源创建一个新的资源
Resource createRelative(String relativePath) throws IOException;
// 获取资源的文件名
@Nullable
String getFilename();
// 获取资源的描述信息
String getDescription();
}
PathMatchingResourcePatternResolver
我们在介绍ResourceLoader的一个实现类——PathMatchingResourcePatternResolver,在ApplicationContext的初始化过程中会使用他来完成资源文件的解析。
/**
* 一个{@link ResourcePatternResolver}实现,它能够将指定的资源位置路径解析为一个或多个匹配的资源。
*/
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
// 将传入的资源文件路径解析为资源。
// 传入locationPattern,是资源文件路径,比如{classpath*:applicationContext.xml}
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 处理[classpath*:]开头的文件
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// 资源路径中存在* { }这3中模糊匹配的情况下
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// 通过模式匹配获取资源路径的所有资源
return findPathMatchingResources(locationPattern);
}
else {
// 根据全路径匹配获取资源
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Generally only look for a pattern after a prefix here,
// and on Tomcat only after the "*/" separator for its "war:" protocol.
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
.....
}
其核心就是getResources(),包含了对各种资源文件路径方式的解析逻辑,里面具体的逻辑就不研究了。
Spring IoC容器用来管理各种Bean对象及其互相关系,BeanDefinition就是用来定义Bean对象及其关系的。
BeanDefinition主要定义了以下信息:
BeanDefinition 包含了我们对 bean 做的配置,比如 XML
Spring 将我们对 bean 的定义信息进行了抽象,抽象后的实体就是 BeanDefinition,并且 Spring 会以此作为标准对 bean 进行创建。
BeanDefinition 包含以下元数据:
一个全限定类名,通常来说,就是对应 bean 的全限定类名。
bean 的行为配置元素,这些元素展示了这个 bean 在容器中是如何工作的,包括 scope,lifecycle callbacks(生命周期回调)等等。
这个 bean 的依赖信息。
一些其他配置信息,比如我们配置了一个连接池的对象,那么我们还会配置它池子的大小,最大连接数等等。
我们看源码中BeanDefinition是如何定义Bean对象的。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
// 单例、原型标识符
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
// 标识 Bean 的类别,分别对应 用户定义的 Bean、来源于配置文件的 Bean、Spring 内部的 Bean
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
// 设置、返回 Bean 的父类名称
void setParentName(@Nullable String parentName);
@Nullable
String getParentName();
// 设置、返回 Bean 的 className
void setBeanClassName(@Nullable String beanClassName);
@Nullable
String getBeanClassName();
// 设置、返回 Bean 的作用域
void setScope(@Nullable String scope);
@Nullable
String getScope();
// 设置、返回 Bean 是否懒加载
void setLazyInit(boolean lazyInit);
boolean isLazyInit();
// 设置、返回当前 Bean 所依赖的其它 Bean 名称。
void setDependsOn(@Nullable String... dependsOn);
@Nullable
String[] getDependsOn();
// 设置、返回 Bean 是否可以自动注入。只对 @Autowired 注解有效
void setAutowireCandidate(boolean autowireCandidate);
boolean isAutowireCandidate();
// 设置、返回当前 Bean 是否为主要候选 Bean 。
// 当同一个接口有多个实现类时,通过该属性来配置某个 Bean 为主候选 Bean。
void setPrimary(boolean primary);
boolean isPrimary();
// 定义创建该Bean对象的工厂类
void setFactoryBeanName(@Nullable String factoryBeanName);
@Nullable
String getFactoryBeanName();
// 创建该Bean对象的工厂方法
void setFactoryMethodName(@Nullable String factoryMethodName);
@Nullable
String getFactoryMethodName();
// 返回此bean的构造函数参数值。
ConstructorArgumentValues getConstructorArgumentValues();
default boolean hasConstructorArgumentValues() {
return !getConstructorArgumentValues().isEmpty();
}
// 获取普通属性集合
MutablePropertyValues getPropertyValues();
default boolean hasPropertyValues() {
return !getPropertyValues().isEmpty();
}
// 定义初始化Bean的方法名称
void setInitMethodName(@Nullable String initMethodName);
@Nullable
String getInitMethodName();
// 定义销毁Bean的方法名称
void setDestroyMethodName(@Nullable String destroyMethodName);
@Nullable
String getDestroyMethodName();
// 设置和获取这个bean的应用
void setRole(int role);
int getRole();
// 设置和获取对bean定义的可读描述。
void setDescription(@Nullable String description);
@Nullable
String getDescription();
// Read-only attributes
// Bean类型
ResolvableType getResolvableType();
// 是否为单例、原型和抽象类
boolean isSingleton();
boolean isPrototype();
boolean isAbstract();
// 返回该bean定义来自的资源的描述(用于在出现错误时显示上下文)
@Nullable
String getResourceDescription();
@Nullable
BeanDefinition getOriginatingBeanDefinition();
}
BeanDefinitionReader 的作用是读取 Spring 配置文件中的内容,将其转换为 IoC 容器内部的数据结构:BeanDefinition。
为了保证足够的扩展性和灵活性,Spring对Bean的解析过程设计的非常复杂,我们看看BeanDefinitionReader接口的定义,具体实现类我们在ApplicationContext实例化过程中在介绍。
public interface BeanDefinitionReader {
// 返回Bean工厂以向其注册Bean定义。
BeanDefinitionRegistry getRegistry();
// 返回资源加载器以用于资源位置。
// 可以检查ResourcePatternResolver接口并进行相应的转换,以针对给定的资源模式加载多个资源。
// 一个null返回值表明,绝对资源加载不适用于这个bean定义阅读器。这主要用于从bean定义资源中导入其他资源,
// 例如,通过XML bean定义中的“ import”标记。但是,建议相对于定义资源应用此类导入;
// 只有明确的完整资源位置才会触发绝对资源加载。
@Nullable
ResourceLoader getResourceLoader();
// 返回用于Bean类的类加载器。
@Nullable
ClassLoader getBeanClassLoader();
// 返回BeanNameGenerator用于匿名Bean(未指定显式Bean名称)。
BeanNameGenerator getBeanNameGenerator();
//从指定的资源加载bean定义。
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
//从指定的资源位置加载bean定义。
//该位置也可以是位置模式,前提是此bean定义读取器的ResourceLoader是ResourcePatternResolver。
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
简单的说,BeanDefinitionHolder就是对一个BeanDefinition的持有,只是除了BeanDefinition,他包装了更多的元素。将BeanDefinition封装起来,使用起来更加方便的同时,也支持Spring应对更多的变化。
public class BeanDefinitionHolder implements BeanMetadataElement {
//持有BeanDefinition
private final BeanDefinition beanDefinition;
//BeanDefinition的名称
private final String beanName;
//BeanDefinition的别名
@Nullable
private final String[] aliases;
// 根据bean的名称和beanDefinition初始化BeanDefinitionHolder
public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName) {
this(beanDefinition, beanName, null);
}
// 根据bean的名称和beanDefinition,别名aliases初始化BeanDefinitionHolder
public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName, @Nullable String[] aliases) {
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
Assert.notNull(beanName, "Bean name must not be null");
this.beanDefinition = beanDefinition;
this.beanName = beanName;
this.aliases = aliases;
}
// 根据指定的BeanDefinitionHolder 复制一个新的BeanDefinitionHolder
// 浅克隆
public BeanDefinitionHolder(BeanDefinitionHolder beanDefinitionHolder) {
Assert.notNull(beanDefinitionHolder, "BeanDefinitionHolder must not be null");
this.beanDefinition = beanDefinitionHolder.getBeanDefinition();
this.beanName = beanDefinitionHolder.getBeanName();
this.aliases = beanDefinitionHolder.getAliases();
}
// 获取BeanDefinition
public BeanDefinition getBeanDefinition() {
return this.beanDefinition;
}
// 获取bean的名称
public String getBeanName() {
return this.beanName;
}
// 获取别名
@Nullable
public String[] getAliases() {
return this.aliases;
}
// 获取beanDefinition的源对象,实现了BeanMetadataElement
@Override
@Nullable
public Object getSource() {
return this.beanDefinition.getSource();
}
// 判断指定的名称与beanName或者别名是否匹配
public boolean matchesName(@Nullable String candidateName) {
return (candidateName != null && (candidateName.equals(this.beanName) ||
candidateName.equals(BeanFactoryUtils.transformedBeanName(this.beanName)) ||
ObjectUtils.containsElement(this.aliases, candidateName)));
}
//返回一个描述包括bean的名称和所有的别名
public String getShortDescription() {
if (this.aliases == null) {
return "Bean definition with name '" + this.beanName + "'";
}
return "Bean definition with name '" + this.beanName + "' and aliases [" + StringUtils.arrayToCommaDelimitedString(this.aliases) + ']';
}
//返回一个长描述包括名称,别名已经beanDefinition的内容
public String getLongDescription() {
return getShortDescription() + ": " + this.beanDefinition;
}
@Override
public String toString() {
return getLongDescription();
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof BeanDefinitionHolder)) {
return false;
}
BeanDefinitionHolder otherHolder = (BeanDefinitionHolder) other;
return this.beanDefinition.equals(otherHolder.beanDefinition) &&
this.beanName.equals(otherHolder.beanName) &&
ObjectUtils.nullSafeEquals(this.aliases, otherHolder.aliases);
}
@Override
public int hashCode() {
int hashCode = this.beanDefinition.hashCode();
hashCode = 29 * hashCode + this.beanName.hashCode();
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.aliases);
return hashCode;
}
}
在ResourceLoader部分有介绍。
在将XML配置转为Document文档后,Spring是委托给BeanDefinitionParserDelegate来完成文档的解析的。
BeanDefinitionParserDelegate中方法比较多,都是针对xml配置中各种标签进行解析操作。
源码比较多,这里就不贴了。
了解了Spring IoC容器的核心类之后,我们正式来研究Spring IoC源码初始化流程。
Spring IoC容器初始化,有两个不同的入口,我们分别介绍一下。
DispatcherServlet:在使用tomcat等Web容器启动时触发,继而会在init()中触发IoC初始化,配置在web.xml中;
ApplicationContext:在使用main()启动的Web容器启动时触发,在具体的ApplicationContext实现类中完成IoC初始化;
在基于Web容器启动应用时,我们常见的web.xml配置如下:
springMVC
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:application.properties
1
true
springMVC
/*
可以看到,在Web项目启动时,会执行DispatcherServlet逻辑,Spring IoC的初始化就是从DispatcherServlet.init()开始的。
但是,在DispatcherServlet中没有找到init()方法,最终我们在父类HttpServletBean中找到了init方法。
// org.springframework.web.servlet.HttpServletBean#init
@Override
public final void init() throws ServletException {
// 从Web.xml配置中获取初始化参数中的bean properties配置。
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;
}
}
// 真正完成容器初始化动作是在initServletBean()中,由子类根据自身逻辑完成初始化(重要)。
initServletBean();
}
继续来看initServletBean的逻辑:
// org.springframework.web.servlet.FrameworkServlet#initServletBean
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
// 真正完成IoC容器初始化逻辑实在ApplicationContext初始化过程中(重要)。
// initWebApplicationContext()看起来和我们手写Spring实现时,new ApplicationContext()的逻辑很像。
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
从名字来看,initWebApplicationContext就比较符合我们的思维了,我们继续看看是如何初始化ApplicationContext的。
// org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
// 1、从ServletContext中获得父容器WebApplicationContext。
// Web的本质是Servlet实现,ApplicationContext是为了兼容Servlet逻辑,而又能够完成丰富的Web应用程序而诞生的。
// 所以从ServletContext中获得Web中上下文信息。
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// 子容器
WebApplicationContext wac = null;
// 2、初始化时传入webApplicationContext实例对象
// A context instance was injected at construction time -> use it
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
// 触发IoC容器初始化。
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 3、从ServletContext中查找Web容器是否存在,并创建默认的空IoC容器。
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
// 4、创建WebApplicationContext容器,初始化IoC容器(重要)。
// 基于ServletContext中创建WebApplicationContext,并启动IoC容器的加载。
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
// 5、触发onRefresh(),初始化Spring MVC的九大组件。
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
// 6、将WebApplicationContext对象放入ServletContext中。
// 对应上面步骤[3.]的读取操作,只有WebApplicationContext已经完成加载,才会放入ServletContext中。
// 所以如果ServletContext可以获取到wac,是可以直接使用的(参照步骤[3.]),否则需要重新创建(参照步骤[4.]和[5.])。
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
// org.springframework.web.servlet.FrameworkServlet#findWebApplicationContext
@Nullable
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
执行创建ApplicationContext操作:
// org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.web.context.WebApplicationContext)
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
// org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 通过反射创建新的WebApplicationContext对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
// 环境变量设置
wac.setEnvironment(getEnvironment());
// 父容器
wac.setParent(parent);
// 配置文件路径
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// (重要)
configureAndRefreshWebApplicationContext(wac);
return wac;
}
configureAndRefreshWebApplicationContext是我们最终要找到的地方,他启动了Spring IoC的初始化操作:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
// Spring IoC容器启动入口(重要)。
wac.refresh();
}
wac.refresh()是执行Spring IoC初始化的入口,容器的完整初始化逻辑实在refresh()中完成。
这条路我们不再继续分析下去,下面这种方式最终也是走到refresh()的逻辑,我们在下面这条链路再分析refresh()的具体逻辑。
还有一种比较常用的启动xml配置的Web应用的方式,是通过main()方法启动ClassPathXMLApplicationContext。
// main()启动Web容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
先看ClassPathXmlApplicationContext的配置:
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
实际执行逻辑的构造器
/**
* Spring启动是需要做几件事情:
* 1. 读取Spring配置;
* 2. 扫描Bean定义;
* 3. 初始化IoC,完成实例化Bean;
* 4. DI,设置Bean的依赖关系;
* 5. 设置Handler Mapping,配置URL和Method对应关系;
*/
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 1、调用父类构造方法设置Bean资源加载器。
super(parent);
// 2、保存Spring配置文件的路径地址。
// 配置文件中包含Spring默认配置,Bean定义,类自动扫描路径等,用于后续IoC、DI等。
setConfigLocations(configLocations);
// 3、至此,Spring IoC容器在初始化时将配置的Bean信息定位为Spring封装的Resource。
// 4、启动IoC容器,载入Bean配置资源。
if (refresh) {
refresh();
}
}
除了ClassPathXmlApplicationContext之外,Spring还有AnnotationConfigApplicationContext、FileSystemXMLApplicationContext、XmlWebApplicationContext等,他们都是AbstractApplicationContext的实现类,最终都是执行refresh()来完成IoC容器初始化。
接下来我们沿着ClassPathXmlApplicationContext这条线,看具体加载的过程。
super(parent)
通过父类完成资源加载起的初始化,看下源码:
// org.springframework.context.support.AbstractXmlApplicationContext#AbstractXmlApplicationContext(org.springframework.context.ApplicationContext)
public AbstractXmlApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
// org.springframework.context.support.AbstractRefreshableConfigApplicationContext#AbstractRefreshableConfigApplicationContext(org.springframework.context.ApplicationContext)
public AbstractRefreshableConfigApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
// org.springframework.context.support.AbstractRefreshableApplicationContext#AbstractRefreshableApplicationContext(org.springframework.context.ApplicationContext)
public AbstractRefreshableApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
一路找上去,最后在父类AbstractApplicationContext找到实现逻辑:
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
// 1、初始化Bean资源加载器;
this();
// 2、设置父上下文,主要是获取父上下文的配置信息。
setParent(parent);
}
// this()的逻辑
public AbstractApplicationContext() {
// ResourcePatternResolver继承了ResourceLoader,用于解析Bean定义的配置资源。
// ResourceLoader接口是Spring为了统一读取诸如本地文件、classpath项目路径下的文件、url互联网上
// 的文件等不同类型渠道的资源,封装隐藏如打开流、关闭流、报错处理等大量重复模板代码,而专程设计提供
// 的接口类。
this.resourcePatternResolver = getResourcePatternResolver();
}
// 获取Spring Source的加载器用于读入Spring配置信息
protected ResourcePatternResolver getResourcePatternResolver() {
// this -> AbstractApplicationContext继承自DefaultResourceLoader,本身也是资源加载器
// Spring资源加载器的getResource()用于载入资源
return new PathMatchingResourcePatternResolver(this);
}
在ApplicationContext上下文中,缓存了用于将特定正则模式的资源位置路径解析为资源的解析器,ResourcePatternResolver。
在上面ApplicationContext的类图中,可以看到ApplicationContext本身是继承了ResourcePatternResolver和ResourceLoader的,所以ApplicationContext本身其实就是一个资源解析器。
PathMatchingResourcePatternResolver能够将指定的资源位置路径解析为一个或多个匹配的资源,这个我们在前面ResourceLoader介绍过,这里就不说明了。
// org.springframework.core.io.support.PathMatchingResourcePatternResolver#PathMatchingResourcePatternResolver(org.springframework.core.io.ResourceLoader)
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
ApplicationContext需要操作资源解析器,继承ResourcePatternResolver,获得了资源操作的能力(继承接口方法)。
虽然本身ApplicationContext也是ResourcePatternResolver的实现类,他实现了接口的方法。但是他是通过组合的方式,调用其他ResourcePatternResolver实现类来操作资源解析,而没有自己去实现一个资源解析的具体逻辑。
比如我们看他的继承方法的实现逻辑:
@Override
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
}
ApplicationContext这么设计的原因,我想应该是为了简化ApplicationContext中这块操作逻辑复杂度。
ResourceLoader是一种策略模式的设计,针对不同的资源类型有不同的实现类。比如PathMatchingResourcePatternResolver、ServletContextResourcePatternResolver等,针对不同的类型通过不同的策略类来实现逻辑,再通过组合的方式将能力集成到ApplicationContext中。这种通过继承+组合的方式,既得到了接口的能力,又简化自身代码实现逻辑,非常值得学习。
setConfigLocations(configLocations)
我们看看setConfigLocations的实现逻辑:
/**
* 解析Bean定义资源文件的路径,处理多个资源文件字符串数组String[]。
* 设置当前上下文涉及的配置文件路径,XML文件中配置了诸如Spring的Bean定义,自动扫描的basePackage等,关于Spring上下文环境信息.
*/
public void setConfigLocations(@Nullable 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++) {
// resolvePath:将字符串解析为路径
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
就是缓存构造器中传入的资源路径。
到这一步,已经完成了对Bean定义资源的定位处理。
Spring IoC容器对资源的载入是从refresh()开始的,前面也介绍过,DispatcherServlet的启动模式,最终也是通过refresh()来完成容器初始化,接下来重点研究refresh逻辑。
refresh()是一个模板方法,规定了IoC容器的启动流程,有些逻辑会交给子类来实现。
源码如下:
// org.springframework.context.support.AbstractApplicationContext#refresh
/**
* Spring IoC容器对Bean资源配置的载入就是从refresh()开始的。refresh()是一个模板方法,规定了IoC容器的启动流程,
*/
@Override
public void refresh() throws BeansException, IllegalStateException {
// Spring IoC加载时防止出现并发
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 1、调用容器准备刷新的方法,获取容器的当前时间,同时给容器设置同步标识。
prepareRefresh();
// 2、初始化BeanFactory,解析Spring XML配置文件,获取Bean定义(重要)。
// 告诉子类启动refreshBeanFactory(),开始执行Bean定义资源文件载入。
// 到这步实际上已经完成IoC容器的初始化。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3、为BeanFactory配置容器特性,例如类加载器、事件处理器等。
prepareBeanFactory(beanFactory);
try {
// 4、为容器的某些子类指定特殊的Post事件处理器。
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 5、调用所有注册的BeanFactoryPostProcessor的Bean。
invokeBeanFactoryPostProcessors(beanFactory);
// 6、为BeanFactory注册Post事件处理器。
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 7、初始化信息源,和国际化相关。
initMessageSource();
// 8、初始化容器事件传播器。
initApplicationEventMulticaster();
// 9、调用子类某些特殊Bean的初始化方法。
onRefresh();
// 10、为事件传播器注册监听事件监听器。
registerListeners();
// 11、初始化所有剩余的单例Bean
finishBeanFactoryInitialization(beanFactory);
// 12、初始化容器的生命周期事件处理器,并发布容器的生命周期事件。
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 13、销毁已创建的Bean
destroyBeans();
// 14、取消刷新操作,重置容器的同步标识
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// 15、重设公共缓存
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
refresh()的主要作用:在创建IoC容器前,如果已经有容器存在,需要把已有的容器销毁和关闭,以保证在refresh()之后使用的是新创建的IoC容器。它类似于对IoC容器的重启,在新创建的容器中对容器进行初始化,对Bean配置资源进行载入。
Spring IoC容器对Bean载入的核心处理逻辑是在obtainFreshBeanFactory()中完成的,所以我们重点看obtainFreshBeanFactory()的逻辑。
进入obtainFreshBeanFactory():
// org.springframework.context.support.AbstractApplicationContext#obtainFreshBeanFactory
// 调用子类容器的refreshBeanFactory()来启动容器载入Bean配置信息的过程。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 1、初始化BeanFactory,解析Spring配置文件,获取Bean定义信息。
// refreshBeanFactory()在AbstractApplicationContext中是抽象方法,未定义实现。可以看到有两个子类GenericApplicationContext和
// AbstractRefreshableApplicationContext实现了这个方法,但是因为我们是在ClassPathXMLApplicationContext这条继承关系上,所以应该是看
// AbstractRefreshableApplicationContext实现类的逻辑。
refreshBeanFactory();
// 2、返回BeanFactory,缓存了Spring IoC容器。
return getBeanFactory();
}
子类实现了refreshBeanFactory():
// org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory
protected final void refreshBeanFactory() throws BeansException {
// 1、如果BeanFactory容器已经存在,销毁容器中的Bean,关闭容器(重新加载)。
if (hasBeanFactory()) {
// 销毁Singleton Beans。
// 在会面如果IoC初始化发生异常,也需要销毁Singleton Beans。其实也比较容易理解,因为Singleton只会初始化一次,一旦容器中有
// Singleton实例,会重复被使用,所以需要显示的进行销毁。而Prototype则不存在这类问题。
destroyBeans();
// 清空当前BeanFactory
closeBeanFactory();
}
try {
// 2、创建新的BeanFactory。
// 从前面的描述中我们知道,BeanFactory虽然有3个接口分支,但是最终只有DefaultListableBeanFactory一个实现类。
// 他继承自BeanFactory,定义了Bean的集合、Bean之间的关系和Bean的行为。
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
// 3、对IoC容器进行定制化,比如设置启动参数、开启注解的自动装配等。
customizeBeanFactory(beanFactory);
// 4、从配置中加载Bean信息(重要)。
// 这里也是使用了委派模式,父类定义抽象行为,子类实现具体细节。
loadBeanDefinitions(beanFactory);
// 5、设置BeanFactory对象。
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
在创建IoC容器前,如果已经有容器存在,需要把已有的容器销毁和关闭,以保证在refresh()之后使用的是新创建的IoC容器。
然后创建DefaultListableBeanFactory实例,并进行配置。
// org.springframework.context.support.AbstractRefreshableApplicationContext#createBeanFactory
protected DefaultListableBeanFactory createBeanFactory() {
// 创建DefaultListableBeanFactory实例
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
// org.springframework.context.support.AbstractRefreshableApplicationContext#customizeBeanFactory
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 是否允许重写已存在的Bean定义
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 是否允许循环依赖
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
完成BeanFactory初始化,就要开始载入配置资源中的Bean定义,从loadBeanDefinitions(beanFactory)开始。
loadBeanDefinitions()是在子类中实现的:
// org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
// 通过XmlBeanDefinitionReader完成Bean定义信息的加载。
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 1、创建XmlBeanDefinitionReader用于读取配置中的Bean。
// 并通过回调设置到BeanFactory容器中,容器通过Reader读取Bean配置。
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 2、为Bean读取器设置Spring资源加载器。
// AbstractXmlApplicationContext继承了DefaultResourceLoader,因此容器本身也是一个资源加载起。
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
// 为Bean读取器设置SAX xml解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 3、初始化Bean资源加载器,主要是启用xml校验机制。
initBeanDefinitionReader(beanDefinitionReader);
// 4、根据Spring配置文件的路径,获取资源的Doc对象,解析Doc获取Bean定义(重要)。
loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}
前三个步骤主要是初始化XmlBeanDefinitionReader,用于读取Bean配置文件。
重点看loadBeanDefinitions()逻辑:
// 根据Spring配置资源路径,获取配置资源的内容,解析获取Bean的定义。
// XML Bean 读取器用于加载xml配置的Bean资源。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 1、获取配置了Spring Bean的资源信息。
Resource[] configResources = getConfigResources();
if (configResources != null) {
// 2、解析Bean的配置资源,获取Spring配置信息(包含Bean定义信息),缓存到WEB容器中(重要)。
// XML Bean读取器调用其父类AbstractBeanDefinitionReader读取定位的Bean配置资源。
reader.loadBeanDefinitions(configResources);
}
// 3、获取获取ClassPathXMLApplicationContext构造时传入的配置文件路径构造时传入的配置文件路径(重要)。
// 实际上,在XML的场景下,[2.]ConfigResources为空,走的是下面这个分支。
String[] configLocations = getConfigLocations();
if (configLocations != null) {
// 4、解析Bean的配置资源,获取Spring配置信息(包含Bean定义信息),缓存到WEB容器中。
// 具体也是通过父类中实现来完成解析,和[2.]一样。
reader.loadBeanDefinitions(configLocations);
}
}
我们是基于ClassPathXMLApplicationContext逻辑进行初始化,所以最终走到的是[4.],我们看Reader中的实现逻辑。
// org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String...)
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
for (String location : locations) {
count += loadBeanDefinitions(location);
}
return count;
}
继续看loadBeanDefinitions(location)
// org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String)
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
在第二步中,我们获取的ResourceLoader的实现类用来加载资源,这里我们就要用到了。
无论是单个资源加载,还是多个资源加载,最后都是通过重载方法来完成。
继续看loadBeanDefinitions(resource)源码:
// org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource...)
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
for (Resource resource : resources) {
// 通过子类XMLBeanDefinitionReader来加载资源。
count += loadBeanDefinitions(resource);
}
return count;
}
又是重载方法loadBeanDefinitions(resource),只不过这次的逻辑是派生到子类XmlBeanDefinitionReader来实现的。
先不看子类的具体实现,我们总结下,在AbstractBeanDefinitionReader的loadBeanDefinitions()方法中,主要做了两件事情:
调用资源加载器的获取资源方法,获取要加载的资源;
调用子类XmlBeanDefinitionReader#loadBeanDefinitions执行真正的加载功能;
接下来,我们分析下这两步的核心逻辑。
// org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set)
public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException {
// 1、获取在IoC容器初始化时设置的资源加载器。 --> 第二步中获取的。
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// 2、将配置文件路径解析为Spring IoC容器封装的资源。
// 加载多个位置指定的Bean配置信息。
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 3、使用子类XmlBeanDefinitionReader具体实现加载资源(重要)。
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// 4、将指定位置的Bean配置信息解析为Spring IoC容器封装的资源。
// 加载单个指定位置的Bean配置信息。
Resource resource = resourceLoader.getResource(location);
// 5、使用子类XmlBeanDefinitionReader具体实现加载资源。
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
首先是用资源加载器的获取资源方法,获取要加载的资源;
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
ApplicationContext中的resourceLoader对象在前面初始化的时候,是实例化PathMatchingResourcePatternResolver对象,getResources是在PathMatchingResourcePatternResolver中实现的。
// org.springframework.core.io.support.PathMatchingResourcePatternResolver#getResources
// 将传入的资源文件路径解析为资源。
// 传入locationPattern,是资源文件路径,比如{classpath*:applicationContext.xml}
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 处理[classpath*:]开头的文件
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// 资源路径中存在* { }这3中通配符的情况
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// 找到所有匹配该路径模式的资源
return findPathMatchingResources(locationPattern);
}
else {
// 根据全路径匹配获取资源
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Generally only look for a pattern after a prefix here,
// and on Tomcat only after the "*/" separator for its "war:" protocol.
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
这块在前面ResourceLoader专场简单介绍过PathMatchingResourcePatternResolver,这里就不冗余了。
到此我们已经加载了配置资源,实际上是获得了配置资源的输入流。
接下来我们要开始对输入流进行解析。
首先是获取资源的输入流:
// org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource)
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 将读入的XML资源进行特殊编码处理(重要)
return loadBeanDefinitions(new EncodedResource(resource));
}
// org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
// 1、将资源文件转换为I/O流。
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
// 2、从InputStream中得到XML的解析源
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 3、具体读取XML过程(重要)。
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
将输入流转换为DOM对象,便于解析。
// org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 1、将XML转换为DOM对象,解析过程由doLoadDocument()完成。
Document doc = doLoadDocument(inputSource, resource);
// 2、解析Doc文档对象,获取Bean定义并缓存(重要)。
// count返回的是本次refresh解析的Bean数量。
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
这一步同样是两个操作:
doLoadDocument:将配置资源转换为文档对象;
registerBeanDefinitions:解析文档;
先来看文档创建逻辑:
// org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadDocument
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
// DocumentLoader将Bean配置信息转换成文档对象。
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 1、创建文档解析工厂类。
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 2、创建文档解析器。
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 3、解析Spring的Bean配置文件资源。
return builder.parse(inputSource);
}
建立文档解析器,将输入流解析为Document对象,便于后面DOM的解析。文档的解析过程是JAXP标准。
至此,Bean配置信息已经被转化为内存Document(DOM)对象缓存,后面就是解析内存中的DOM对象。
然后,我们看看Document对象的解析逻辑。
int count = registerBeanDefinitions(doc, resource);
在前面已经从特定的XML文件中加载了Bean配置资源,并将其转换为Document对象。接下来启动Spring IoC容器对Bean定义的解析过程。
源码如下:
// 解析已经获取到的Doc文档对象,获取Spring配置的Bean定义。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 1、创建BeanDefinitionDocumentReader,负责对XML格式的BeanDefinition进行解析。
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 2、获得容器中注册的Bean数量。
// 如果第一次初始化,这里是默认0。
// 如果不是第一次初始化,这里用于记录重新刷新后Bean数量的变化情况。
int countBefore = getRegistry().getBeanDefinitionCount();
// 3、解析Spring XML配置文件,获取Bean定义(重要)。
// 此处是解析过程的入口,具体实际上是由实现类DefaultBeanDefinitionDocumentReader来完成的。
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 4、通过前后Bean数量的差异,统计解析的Bean数量。
return getRegistry().getBeanDefinitionCount() - countBefore;
}
其中,2和4只是为了统计解析前后的Bean数量差异,1和3主要是完成加载和解析的动作。
首先通过调用XML解析器将Bean配置信息转换为文档对象,但这些文档还没有按照Spring的Bean规则进行解析,这一步只是载入操作。
其次,完成通用XML解析之后,按照Spring Bean的定义规则对Document进行解析。
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
接下来,正式开始对文档的解析操作。
// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
/**
* 解析配置资源获取Bean定义,并缓存到内存中。
* 根据Spring DTD对Bean的定义规则解析Bean定义的文档对象。
*/
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
// 1、获取XmlReaderContext上下文,用于解析XML文件。
this.readerContext = readerContext;
// 2、解析XML文件,得到Bean定义,并缓存到内存中(重要)。
// doc.getDocumentElement()获取根元素
doRegisterBeanDefinitions(doc.getDocumentElement());
}
// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
// 将根目录下定义的Bean信息注册到IoC容器
@SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
// 将标签下的元素委派给BeanDefinitionParserDelegate进行解析。
// BeanDefinitionParserDelegate定义了元素下各种标签的解析逻辑。
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 在解析Bean定义之前,进行自定义解析,增强解析过程的可扩展性
preProcessXml(root);
// 从文档的根元素开始进行Bean定义的文档对象的解析(重要)
parseBeanDefinitions(root, this.delegate);
// 在解析Bean定义之后,进行自定义解析,增加解析过程的可扩展性
postProcessXml(root);
this.delegate = parent;
}
parseBeanDefinitions(root, this.delegate);
开始文档解析:
// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
// 使用Spring的Bean规则从文档的根元素开始对Bean定义文档对象进行解析。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// Bean定义文档使用Spring默认的XML命名空间
// Spring默认XML命名空间:根节点包含[http://www.springframework.org/schema/beans]
if (delegate.isDefaultNamespace(root)) {
// 获得Bean定义文档对象根元素的所有节点
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 获得文档节点是XML元素节点
if (node instanceof Element) {
Element ele = (Element) node;
// 使用Spring的Bean规则解析元素节点
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
// 如果没有使用Spring默认的XML命名空间,则使用用户自定义的解析规则解析元素节点。
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 文档的根节点没有使用Spring默认命名空间,则使用自定义的规则解析文档的根节点。
delegate.parseCustomElement(root);
}
}
下面是一个包含多个Spring Bean标签的配置文件样例,参照样例我们看下Spring如何完成文档解析:
parseDefaultElement(ele, delegate)处理逻辑如下,我们来看一下他如何解析上述XML文件的:
// 使用Spring的Bean规则解析元素节点
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 解析import标签元素,获取标签中的资源路径,完成资源的解析和内容加载。
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 解析标签元素,记录别名关系。
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 解析标签元素。
// 将bean标签元素封装成BeanDefinition,并装饰成BeanDefinitionHolder。
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 解析标签元素的场景
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
每个具体的方法内容就不深入,逻辑不是很复杂,主要解析XML标签的内容。
Spring XML有很多配置信息,这里只解析了和Bean相关的元素。
我们重点解读下
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 1、解析XML元素,并包装成BeanDefinitionHolder使用。
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 当返回的bdHolder 不为空的情况下,若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析.
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 2、将解析的BeanDefinitionHold注册到Spring IoC容器中。
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
1、解析XML
元素,并包装成BeanDefinitionHolder使用。
// 解析元素的入口。
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
// 解析Bean配置信息中的元素,处理元素的id、name和alias
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 获取标签定义的id属性值
String id = ele.getAttribute(ID_ATTRIBUTE);
// 获取标签定义的name属性值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// 保存标签定义的alias属性值
List aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
// 将元素所有name属性值存入别名中
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
// 如果配置了id,将id作为beanName。
// 如果没有配置id,则将别名中第一个值作为beanName。
// beanName就是我们在依赖注入时使用的类名称。
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
// 检查元素所配置的id或者name的唯一性。
// containingBean表示元素中是否包含子元素。
if (containingBean == null) {
// 检查元素所配置的id、name和别名是否重复
checkNameUniqueness(beanName, aliases, ele);
}
// 解析bean元素,封装成BeanDefinition结构
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
// 执行完parseBeanDefinitionElement处理,已经完成对的解析,并将Bean定义封装成为BeanDefinition结构。
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
// 如果beanName为空,即在上面的处理中,id、name和别名都没有配置,且没有包含子元素的情况下,
// 将为解析的Bean生成特定的beanName(根据规则),为后续注册到IoC做准备。
// 此处传入Registry对象,用于获取BeanFactory并判断是否已经包含生成的beanName信息,并没有将生成的beanName注册到IoC容器中。
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
// 如果beanName为空,即在上面的处理中,id、name和别名都没有配置,但包含子元素的情况下,
// 将为解析的Bean生成特定的beanName(根据规则),为后续注册到IoC做准备。
// 此处传入Registry对象,用于获取BeanFactory并判断是否已经包含生成的beanName信息,并没有将生成的beanName注册到IoC容器中。
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
// 为解析的Bean使用病名注册,为了向后兼容。
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 将BeanDefinition封装成BeanDefinitionHolder
// BeanDefinitionHolder是对BeanDefinition的装饰。
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
protected void checkNameUniqueness(String beanName, List aliases, Element beanElement) {
String foundName = null;
if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
foundName = beanName;
}
if (foundName == null) {
foundName = CollectionUtils.findFirstMatch(this.usedNames, aliases);
}
if (foundName != null) {
error("Bean name '" + foundName + "' is already used in this element", beanElement);
}
this.usedNames.add(beanName);
this.usedNames.addAll(aliases);
}
// 详细对元素中配置的Bean定义的其他属性进行解析。
// 在上面的处理中,已经完成了对元素中id、name和alias的处理,所以本方法主要处理这3个属性之外的其他属性。
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
// 记录解析的Bean
this.parseState.push(new BeanEntry(beanName));
// 获取定义的class,存入BeanDefinition中。
// 这里只记录Bean的class类型,不做任何实例化操作,实例化是在依赖注入时完成。
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
// 如果元素中记录了parent属性,则获取parent属性值
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
// 根据配置的class名称和parent初始化BeanDefinition,用于后续Bean定义信息的载入。
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 对配置的属性进行解析,设置到BeanDefinition对象中。
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
// 为元素解析的Bean设置描述信息
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 对元素的meta属性进行解析。
parseMetaElements(ele, bd);
// 对元素的lookup-Method属性进行解析。
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 对元素的replaced-Method属性进行解析。
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析元素的构造方法设置。
parseConstructorArgElements(ele, bd);
// 解析元素的设置,这里相对复杂,后面会详细介绍。
parsePropertyElements(ele, bd);
// 解析元素的qualifier属性。
parseQualifierElements(ele, bd);
// 为当前解析的Bean设置所需的资源和依赖对象
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
// 如果解析出现错误,则返回null
return null;
}
使用BeanDefinitionHolder包装BeanDefinition:
public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName, @Nullable String[] aliases) {
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
Assert.notNull(beanName, "Bean name must not be null");
this.beanDefinition = beanDefinition;
this.beanName = beanName;
this.aliases = aliases;
}
在解析
到这里,我们已经完成了Spring XML配置中
接下来,就是将对象注册到IoC容器中,这一步直接跳到[3.16]即可。
后面3个小节,主要是再详细介绍
在bean元素中,有些比较复杂的Bean会有property子元素,解析过程相对比较复杂,我们这里介绍一下。
我们先找一个带property子元素的bean元素实例:
4级
然后我们看如何解析property元素:
// org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyElements
// 解析元素中的元素
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
// 获取元素的所有子元素
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 如果子元素是元素,则调用下面方法执行元素解析处理。
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);
}
}
}
// 解析元素
public void parsePropertyElement(Element ele, BeanDefinition bd) {
// 获取元素的name属性
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
if (!StringUtils.hasLength(propertyName)) {
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
// 记录解析的propertyName
this.parseState.push(new PropertyEntry(propertyName));
try {
// 如果一个Bean中已经有同名的元素存在,则不进行解析。
// 所以如果一个Bean中配置了多个同名,则只有第一个起作用。
if (bd.getPropertyValues().contains(propertyName)) {
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
// 解析获取元素的值
Object val = parsePropertyValue(ele, bd, propertyName);
// 根据元素的名字和值创建实例
PropertyValue pv = new PropertyValue(propertyName, val);
// 解析
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop();
}
}
// org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyValue
// 解析获取元素的值
@Nullable
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
String elementName = (propertyName != null ?
" element for property '" + propertyName + "'" :
" element");
// 获取元素的所有子元素,只能是: ref, value, list, etc中的一种。
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 子元素不是description和meta属性
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we're looking for.
if (subElement != null) {
error(elementName + " must not contain more than one sub-element", ele);
}
else {
// 当前包含子元素
subElement = (Element) node;
}
}
}
// 判断属性值是ref还是value,只能是两者其一,不能全部包含。
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
// 如果属性值是ref,创建一个ref的数据对象RuntimeBeanReference,来封装ref
if (hasRefAttribute) {
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
// 生成一个指向运行时所依赖对象的引用,即当前Bean在初始化时依赖ref Bean
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
// 设置这个ref的数据对象被当前对象所引用
ref.setSource(extractSource(ele));
return ref;
}
// 如果属性值是value,创建一个value的数据对象TypedStringValue,来封装value
else if (hasValueAttribute) {
// 一个持有String类型值的对象
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
// 设置这个value的数据对象被当前对象所引用
valueHolder.setSource(extractSource(ele));
return valueHolder;
}
// 如果当前还有子元素,解析子元素
else if (subElement != null) {
return parsePropertySubElement(subElement, bd);
}
// 属性值既不是ref,也不是value,解析报错。
else {
// Neither child element nor "ref" or "value" attribute found.
error(elementName + " must specify a ref or value", ele);
return null;
}
}
在对property元素的解析中,主要有3个要点:
ref被封装为指向依赖对象的一个引用;
value被封装为一个字符串类型的对象;
ref和value都通过setSource方法将属性值(或引用)与所引用的属性关联起来;
我们再来看看如何解析property的子元素:
// org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertySubElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)
// 解析-的子元素
@Nullable
public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) {
return parsePropertySubElement(ele, bd, null);
}
@Nullable
public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) {
// 如果元素没有使用Spring默认命名空间,则使用用户自定义的规则解析内嵌元素。
if (!isDefaultNamespace(ele)) {
return parseNestedCustomElement(ele, bd);
}
// 如果子元素是,则使用解析的方法解析
else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
if (nestedBd != null) {
nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
}
return nestedBd;
}
// 如果子元素是ref,ref只能有3个属性:bean、local、parent,执行以下逻辑解析
else if (nodeNameEquals(ele, REF_ELEMENT)) {
// A generic reference to any name of any bean.
String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);
boolean toParent = false;
if (!StringUtils.hasLength(refName)) {
// A reference to the id of another bean in a parent context.
refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
toParent = true;
if (!StringUtils.hasLength(refName)) {
error("'bean' or 'parent' is required for element", ele);
return null;
}
}
if (!StringUtils.hasText(refName)) {
error(" element contains empty target attribute", ele);
return null;
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
ref.setSource(extractSource(ele));
return ref;
}
// 如果子元素是使用解析的方法解析
else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
return parseIdRefElement(ele);
}
// 如果子元素是
else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
return parseValueElement(ele, defaultValueType);
}
// 如果子元素是null,为设置一个封装null值得字符串数据
else if (nodeNameEquals(ele, NULL_ELEMENT)) {
// It's a distinguished null value. Let's wrap it in a TypedStringValue
// object in order to preserve the source location.
TypedStringValue nullHolder = new TypedStringValue(null);
nullHolder.setSource(extractSource(ele));
return nullHolder;
}
// 如果子元素是array
else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
return parseArrayElement(ele, bd);
}
// 如果子元素是list
else if (nodeNameEquals(ele, LIST_ELEMENT)) {
return parseListElement(ele, bd);
}
// 如果子元素是set
else if (nodeNameEquals(ele, SET_ELEMENT)) {
return parseSetElement(ele, bd);
}
// 如果子元素是map
else if (nodeNameEquals(ele, MAP_ELEMENT)) {
return parseMapElement(ele, bd);
}
// 如果子元素是props
else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
return parsePropsElement(ele);
}
// 如果子元素是什么也没匹配上
else {
error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
return null;
}
}
可以看出,property的子元素可以有很多种数据类型,其中我们以List的解析距离看看具体逻辑,其他类型就不过多解析了:
// org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseListElement
// 解析集合子元素
public List
至此,Bean已经解析完了,接下来就是要注册到IoC容器中去了。要说明的是,接下来的注册动作,只是将已经解析的Bean的配置注册到IoC容器中,还没有真正完成Bean对象的实例化操作。
先来看看Bean是如何注册到IoC容器中。
// org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition
// 将解析的BeanDefinitionHoklder注册到Spring IoC容器中。
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 获取之前解析的beanName,作为key name注册到IoC容器中。
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 如果解析的bean有别名,将别名信息注册到IoC容器中。
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
真正完成注册动作的是在DefaultListableBeanFactory中,我们继续分析源码:
/** 用于缓存注册BeanDefinition的IoC容器 */
private final Map beanDefinitionMap = new ConcurrentHashMap<>(256);
/** 缓存已经完成IoC容器注册的BeanDefinition对应的beanName */
private volatile List beanDefinitionNames = new ArrayList<>(256);
// org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
// 向Spring IoC容器中注册解析的BeanDefinition
@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");
// 1、对解析的BeanDefinition进行校验
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// 2、尝试从IoC容器中获取beanName已缓存的BeanDefinition
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 3、IoC容器已经缓存BeanDefinition时
if (existingDefinition != null) {
// BeanDefinition属性配置时是否允许覆盖
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 将注册到IoC容器中。
this.beanDefinitionMap.put(beanName, beanDefinition);
}
// 4、IoC未缓存beanName对应的BeanDefinition信息时
else {
// 记录缓存beanName的IoC容器是否已完成初始化
if (hasBeanCreationStarted()) {
// 注册到IoC容器需要线程同步,以保证数据的一致性。
synchronized (this.beanDefinitionMap) {
// 将注册到IoC容器中。
this.beanDefinitionMap.put(beanName, beanDefinition);
// 记录已经注册的BeanDefinition对应的beanName信息。
List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
到此为止,Bean配置文件中的定义信息已经完成解析并注册到Spring IoC容器中,交由容器来进行管理,IoC的初始化操作已经全部完成。现在Spring IoC容器中,已经缓存了所有Bean的定义信息,可以进行检索和使用,Spring IoC容器会对这些Bean进行管理和维护。
接下来就是DI(控制翻转)登场,完成Bean的实例化和自动依赖注入操作。
最后附上一张容器初始化整体流程图:
图片有点大,如果看不清的话,可以通过下面的链接查看。
https://processon.com/view/6072c7c3e0b34d042604dd86
参考学习资料和相关文章列表,请参照如下链接:
https://blog.csdn.net/moonlight821/article/details/116463513