编程知识 cdmana.com

深入挖掘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对象,只能支持依赖注入,不支持依赖查找。

非Spring容器的外部化配置

简单来说,这类配置主要是我们在工作中常用的一些@Value相关配置注入。来看一段代码案例:

package org.idea.spring.bean.source.resource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.io.InputStream;

/** * @Author linhao * @Date created in 11:28 上午 2021/5/3 */
@Configuration
@PropertySource(value = "META-INF/default.properties",encoding = "UTF-8")
public class ResourceInjectDemo {
    

    @Value("${user.id}")
    private long id;

    @Value("${user.nickname}")
    private String name;

    @Value("${user.resource:classpath://default.properties}")
    private Resource resource;


    public static void main(String[] args) throws IOException {
    
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(ResourceInjectDemo.class);
        annotationConfigApplicationContext.refresh();
        ResourceInjectDemo resourceInjectDemo = annotationConfigApplicationContext.getBean(ResourceInjectDemo.class);
        System.out.println(resourceInjectDemo.id);
        System.out.println(resourceInjectDemo.name);
        String fileName = resourceInjectDemo.resource.getFilename();
        System.out.println(fileName);
        annotationConfigApplicationContext.close();
    }
}

配置文件:
在这里插入图片描述
上边的这段代码比较简单,主要就是针对一些外部化的资源加载进行配置。
如果需要避免注入属性为空的情况,可以在配置的时候加入冒号,代码截图如下:
在这里插入图片描述

最后小结

关于本篇文章着重介绍了Spring内部的三种依赖来源渠道,这里我们抽取几道经典问题来进行分析探讨。
依赖注入和依赖查找来源是否相同
不相同,依赖查找的来源只局限于Spring BeanDefinition和手动注入的Sington对象,但是对于Resolvable Dependency对象以及@Value这类外部化配置的对象并不支持。

当Spring容器初始化之后还能注册bean吗
可以的,这里我贴一段自己实践出来的代码供大家参考:

package org.idea.spring.bean.source;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.idea.spring.bean.beandefinitionbuilder.User;
/** * @Author linhao * @Date created in 4:44 下午 2021/5/3 */
public class AddBeanAfterRefreshDemo {
    



    public static void main(String[] args) {
    
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(AddBeanAfterRefreshDemo.class);
        annotationConfigApplicationContext.refresh();
        try {
    
            User user0 = (User) annotationConfigApplicationContext.getBean("user");
            System.out.println("user0 is "+user0);
        }catch (Exception b){
    
            b.printStackTrace();
        }
        System.out.println("启动后手动注入bean对象");

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        beanDefinitionBuilder.setScope("prototype");
        beanDefinitionBuilder
                .addPropertyValue("id",2)
                .addPropertyValue("name","idea");
        annotationConfigApplicationContext.registerBeanDefinition("user",beanDefinitionBuilder.getBeanDefinition());
        User user1 = (User) annotationConfigApplicationContext.getBean("user");
        User user2 = annotationConfigApplicationContext.getBean(User.class);

        BeanDefinition beanDefinition = annotationConfigApplicationContext.getBeanDefinition("user");
        System.out.println(beanDefinition.getScope());
        System.out.println(user1==user2);
        annotationConfigApplicationContext.close();
    }
}

通过这段代码实践发现其实关于BeanDefinition定义的bean在容器进行初始化之后依旧可以动态注入,不过这里有个点需要注意下:
如果将案例代码稍微修改:

package org.idea.spring.bean.source;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.idea.spring.bean.beandefinitionbuilder.User;
/** * @Author linhao * @Date created in 4:44 下午 2021/5/3 */
public class AddBeanAfterRefreshDemo {
    


    public static void main(String[] args) {
    
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(AddBeanAfterRefreshDemo.class);
        annotationConfigApplicationContext.refresh();
        try {
    
            User user0 = (User) annotationConfigApplicationContext.getBean(User.class);
            System.out.println("user0 is "+user0);
        }catch (Exception b){
    
            b.printStackTrace();
        }
        System.out.println("启动后手动注入bean对象");

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        beanDefinitionBuilder.setScope("prototype");
        beanDefinitionBuilder
                .addPropertyValue("id",2)
                .addPropertyValue("name","idea");
        annotationConfigApplicationContext.registerBeanDefinition("user",beanDefinitionBuilder.getBeanDefinition());
        User user1 = (User) annotationConfigApplicationContext.getBean(User.class);
        User user2 = annotationConfigApplicationContext.getBean(User.class);

        BeanDefinition beanDefinition = annotationConfigApplicationContext.getBeanDefinition("user");
        System.out.println(beanDefinition.getScope());
        System.out.println(user1==user2);
        annotationConfigApplicationContext.close();
    }
}

修改后的代码就会在运行的时候抛出异常。这是因为在Spring容器底层会有一个Map专门记录不同的beanClass类型对应不同的beanName集合:

代码位于:

org.springframework.beans.factory.support.DefaultListableBeanFactory

/** Map of singleton and non-singleton bean names, keyed by dependency type. */
  private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);

如果第一次尝试通过类型获取Bean失败了的话,后续的多次尝试获取也会出现失败。
在实际工作中我们可能比较少会遇到需要在容器初始化之后再去手动注入Bean对象的情况。如果有需要的时候记得重复调用Bean对象的时候借助名称查询,而不是类型查找。

版权声明
本文为[Danny_idea]所创,转载请带上原文链接,感谢
https://blog.csdn.net/Danny_idea/article/details/116423251

Scroll to Top