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

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

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

其中,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 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的名字得到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。,其继承体系如下图所示。

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

2.3. BeanDefinitionReader

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

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

通过前面的分析,我们对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);
}

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

本文为”Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

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

Original: https://www.cnblogs.com/gupaoedu-tom/p/15727066.html
Author: Tom弹架构
Title: Spring核心原理之IoC容器初体验(2)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/579206/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 如何等待ajax完成再执行相应操作

    ajax广泛应用于异步请求,对于大多数业务来说,这是十分方便的,但对于一些特殊的业务,ajax的异步性会起到相反的作用。 例如在ajax请求成功后,后续的操作需要依赖ajax执行成…

    Java 2023年6月6日
    090
  • 【干货】MySQL底层架构设计,你了解多少?

    很多开发同学对SQL优化如数家珍,却对MySQL架构一知半解。岂不是只见树叶,不见森林,终将陷入细节中不能自拔。 今天就一块学习MySQL分层架构,深入了解MySQL底层实现原理,…

    Java 2023年6月8日
    090
  • windows中打开redis-server闪退

    windows中打开redis-server闪退 第一次运行redis-server的时候闪退 win+R运行cmd打开命令行 进入redis根目录 输入 redis-server…

    Java 2023年6月9日
    095
  • 记录一下copy我博客的地址(捂脸)

    背景 今天又需要基于Spring扩展点做些事情,来看看自己之前记录的博客,好奇百度页面搜索了下看看能不能搜出我的文章,发现了熟悉的字眼和图片,发现完全就是自己的…. 有…

    Java 2023年6月6日
    0117
  • 多线程的一点小结

    最近看多线程比较多,对于volatile,syn,CAS,JMM模型(主内存,工作内存)这些概念有些理解。volatile,syn工作机制等。以及对于多线程中可见性,原子性的理解。…

    Java 2023年6月15日
    080
  • 设计模式之备忘录模式

    无论是我们在使用word还是记事本,系统都会为我们提供撤销的功能,这几乎是人人都会使用到的功能,而在我们实际开发中,会不会存在一个很复杂的对象,当更改了其中的某一个属性以后,也提供…

    Java 2023年6月8日
    072
  • Java SE 2、抽象类

    用abstract关键字来修饰一个类时,这个类就是抽象类 ​ 访问修饰符 abstract 类名 { ​ } 用abstract关键字来修饰一个方法时,这个方法就是抽象方法 ​ 访…

    Java 2023年6月7日
    092
  • java通过freemarker导出包含富文本图片的word文档

    废话不多说,进入正题! 本文重点在于:对富文本图片的导出(基础的freemarker+word模板导出这里不做详细解说哈) 参考文章:http://www.cnblogs.com/…

    Java 2023年6月7日
    0101
  • 方法习题记录

    1、1的阶乘到20的阶乘之和 /** * 1的阶乘到20的&#…

    Java 2023年6月6日
    080
  • 两个单链表相交的一系列问题

    给定两个单链表,单链表可能有环,也可能没有环,给定两个链表的头节点,判断是否相交,如果相交就返回相交的第一个结点,如果不相交就返回null 这道问题要求判断两个单链表是否相交,首先…

    Java 2023年6月7日
    0169
  • MySQL七:一文详解六大日志

    转载~ 日志一般分为逻辑日志与物理日志两类 「逻辑日志」:即执行过的事务中的sql语句,执行的sql语句(增删改) 「反向」的信息 「物理日志」: mysql 数据最终是保存在数据…

    Java 2023年6月8日
    0102
  • 7月8日 学习日记

    HashMap 通过扩容可以减少链表长度 当链表长度超过8时会扩容 当数组长度到达64且链表长度超过8个的时候会转化成红黑树 红黑树 左结点都是比结点小 右结点都是比结点大 先比较…

    Java 2023年6月6日
    089
  • 关于Mysql触发器

    关于:after触发器—是在记录操纵之后触发,是先完成数据的增删改,再触发,触发的语句晚于监视的增删改操作,无法影响前面的增删改动作before触发器—是在记录操纵之前触发,是先完…

    Java 2023年6月13日
    087
  • Springboot整合mybatisPlus+Swagger3.0

    1. SpringBoot+Mybatis+MybatisPlus com.baomidou mybatis-plus-boot-starter 3.5.1 org.mybatis…

    Java 2023年6月8日
    096
  • 如何从GitHub上下载部分自己需要的文件

    https://blog.csdn.net/weixin_43298370/article/details/105698073 Original: https://www.cnbl…

    Java 2023年6月5日
    0102
  • .Net Core下DllImport使用方法及扩展

    引言​ 在有时候的开发过程中,我们会遇到需要调用系统的API,不巧的是.Net Core可能没办法为我们提供相关的调用方式。那需要如何才能解决这个问题呢?​ 这时候我们就可能会考虑…

    Java 2023年6月15日
    097
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球