07. Spring IoC源码解析
Posted IT BOY
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了07. Spring IoC源码解析相关的知识,希望对你有一定的参考价值。
07 Spring IoC源码解析
目录
Pt2.9 BeanDefinitionParserDelegate
Pt1 IoC 和 DI
IoC(Inversion of Control,控制反转)就是把原来代码里需要实现的对象创建、依赖,反转给容器来帮助管理。我们需要创建一个容器(FactoryBean),同时需要一种描述来让容器知道要创建的对象与对象的关系,这个描述最具体的表现就是Spring配置文件。
DI(Dependency Injection,依赖注入)就是指对象被动接受依赖类而不需要自己主动去找。换句话说,就是指对象不是从容器中查找他依赖的类,而是在容器实例化对象时主动将它依赖的类注入给他。
Pt2 IoC容器核心类
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
接口的实现。
Pt2.1 ApplicationContext
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;
}
Pt2.2 BeanFactory
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> T getBean(String name, Class<T> requiredType) throws BeansException;
/**
* 根据Bean的名字和参数,获取在IoC容器中得到的Bean的实例。
*/
Object getBean(String name, Object... args) throws BeansException;
/**
* 根据Class类型获取IoC容器中的实例。
*/
<T> T getBean(Class<T> requiredType) throws BeansException;
/**
* 根据Class类型和参数,获取在IoC容器中得到的Bean的实例。
*/
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
/**
* 获取Bean的提供者。
*/
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> 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拿来使用。
Pt2.3 Environment
Environment用来表示整个应用运行时的环境,为了更形象地理解Environment,你可以把Spring应用的运行时简单地想象成两个部分:一个是Spring应用本身,一个是Spring应用所处的环境,而Environment这个接口,就是对这个所处的环境的概念性建模。
Environment在容器中是一个抽象的集合,是指应用环境的2个方面:profiles和properties。
(1) Profile
profile配置是一个被命名的、bean定义的逻辑组,这些bean只有在给定的profile配置激活时才会注册到容器。profile的使用是为了能以最简单的方式切换配置文件,通常是简化多个环境不同配置的复杂。
比如项目中经常遇到的开发、测试、生产等环境的切换。
<beans profile="test">
<context:property-placeholder location="/WEB-INF/test.properties" />
</beans>
<beans profile="dev">
<context:property-placeholder location="/WEB-INF/dev.properties" />
</beans>
启动是指定运行环境:
JAVA_OPTS="-Dspring.profiles.active=test"
Environment环境对象的作用,对于profiles配置来说,它能决定当前激活的是哪个profile配置,和哪个profile是默认。
一个profile就是一组Bean定义的逻辑分组。
-
这个分组,也就这个profile,被赋予一个命名,就是这个profile名字。
-
只有当一个profile处于active状态时,它对应的逻辑上组织在一起的这些Bean定义才会被注册到容器中。
-
Bean添加到profile可以通过XML定义方式或才annotation注解方式。
-
Environment对于profile所扮演的角色是用来指定哪些profile是当前活跃的缺省。
(2) Properties
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();
}
Pt2.4 ResourceLoader
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(),包含了对各种资源文件路径方式的解析逻辑,里面具体的逻辑就不研究了。
Pt2.5 BeanDefinition
Spring IoC容器用来管理各种Bean对象及其互相关系,BeanDefinition就是用来定义Bean对象及其关系的。
BeanDefinition主要定义了以下信息:
-
BeanDefinition 包含了我们对 bean 做的配置,比如 XML<bean/>标签的形式进行的配置;
-
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();
}
Pt2.6 BeanDefinitionReader
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;
}
Pt2.7 BeanDefinitionHolder
简单的说,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;
}
}
Pt2.8 Resource
在ResourceLoader部分有介绍。
Pt2.9 BeanDefinitionParserDelegate
在将XML配置转为Document文档后,Spring是委托给BeanDefinitionParserDelegate来完成文档的解析的。
BeanDefinitionParserDelegate中方法比较多,都是针对xml配置中各种标签进行解析操作。
源码比较多,这里就不贴了。
Pt3 IoC容器初始化
了解了Spring IoC容器的核心类之后,我们正式来研究Spring IoC源码初始化流程。
Pt3.1 寻找入口
Spring IoC容器初始化,有两个不同的入口,我们分别介绍一下。
-
DispatcherServlet:在使用tomcat等Web容器启动时触发,继而会在init()中触发IoC初始化,配置在web.xml中;
-
ApplicationContext:在使用main()启动的Web容器启动时触发,在具体的ApplicationContext实现类中完成IoC初始化;
(1) DispatcherServlet#init()
在基于Web容器启动应用时,我们常见的web.xml配置如下:
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
可以看到,在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()的具体逻辑。
(2) ApplicationContext入口
还有一种比较常用的启动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这条线,看具体加载的过程。
Pt3.2 获得配置路径
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定义资源的定位处理。
Pt3.3 开始启动容器
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()的逻辑。
Pt3.4 创建容器
进入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、创建新以上是关于07. Spring IoC源码解析的主要内容,如果未能解决你的问题,请参考以下文章