写点什么

Spring 核心原理之 IoC 容器初体验(2)

作者:Tom弹架构
  • 2021 年 12 月 24 日
  • 本文字数:6227 字

    阅读完需:约 20 分钟

> 本文节选自《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 容器的实现供用户选择,其相互关系如下图所示。

![file](http://image.openwrite.cn/28055_1C121177448E4EE3B7EAD700950E7F01)

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

```java

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。,其继承体系如下图所示。

![file](http://image.openwrite.cn/28055_32922032DF96403FB5B0E0CA80298957)

## 2.3. BeanDefinitionReader

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

![file](http://image.openwrite.cn/28055_40D4187395DA46FCB57BA26A24A86A53)

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

## 3 基于 Web 的 IoC 容器初体验

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

```java

@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()方法中,我们继续跟进:

```java

@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(),继续跟进:

```java

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 的九大组件:

```java

@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”可获取完整源码。


本文为“Tom 弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom 弹架构 』可获取更多技术干货!


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

发布于: 2021 年 12 月 24 日
用户头像

Tom弹架构

关注

不只做一个技术者,更要做一个思考者 2021.10.22 加入

畅销书作者,代表作品:《Spring 5核心原理》、《Netty 4核心原理》、《设计模式就该这样学》

评论

发布
暂无评论
Spring核心原理之IoC容器初体验(2)