本文节选自《Spring 5核心原理》

1 IoC与DI基本概念

IoC(Inversion of Control,控制反转)就是把原来代码里需要实现的对象创建、依赖,反转给容器来帮忙实现。我们需要创建一个容器,同时需要一种描述来让容器知道要创建的对象与对象的关系。这个描述最具体的表现就是我们所看到的配置文件。

DI(Dependency Injection,依赖注入)就是指对象被动接受依赖类而不自己主动去找,换句话说,就是指对象不是从容器中查找它依赖的类,而是在容器实例化对象时主动将它依赖的类注入给它。

我们先从自己设计的视角来考虑。

(1)对象与对象的关系怎么表示?

可以用XML、properties等语义化配置文件表示。

(2)描述对象关系的文件存放在哪里?

可能是classpath、filesystem或者URL网络资源、servletContext等。

(3)不同的配置文件对对象的描述不一样,如标准的、自定义声明式的,如何统一?

在内部需要有一个统一的关于对象的定义,所有外部的描述都必须转化成统一的描述定义。

(4)如何对不同的配置文件进行解析?

需要对不同的配置文件语法采用不同的解析器。

2 Spring核心容器类图

2.1. BeanFactory

Spring中Bean的创建是典型的工厂模式,这一系列的Bean工厂,即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现供用户选择,其相互关系如下图所示。

其中,BeanFactory作为最顶层的一个接口类,定义了IoC容器的基本功能规范,BeanFactory有三个重要的子类:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口。那么为何要定义这么多层次的接口呢?查阅这些接口的源码和说明发现,每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。例如,ListableBeanFactory接口表示这些Bean可列表化,而HierarchicalBeanFactory表示这些Bean是有继承关系的,也就是每个Bean可能有父Bean。AutowireCapableBeanFactory接口定义Bean的自动装配规则。这三个接口共同定义了Bean的集合、Bean之间的关系及Bean行为。最基本的IoC容器接口是BeanFactory,来看一下它的源码:


public interface BeanFactory { //对FactoryBean的转义定义,因为如果使用Bean的名字检索FactoryBean得到的对象是工厂生成的对象
//如果需要得到工厂本身,需要转义
String FACTORY_BEAN_PREFIX = "&"; //根据Bean的名字,获取在IoC容器中得到的Bean实例
Object getBean(String name) throws BeansException; //根据Bean的名字和Class类型来得到Bean实例,增加了类型安全验证机制
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException; //提供对Bean的检索,看看在IoC容器中是否有这个名字的Bean
boolean containsBean(String name); //根据Bean的名字得到Bean实例,同时判断这个Bean是不是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
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是Spring提供的一个高级的IoC容器,它除了能够提供IoC容器的基本功能,还为用户提供了以下附加服务。

(1)支持信息源,可以实现国际化(实现MessageSource接口)。

(2)访问资源(实现ResourcePatternResolver接口,后面章节会讲到)。

(3)支持应用事件(实现ApplicationEventPublisher接口)。

2.2. BeanDefinition

BeanDefinition 用于保存 Bean 的相关信息,包括属性、构造方法参数、依赖的 Bean 名称及是否单例、延迟加载等,它相当于实例化 Bean 的原材料,Spring 就是根据 BeanDefinition 中的信息实例化 Bean。,其继承体系如下图所示。

2.3. BeanDefinitionReader

Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。这个解析过程主要通过BeanDefinitionReader来完成,看看Spring中BeanDefinitionReader的类结构图,如下图所示。

通过前面的分析,我们对Spring框架体系有了一个基本的宏观了解,希望“小伙伴们”好好理解,最好在脑海中形成画面,为以后的学习打下良好的基础。

3 基于Web的IoC容器初体验

我们还是从大家最熟悉的DispatcherServlet开始,最先想到的应该是DispatcherServlet的init()方法。我们在DispatherServlet中并没有找到init()方法,经过探索,在其父类HttpServletBean中找到了,代码如下:


@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
} 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");
}
}

在init()方法中,真正完成初始化容器动作的代码其实在initServletBean()方法中,我们继续跟进:

@Override
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");
}
}

在上面的代码中终于看到了似曾相识的代码initWebApplicationContext(),继续跟进:

protected WebApplicationContext initWebApplicationContext() {

   //先从ServletContext中获得父容器WebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
//声明子容器
WebApplicationContext wac = null; //建立父、子容器之间的关联关系
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//先去ServletContext中查找Web容器的引用是否存在,并创建好默认的空IoC容器
if (wac == null) {
wac = findWebApplicationContext();
}
//给上一步创建好的IoC容器赋值
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
//触发onRefresh()方法
if (!this.refreshEventReceived) {
onRefresh(wac);
} if (this.publishContext) {
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;
} @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;
} protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
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");
}
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;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
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())); ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(),
getServletConfig());
} postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}

从上面的代码可以看出,在configAndRefreshWebApplicationContext()方法中调用了refresh()方法,这是真正启动IoC容器的入口,后面会详细介绍。IoC容器初始化以后,调用了DispatcherServlet的onRefresh()方法,在onRefresh()方法中又直接调用initStrategies()方法初始化Spring MVC的九大组件:

@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
} //初始化策略
protected void initStrategies(ApplicationContext context) {
//多文件上传的组件
initMultipartResolver(context);
//初始化本地语言环境
initLocaleResolver(context);
//初始化模板处理器
initThemeResolver(context);
//初始化handlerMapping
initHandlerMappings(context);
//初始化参数适配器
initHandlerAdapters(context);
//初始化异常拦截器
initHandlerExceptionResolvers(context);
//初始化视图预处理器
initRequestToViewNameTranslator(context);
//初始化视图转换器
initViewResolvers(context);
//初始化Flashmap管理器
initFlashMapManager(context);
}

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!

如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!

Spring核心原理之IoC容器初体验(2)的更多相关文章

  1. Spring核心原理之 IoC容器中那些鲜为人知的细节(3)

    本文节选自<Spring 5核心原理> Spring IoC容器还有一些高级特性,如使用lazy-init属性对Bean预初始化.使用FactoryBean产生或者修饰Bean对象的生成. ...

  2. 30个类手写Spring核心原理之Ioc顶层架构设计(2)

    本文节选自<Spring 5核心原理> 1 Annotation(自定义配置)模块 Annotation的代码实现我们还是沿用Mini版本的,保持不变,复制过来便可. 1.1 @GPSer ...

  3. Spring核心模块:IoC容器介绍

    1.IoC容器运用的是控制反转模式. 2.IoC容器负责管理对象之间的依赖关系,并完成对象的注入. 3.在IoC设计中,会将依赖关系注入到特定组件中,其中setter注入和构造器注入是主要的注入方式. ...

  4. Spring boot集成Rabbit MQ使用初体验

    Spring boot集成Rabbit MQ使用初体验 1.rabbit mq基本特性 首先介绍一下rabbitMQ的几个特性 Asynchronous Messaging Supports mult ...

  5. Spring源码解析-ioc容器的设计

    Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...

  6. 30个类手写Spring核心原理之环境准备(1)

    本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...

  7. Docker深入浅出系列 | 容器初体验

    目录 Docker深入浅出系列 | 容器初体验 教程目标 预备工作 容器与虚拟化技术 什么是Docker 为什么要用Docker 事例 什么是容器镜像和容器 容器与虚拟机的区别 Vagrant与Doc ...

  8. Spring系列14:IoC容器的扩展点

    Spring系列14:IoC容器的扩展点 回顾 知识需要成体系地学习,本系列文章前后有关联,建议按照顺序阅读.上一篇我们详细介绍了Spring Bean的生命周期和丰富的扩展点,没有阅读的强烈建议先阅 ...

  9. spring之:XmlWebApplicationContext作为Spring Web应用的IoC容器,实例化和加载Bean的过程

    它既是 DispatcherServlet 的 (WebApplicationContext)默认策略,又是 ContextLoaderListener 创建 root WebApplicationC ...

随机推荐

  1. 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明

    目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...

  2. 【SCOI2005】繁忙的都市

    Description 城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造.城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口 ...

  3. intent.getSerializableExtra(转)

    Activity之间通过Intent传递值,支持基本数据类型和String对象及它们的数组对象byte.byte[].char.char[].boolean.boolean[].short.short ...

  4. 到底什么是自动化优先思维?与RPA有什么关系?

    基于RPA的自动化优先,正在成为广大组织的主流管理思维 到底什么是自动化优先思维?与RPA有什么关系? 如何用RPA简单快速的打造一个自动化优先的组织? 文/王吉伟 在IT运维项目中,组织经常会遇到先 ...

  5. STM32一些特殊引脚做IO使用的注意事项

    1 PC13.PC14.PC15的使用 这三个引脚与RTC复用,<STM32参考手册>中这样描述: PC13 PC14 PC15需要将VBAT与VDD连接,实测采用以下程序驱动4个74HC ...

  6. Shell变量与算术运算

    区分两个 Shell Shell 语言与 Shell 解释器 Shell 语言 写 Shell 脚本使用的是 Shell 语言,Shell 既是一种命令语言,又是一种程序设计语言. 作为命令语言,它交 ...

  7. JavaIO——内存操作流、打印流

    我们之前所做的都是对文件进行IO处理,实则我们也可以对内存进行IO处理.我们将发生在内存中的IO处理称为内存流. 内存操作流也可分为两类:字节内存流和字符内存流. (1)ByteArrayInputS ...

  8. 【Linux】【Commands】trouble shooting命令详解目录

    1. 简介 1.1. 最近看到阿里的运维招聘需要熟练掌握以下的命令,我就针对这几个命令做一下总结,有些命令我觉得别人总结的挺好了,我就不赘述了 1.2. 还有一些其他我觉得用得到的命令的用法会在第三部 ...

  9. jquery总结和注意事项

    1.关于页面元素的引用通过jquery的$()引用元素包括通过id.class.元素名以及元素的层级关系及dom或者xpath条件等方法,且返回的对象为jquery对象(集合对象),不能直接调用dom ...

  10. oracle体系结构(图)