Spring框架能发展至今并成为最主流的框架必然有它的道理,对于我们学习者来说理应尽量的去汲取前辈们知识,我们不能被现今便捷的框架遮蔽了自己的双眼也许你离开了这些框架你可能什么都不是,因为我们已经是站在了巨人的肩膀上。
- 一、 前言
- 二、IOC(Inversion of Control)
- 2.1 BeanFactory
- 2.2 BeanDefinition
- 2.3 BeanDefinitionReader
- 三、IOC初始化流程
- 四、基于配置文件的IOC容器初始化
- 4.1定位文件
- 4.2 获得文件路径
- 4.3启动
- 4.4创建容器
- 4.5载入配置路径
- 4.6分配路径处理策略
- 4.7解析配置文件路径
- 4.8读取文件配置
- 4.9准备文档对象
- 4.1.10分配解析策略
- 4.1.11将配置载入内存
- 4.1.12 载入元素
- 4.1.13载入元素
- 4.1.14载入的子元素
- 4.1.15载入的子元素
- 4.1.16 分配注册策略
- 4.1.17 向容器中注册
- 五、基于注解的IOC容器初始化
- 5.1定位Bean的扫描路径
- 5.2读取Annotation元数据
- 5.2.1 AnnotationConfigApplicationContext通过调用注解Bean定义读取器
- 5.2.2AnnotationScopeMetadataResolver解析作用域元数据
- 5.2.3AnnotationConfigUtils处理注解Bean定义类中的通用注解
- 5.2.4AnnotationConfigUtils根据注解Bean定义类中配置的作用域为其应用相应的代理策略
- 5.2.5 BeanDefinitionReaderUtils向容器注册Bean
- 5.3扫描指定包并解析为BeanDefinition
- 5.3.1ClassPathBeanDefinitionScanner扫描给定的包及其子包
- 5.3.2 ClassPathScanningCandidateComponentProvider扫描给定包及其子包的类
- 5.4 注册注解BeanDefinition
记录本系列的文章主要目的是为了自己的学习梳理,同时也希望各位业界的前辈们能够帮忙指点迷津。 本篇内容主要是理解一下IOC运行流程以及源码。
二、IOC(Inversion of Control)控制反转:所谓控制反转就是将我们代码中需要实现的对象创建、依赖的代码,反转给容器来帮忙实现。要完成此操作必然需要一个容器,同时需要某种描述来让容器知道需要创建的对象与对象的关系,这个描述具体的体现就是 配 置 文 件 \color{#FF0000}{配置文件} 配置文件 既然说到了配置文件那我们来思考几个问题: 1.如何描述对象和对象的关系? ----> 我们从xml、properties、yml等语义话配置文件都可以表示? 2.文件存放在哪? ----> 可以存在classpath、filesystem、servletContext也可以是网络资源。 3.配置文件不同,我们如何统一解析? ----> 我们首先需要定义一个统一的对象定义,所有资源都需要转换成改定义格式。
2.1 BeanFactorySpirngBean的创建是典型工厂模式,一系列的工厂模式,为IOC容器的开发和管理提供了许多便捷的基础服务,我们先来看看BeanFactory的类图看一看他们之间的关系,如下: 我们可以看到BeanFactory作为顶层的接口类,它的里面定义很多IOC容器的基本规范,它有三个重要的子类,AutowireCapableBeanFactory、HierarchicalBeanFactory 、ListableBeanFactory并且它们最终的实现类都是DefaultListableBeanFactory,他实现了所有的接口。 那为何要定义这么多的接口? 个人的理解是单一职责原则,每个接口都有他自己的使用场合,为了区分在Spring内部操作过程中对象的传递和转化的过程。说白了就是方便管理和维护。 比如说:ListableBeanFactory表示这些Bean是可序列化的,HierarchicalBeanFactory 表示这些Bean是有继承关系的,每个Bean也可能会有父类Bean。AutowireCapableBeanFactory 表示这些Bean的自动装配规则。这三个接口共用定义组成了Bean的集合、Bean之间的关系、以及Bean的行为。 我们来看看BeanFactory的源码:
public interface BeanFactory {
//转义标识符,如果需要得到工厂本身,需要转义
String FACTORY_BEAN_PREFIX = "&";
//根据bean的名字,获取在IOC容器中得到bean实例
Object getBean(String name) throws BeansException;
//根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。
T getBean(String name, @Nullable Class requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
T getBean(Class requiredType) throws BeansException;
T getBean(Class requiredType, Object... args) throws BeansException;
//提供对bean的检索,看看是否在IOC容器有这个名字的bean
boolean containsBean(String name);
//并同时判断这个bean是不是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
//判断是不是原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
/**
*
* 根据beanName判断是否有指定类型的匹配
* @param name beanName
* @param typeToMatch 需要匹配的目标类型
* 例如:
* name="time", typeToMatch=Date.Class
* 如果time类型是Date类型返回true
*/
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
//同上一样
boolean isTypeMatch(String name, @Nullable Class typeToMatch) throws NoSuchBeanDefinitionException;
//得到bean实例的Class类型
@Nullable
Class getType(String name) throws NoSuchBeanDefinitionException;
//得到bean的别名,如果根据别名检索,那么其原名也会被检索出来
String[] getAliases(String name);
}
在BeanFactory中对IOC做了基本的行为定义,根本不关心你的Bean是如何定义和加载的。 而我们需要知道工厂是如何生产对象的,就需要看IOC的具体实现类,Spring也提供了许多IOC容器的实现,例如:GenericApplicationContext 、ClasspathXmlApplicationContext ,但是我们最常用的还是ApplicationContext,为什么呢? ApplicationContext是spring提供的一个比较高级点的IOC容器,它除了能够提供IOC的基本功能外,还为用户提供了许多额外的功能扩展: 1、支持信息源,可以实现国际化。(实现MessageSource接口) 2、访问资源。(实现ResourcePatternResolver接口) 3、支持应用事件。(实现ApplicationEventPublisher接口) 这三点目前小编都还没有系统的接触到,先给大家看看,希望大佬能够提供一下资源( - -!),在此感谢
2.2 BeanDefinitionSpirngIOC管理了我们定义的各种Bean对象以及它们之间的相互关系,Bean对象在spring中是以BeanDefinition来描述的,在Spring容器启动的过程中,会将Bean解析成BeanDefinition结构,我们先来看看它的类图,如下: 在这我解析一下BeanDefinition接口定义的属性,在接口内部定义了非常多的属性和方法,类名、scope、属性、构造函数参数列表、依赖的bean、是否是单例类、是否是懒加载之类的,本质就是将Bean封装到BeanDefinition中在后续操作中就是对BeanDefinition操作,可以根据里面的类名、构造函数、构造函数参数,使用反射进行对象创建。
Bean的解析过程实际上还是比较复杂的,功能划分粒度很细,因为这个地方需要被扩展的地方很多,所以必须保证有足够的灵活性来面对不同的变化,Bean的解析本质上就是对Spring配置文件的解析。这个解析的过程通过BeanDefinitionReader来完成,我们来看看类图:
在上一篇文章MVC介绍的DispatcherServlet类中,我们所有的初始化工作都是放在init()中完成,那么这个init()方法是谁的方法呢?往上追寻HttpServletBean类中找到了我们的答案:
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
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();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
我们发现了initServletBean()方法,在init()方法中,真正完成初始化容器动作的逻辑其实在initServletBean()方法中,我们追寻此方法,在FrameworkServlet中完成了对该方法的实现,请看源码:
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
在此方法并没有过于复杂的逻辑,就不做啥解释了,我们发现有initWebAppplicationContext(),是不是有点熟悉的感觉?继续跟进
protected WebApplicationContext initWebApplicationContext() {
//先从ServletContext中获得父容器WebAppliationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
//声明子容器
WebApplicationContext wac = null;
//建立父、子容器之间的关联关系
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
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);
}
//这个方法里面调用了AbatractApplication的refresh()方法
//refresh()方法是真正的IOC初始化的入口
//模板方法,规定IOC初始化基本流程
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//先去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();
}
//给上一步创建好的IOC容器赋值
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
//触发onRefresh方法
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.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
找到重点方法configureAndRefreshWebApplicationContext()跟进
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);
wac.refresh();
}
观察以上两处代码在initWebApplicationContext()方法中调用了configAndRefreshWebApplicationContext()方法,在此方法中调用了refresh()方法,这个是真正启动IOC 容器的入口,IOC 容器初始化以后,最后调用了DispatcherServlet的onRefresh()方法,在onRefresh()方法中又是直接调用initStrategies()方法初始化SpringMVC的九大组件:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* May be overridden in subclasses in order to initialize further strategy objects.
*/
//初始化策略
protected void initStrategies(ApplicationContext context) {
//多文件上传的组件
initMultipartResolver(context);
//初始化本地语言环境
initLocaleResolver(context);
//初始化模板处理器
initThemeResolver(context);
//handlerMapping
initHandlerMappings(context);
//初始化参数适配器
initHandlerAdapters(context);
//初始化异常拦截器
initHandlerExceptionResolvers(context);
//初始化视图预处理器
initRequestToViewNameTranslator(context);
//初始化视图转换器
initViewResolvers(context);
//
initFlashMapManager(context);
}
四、基于配置文件的IOC容器初始化
4.1定位文件
IOC 容器的初始化包括BeanDefinition的Resource定位、加载和注册这三个基本的过程,在上文介绍了spring提供了很多IOC容器实现,我们就以ApplicationContext为例,因为我们最熟悉它使用的也最多,因为在web项目中使用的XmlWebApplicationContext以及ClasspathXmlApplicationContext 就来自这个体系。 ApplicationContext允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。对于Bean的查找 可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的Spring应用提供了一个共享的Bean定义环境。
ApplicationContext app = new ClassPathXmlApplicationContext("application.xml");
不知大家对此方法是否还有映像,在我们最初学习spring的时候就是通过此方法指定配置文件,然后来测试各种Bean的注入啊什么的。我们这里也从此深入。 查看其构造函数的调用
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
查看此重载方法
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
也就是说实际上了是调用了此方法。 其实不光ClassPathXmlApplicationContext像还有 AnnotationConfigApplicationContext 、 FileSystemXmlApplicationContext 、XmlWebApplicationContext等都继承自父容器AbstractApplicationContext主要用到了装饰器模式和策略模式,最终都是调用refresh()方法。
4.2 获得文件路径通 过分析ClassPathXmlApplicationContext的源代码可以知道 ,在创建 ClassPathXmlApplicationContext容器时,构造方法做以下两项重要工作:首先,调用父类容器的构造方法(super(parent)方法)为容器设置好Bean资源加载器。然后,再调用父类AbstractRefreshableConfigApplicationContext的setConfigLocations(configLocations)方法设置Bean配置信息的定位路径。通过追踪ClassPathXmlApplicationContext的继承体系 , 发现其父类的 类AbstractApplicationContext中初始化IOC容器所做的主要部分源码如下:
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
//静态初始化块,在整个容器创建过程中只执行一次
static {
//为了避免应用程序在Weblogic8.1关闭时出现类加载异常加载问题,加载IoC容
//器关闭事件(ContextClosedEvent)类
ContextClosedEvent.class.getName();
}
public AbstractApplicationContext() { this.resourcePatternResolver = getResourcePatternResolver(); }
public AbstractApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); }
//获取一个 Spring Source 的加载器用于读入 Spring Bean 配置信息
protected ResourcePatternResolver getResourcePatternResolver() { //AbstractApplicationContext 继承 DefaultResourceLoader,因此也是一个资源加载器 //Spring 资源加载器,其 getResource(String location)方法用于载入资源
return new PathMatchingResourcePatternResolver(this); }
/**省略以下代码*/
}
AbstractApplicationContext 的默认构造方法中有调用 PathMatchingResourcePatternResolver 的 构造方法创建Spring资源加载器:
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
//设置Spring的资源加载器
this.resourceLoader = resourceLoader;
}
在设置容器的资源加载器之后,接下来回到 ClassPathXmlApplicationContext构造方法中继续 执行setConfigLocations()方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean配置信息的定位,该方法的源码如下:
//解析Bean定义资源文件的路径,处理多个资源文件字符串数组
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 beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("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);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
//当解析出错时,返回null
return null;
}
只要使用过Spring,对Spring配置文件比较熟悉的人,通过对上述源码的分析,就会明白我们在Spring 配置文件中元素的中配置的属性就是通过该方法解析和设置到Bean中去的。 注意:在解析元素过程中没有创建和实例化 Bean 对象,只是创建了 Bean 对象的定义类 BeanDefinition,将元素中的配置信息设置到 BeanDefinition 中作为记录,当依赖注入时才 使用这些记录信息创建和实例化具体的Bean对象。上面方法中一些对一些配置如元信息(meta)、qualifier 等的解析,我们在Spring中配置时使用的也不多,我们在使用Spring的元素时,配置最多的是属性,因此我们下面继续分析源码,了解Bean的属性在解析时是如何设置的。
4.1.13载入元素BeanDefinitionParserDelegate 在解析调用 parsePropertyElements()方法解析元 素中的属性子元素,解析源码如下:
//解析元素中的子元素
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
//获取元素中所有的子元素
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?