深入挖掘Spring系列 -- 依赖的来源(上)

简介: 深入挖掘Spring系列 -- 依赖的来源(上)

前言


上一篇文章里面我们着重介绍了关于Spring容器内部的依赖注入。介绍了Spring内部的依赖注入方式:


手动注入


自动注入


针对这两种模式的注入又细分了下其中的途径,具体为:


手动模式注入的方法


XML模式注入,Java注解方式注入,API方式代码注入。


自动注入的方式


通过xml配置中的Autowiring 项,自动帮我们绑定注入。


手动注入在实际工作中使用的方式有:setter注入,构造器注入,字段注入,方法注入,回调注入 这么几种渠道。同时也介绍了一些特殊数据结构,例如枚举,集合,资源文件的依赖注入。最后简单总结了Spring内部Bean的注入来源有哪些渠道。


本文主要延续上一篇文章的篇末,重点分析Spring内部的Bean注入来源有哪些。


依赖的来源


这里我将Spring容器内部的依赖主要划分为了以下3个大类:


Spring BeanDefinition构建的对象

手动注册的Singleton对象

Resolvable Dependency


Spring BeanDefinition构建对象


用户自定义的Spring BeanDefinition 对象


这类型的Bean比较好理解,就是我们经常在框架中通过xml或者注解注册到容器内部的Bean对象,代码案例如下:


/* 省略其他代码 /
@Resource
private PersonHolder personHolder;
/
省略其他代码 */


这类型的Bean需要根据用户需求去进行手动注入容器,最终的底层是借助Spring容器内部的BeanDefinition进行对象的注册:


/*
  * 代码位置:org.springframework.beans.factory.support.BeanDefinitionRegistry
  */
  /**
   * Register a new bean definition with this registry.
   * Must support RootBeanDefinition and ChildBeanDefinition.
   * @param beanName the name of the bean instance to register
   * @param beanDefinition definition of the bean instance to register
   * @throws BeanDefinitionStoreException if the BeanDefinition is invalid
   * @throws BeanDefinitionOverrideException if there is already a BeanDefinition
   * for the specified bean name and we are not allowed to override it
   * @see GenericBeanDefinition
   * @see RootBeanDefinition
   * @see ChildBeanDefinition
   */
  void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException;
复制代码


ps:BeanDefinition更多是对于Bean的一个定义,用于描述一个对象实例,构造函数参数值,属性字段值等等。


非用户定义的Spring BeanDefinition 对象


这部分的Bean主要是Spring容器在进行初始化过程中注册的,具体对象整理了一部分自己所了解的,贴在下方供大家参考:(可能会有些不全面,自己看的Spring源代码是5.2.3版本)


/**
         * 处理Spring配置对象
         */
        ConfigurationClassPostProcessor configurationClassPostProcessor;
        /**
         * 处理Spring中Bean的一些@Autowired @Value 字段值注入
         */
        AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor;
        /**
         * 处理一些通用注解,例如说@PostContruct @PreDestroy JSR-250注解 等
         */
        CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor;
        /**
         * 处理标注@EventListener的Spring容器事件监听方法
         */
        EventListenerMethodProcessor eventListenerMethodProcessor;
复制代码


上边介绍的这部分对象是在


org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)里面进行注入的。


这里面可以看到基本都是一些Bean的后置处理器相关对象,除了这部分对象之外,Spring容器中还存储了一些单例的资源管理对象,例如说


enviorment,properties 相关对象。


** 非用户定义Spring BeanDefinition 注册时机**


容器启动前注入


上边提到的BeanPostProcessor相关对象是在容器还未启动环节就已经注册的,源码阅读思路:


AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); 
复制代码


根据以下调用栈可以看到


AnnotationConfigUtils#registerAnnotationConfigProcessors的调用栈。


registerAnnotationConfigProcessors:151, AnnotationConfigUtils (org.springframework.context.annotation), AnnotationConfigUtils.java
registerAnnotationConfigProcessors:137, AnnotationConfigUtils (org.springframework.context.annotation), AnnotationConfigUtils.java
<init>:88, AnnotatedBeanDefinitionReader (org.springframework.context.annotation), AnnotatedBeanDefinitionReader.java
<init>:71, AnnotatedBeanDefinitionReader (org.springframework.context.annotation), AnnotatedBeanDefinitionReader.java
<init>:66, AnnotationConfigApplicationContext (org.springframework.context.annotation), AnnotationConfigApplicationContext.java
//这是我自己的测试代码名称
main:31, PersonBeanScopeLookUpDemo (org.idea.spring.bean.aware), PersonBeanScopeLookUpDemo.java
复制代码


实际上在容器启动过程中


org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors函数会会去调用这些PostProcessor的相关方法。


容器启动时注入


Spring容器启动的时候会调用一个refresh的api,大致如下所示:


@Override
  public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
      /**
        省略其他代码...
       **/
      // 代码段1
      prepareBeanFactory(beanFactory);
      /**
        省略其他代码...
       **/
      try {
       /** 省略其他代码... **/
      }
      catch (BeansException ex) {
        /** 省略其他代码... **/
      }
      finally {
       /** 省略其他代码... **/       
      }
    }
  }
复制代码


从代码段1内部debug进去后可以看到prepareBeanFactory中初始化了资源加载的Bean对象信息:


网络异常,图片无法展示
|


单例对象


这里我特意将单例对象抽取出来,因为它们的存放比较特别。


单例对象通过SingletonBeanRegistry.registerSingleton来进行注入。来自于Spring容器的外部,通常是我们用户自己定义,这类Bean不一定是POJO。


单例对象的手动注册


这里我贴出一份自己实践的代码案例更好地帮助大家理解单例对象的注册使用方式:(通过实战理解程序背后的原理会让人认识更加深刻)


package org.idea.spring.bean.source;
import org.idea.spring.bean.scope.User;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * 单例对象的注册
 *
 * @Author linhao
 * @Date created in 9:56 上午 2021/5/3
 */
public class SingletonBeanRegister {
public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(SingletonBeanRegister.class);
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) annotationConfigApplicationContext.getBeanFactory();
if(defaultListableBeanFactory!=null) {
            defaultListableBeanFactory.registerSingleton("singletonUser", new User(1001,"idea"));
        }
        annotationConfigApplicationContext.refresh();
        User user = (User) defaultListableBeanFactory.getSingleton("singletonUser");
        User user2 = (User) defaultListableBeanFactory.getSingleton("singletonUser");
        System.out.println(user == user2);
    }
}
复制代码


通过实际的这段代码案例分析后发现,输出的结果是true。


在代码中调用registerSingleton的时候可以看到这么一段源代码:


org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerSingleton
复制代码


网络异常,图片无法展示
|


图中我用红色边框框住的部分是对单例对象做取出和放入的两个不同操作,虽然说存放对象的是一个ConcurrentHashMap,但是由于这段函数是一个二元操作,因此这里加入了锁。


同样的,在addSingleton中因为还有可能是有其他地方在进行调用,为了避免线程安全问题,所以这里同样也是加锁。内外层都是对同一个对象加锁,根据锁的可重入性判断,这里面的调用不会有影响。


网络异常,图片无法展示
|


Singleton对象有什么不同之处


注意,这里我用Singleton对象来进行描述,为了将其和Scope定义的单例对象进行区分。


在Spring容器中通过手动注册的Singleton对象的是没有生命周期这种管理概念的,同样也没有延迟初始化的实现方式。先通过一个简单的代码案例来认识这一特性:


还是基于上方的代码做些小调整,给User对象加入一个接口的管理:


package org.idea.spring.bean.scope;
import org.springframework.beans.factory.InitializingBean;
/**
 * @Author linhao
 * @Date created in 1:47 下午 2021/5/2
 */
public class User implements InitializingBean {
private long time;
private String username;
/** 省略部分代码 **/
@Override
public void afterPropertiesSet() throws Exception {
        System.out.println("==== afterPropertiesSet ====");
    }
}
复制代码


执行的测试代码依旧不变:


网络异常,图片无法展示
|


执行结果会发现 afterPropertiesSet 函数并没有被回调触发。


这是因为这种方式注册的时候,并不会将User对象写入到BeanDefinition中,所以在容器启动中的回调部分没有搜索到匹配的对象,因此最终没有形成回调。


但是这种方法我个人觉得有些设计上的鸡肋,因为在工作中我们通常都会定义一些单例的配置类,基于其对一些初始化回调接口来做配置定义和赋值操作。


因此我们通常在工作中使用的单例对象都是基于@Scope注解来进行声明。


Scope的单例对象生命周期由spring容器管理,而且有多种配置方式,比如通过@Bean或者xml,可以支持对于生命周期中各种回调的处理。而手动注入的Singleton对象只能通过SingletionBeanRegister注入,生命周期不归Spring容器管理,这是一个细节上的区别。两者都可以用于依赖注入和依赖查找,个人更加推崇第二者。


Resolvable Dependency对象


关于Resolvable Dependency类型对象的注入这里我通过一组代码案例来进行介绍:


package org.idea.spring.bean.source;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.annotation.PostConstruct;
/**
 * @Author linhao
 * @Date created in 11:47 上午 2021/5/3
 */
public class ResolvableDependencySourceDemo  {
    @Autowired
    private String value;
    @PostConstruct
    public void init()  {
        System.out.println(this.value);
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(ResolvableDependencySourceDemo.class);
        annotationConfigApplicationContext.addBeanFactoryPostProcessor(beanFactory -> {
            beanFactory.registerResolvableDependency(String.class,"hello world");
        });
        annotationConfigApplicationContext.refresh();
        String value = annotationConfigApplicationContext.getBean(String.class);
        System.out.println(value);
        annotationConfigApplicationContext.close();
    }
}
复制代码


这里面你会看到如下结果:


网络异常,图片无法展示
|


这里明显能看到对应的String对象已经被注册到了Spring容器当中,但是在进行依赖查找的时候,并没有发现String对象的存在,这是因为Spring内部对ResolvableDependency的定义是一个游离类型的对象,只能通过依赖注入获取,不能通过依赖查找获取。类似的Bean来源有很多,类似的bean注册还在Spring的启动函数refresh中有所体现,下边是源代码地址:


org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory
复制代码


网络异常,图片无法展示
|


这里面会发现,注入我们常用的ApplicationContext,BeanFactory等对象都是属于ResolvableDependency对象,只能支持依赖注入,不支持依赖查找。

目录
相关文章
|
2月前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
40 4
|
4月前
|
缓存 Java 开发者
Spring循环依赖问题之Spring循环依赖如何解决
Spring循环依赖问题之Spring循环依赖如何解决
|
4月前
|
缓存 Java Spring
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
|
4月前
|
Java Spring
Spring循环依赖问题之构造器内的循环依赖如何解决
Spring循环依赖问题之构造器内的循环依赖如何解决
|
3月前
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
|
4月前
|
JavaScript Java Maven
理解固化的Maven依赖:spring-boot-starter-parent 与 spring-boot-dependencies
理解固化的Maven依赖:spring-boot-starter-parent 与 spring-boot-dependencies
1899 1
|
4月前
|
Java Spring 容器
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
|
4月前
|
存储 缓存 Java
Spring循环依赖问题之循环依赖异常如何解决
Spring循环依赖问题之循环依赖异常如何解决
|
4月前
|
XML Java 数据格式
循环依赖问题之创建Bean的过程中发生异常,Spring会如何处理
循环依赖问题之创建Bean的过程中发生异常,Spring会如何处理
|
4月前
|
Java Maven Spring
Spring Boot中的依赖管理策略
Spring Boot中的依赖管理策略