讲解 Spring 实例化的不同方式及相关生命周期源码剖析(三)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 讲解 Spring 实例化的不同方式及相关生命周期源码剖析(三)

2、实例工厂类

public class PersonInstanceFactory {
    public Person getPerson(String name){
        Person person = new Person();
        person.setId(1);
        person.setName(name);
        return person;
    }
}

3、静态工厂类

public class PersonStaticFactory {
    public static Person getPerson(int age) {
        System.out.println("getPerson >>>> age");
        return new Person();
    }
    public static Person getPerson(String name) {
        System.out.println("getPerson >>>> name");
        Person person = new Person();
        person.setId(1);
        person.setName(name);
        return person;
    }
    public static Person getPerson(String name, int id) {
        Person person = new Person();
        person.setId(1);
        person.setName(name);
        return person;
    }
}

4、 factoryMethod.xml 配置文件,让 Spring 容器能够解析到它们之间的依赖关系

<bean id="person" class="com.vnjohn.factoryMethod.PersonStaticFactory" factory-method="getPerson">
  <constructor-arg value="123" type="int"/>
</bean>
<bean id="personInstanceFactory" class="com.vnjohn.factoryMethod.PersonInstanceFactory"/>
<bean id="person2" class="com.vnjohn.factoryMethod.Person" factory-bean="personInstanceFactory" factory-method="getPerson">
  <constructor-arg value="vnjohn"/>
</bean>

constructor-arg:可以为方法指定参数和类型,如果未指定 int 类型的话,那么就会调用类型为 String 参数的方法,因为它会优先按照实际的参数类型来进行调用的

静态工厂直接指定方法即可,class 需指定静态工厂类

实例工厂需要指定工厂实例对象,同时需要指定方法,class 为工厂需要创建的类

5、测试基类

public class TestFactoryMethod {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("factoryMethod.xml");
        Person person = ac.getBean("person", Person.class);
        System.out.println(person);
        Person person2 = ac.getBean("person2", Person.class);
        System.out.println(person2);
    }
}

构造方法注入

通过反射获取实例,主要是先获取到 Bean 构造器集合,然后选择不同的构造函数(有参、无参)进行实例化,可以通过配置文件来配置构造参数,也可以在构造函数上定义 @Autowire 注解

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
  // 确认需要创建的 bean 实例类可以实例化
  Class<?> beanClass = resolveBeanClass(mbd, beanName);
  // 确保 class 不为空,并且访问权限是 public
  if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
        "Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
  }
  // 判断当前 beanDefinition 中是否包含实例供应器,此处相当于一个回调方法,利用回调方法来创建 bean
  Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
  if (instanceSupplier != null) {
    return obtainFromSupplier(instanceSupplier, beanName);
  }
  // 如果工厂方法不为空则使用工厂方法初始化策略
  if (mbd.getFactoryMethodName() != null) {
    return instantiateUsingFactoryMethod(beanName, mbd, args);
  }
  // 一个类可能有多个构造器,所以 Spring 根据参数个数、类型确定需要调用的构造器
  // 在使用构造器创建实例后,Spring 会将解析过后确定下来的构造器或工厂方法保存在缓存中,避免再次创建相同 bean 时再次解析
  // 标记下,防止重复创建同一个bean
  boolean resolved = false;
  // 是否需要自动装配
  boolean autowireNecessary = false;
  // 如果没有参数
  if (args == null) {
    synchronized (mbd.constructorArgumentLock) {
      // 一个类可能由多个构造函数,所以需要根据配置文件中配置的参数或传入的参数来确定最终调用的构造函数
      // 因为判断过程会比较,所以 Spring 会将解析、确定好的构造函数缓存到 BeanDefinition#resolvedConstructorOrFactoryMethod 字段中
      // 在下次创建相同时直接从RootBeanDefinition中的属性resolvedConstructorOrFactoryMethod缓存的值获取,避免再次解析
      if (mbd.resolvedConstructorOrFactoryMethod != null) {
        resolved = true;
        autowireNecessary = mbd.constructorArgumentsResolved;
      }
    }
  }
  // 有构造参数的或者工厂方法
  if (resolved) {
    // 构造器有参数
    if (autowireNecessary) {
      // 构造函数自动注入
      return autowireConstructor(beanName, mbd, null, null);
    }
    else {
      // 使用默认构造函数构造
      return instantiateBean(beanName, mbd);
    }
  }
  // Bean 后置处理器中为自动装配寻找构造方法, 有且仅有一个有参构造或者有且仅有 @Autowired 注解构造
  Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
  // 以下情况符合其一即可进入
  // 1、存在可选构造方法
  // 2、自动装配模型为构造函数自动装配
  // 3、给 BeanDefinition 中设置了构造参数值
  // 4、有参与构造函数参数列表的参数
  if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
      mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
    return autowireConstructor(beanName, mbd, ctors, args);
  }
  // 找出最合适的默认构造方法
  ctors = mbd.getPreferredConstructors();
  if (ctors != null) {
    // 构造函数自动注入
    return autowireConstructor(beanName, mbd, ctors, null);
  }
  // 使用默认无参构造函数创建对象,如果没有无参构造且存在多个有参构造且没有 @AutoWired 注解构造,会报错
  return instantiateBean(beanName, mbd);
}
  1. 首先从 BeanDefinition 缓存中读取当前 Bean 是否存在,若存在直接取用进行实例化,无须再次进行解析
  2. autowireConstructor:通过构造函数注入,若没有传入构造器时,就会筛选类中所有的构造器候选者集合
  3. 一般传入的构造器不为空时,都是通过 @Autowire 注解标注的构造方法,第一次加载的 Bean 都会经过 determineConstructorsFromBeanPostProcessors 方法去扫描,然后存入到缓存中,后续就可以直接从 autowireConstructor 直接获取,无法再次进行扫描.

First:初始化 Bean,进行包装,将一些转换器、属性编辑器注入 BeanDefinition 中,然后从缓存中取出当前 bean,是否存在构造函数、参数,存在就直接实例化

Second:判断传入的构造器是否为空,为空的话就通过反射获取到所有的构造方法, 若构造器只有一个并且不需要参数【一般默认无参构造直接是调用->instantiateBean 方法,这里主要是通过 @Autowire 注解标注了无参构造方法,直接就可以实例化】

Thirty:解析配置文件的构造器参数,再对所有构造器进行排序(1-访问修饰符、2-参数个数多少)遍历构造器集合,筛选出更合适的一个构造器进行实例化,若遇到了冲突的构造函数,按照权重值匹配更合适的

Four:将当前匹配到的构造器存入缓存中:ConstructorResolver.ArgumentsHolder#storeCache

Five:按匹配到的构造器、参数进行实例化操作

determineConstructorsFromBeanPostProcessors:实现于 SmartInstantiationAwareBeanPostProcessor 接口的类才会有此方法实现,若没有开启注解扫描,此处会被忽略,具体实现类->AutowiredAnnotationBeanPostProcessor,由它来完成对构造函数的解析及推断,以下是其方法 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors 处理的流程图:

  1. @Lookup 注解扫描,无论其是否含有 @Lookup 修饰的方法,过滤完成以后都会放入到集合中,证明此 Bean 已经完成了 @Lookup 检查
  2. 通过注解扫描,获取构造器集合会有以下几种情况:

1、若有多个 @Autowired,required 属性为 true,不管有没有默认构造方法,都会报异常:Invalid autowire-marked constructor: public com.vnjohn.reflection.Person(int,java.lang.String). Found constructor with 'required' Autowired annotation already: public com.vnjohn.reflection.Person(int)

2、若只有一个 @Autowired,required 为 false,没有默认构造方法,会报警告:Inconsistent constructor declaration on bean with name ‘person’: single autowire-marked constructor flagged as optional - this constructor is effectively required since there is no default constructor to fall back to: public com.vnjohn.reflection.Person(int)

3、若没有 @Autowired 注解,定义了两个及以上有参数的构造方法,没有无参构造方法,就会报错:No default constructor found; nested exception is java.lang.NoSuchMethodException: com.vnjohn.reflection.Person.<init>()

4、其他情况都可以,但首先以有 @Autowired 注解修饰的构造方法优先,然后才是默认构造方法

  1. 此方法处理完以后,将所有的满足条件的构造器选出以后,又会调用 autowireConstructor 方法继续去进行实例化操作

实例化策略

在 Spring 反射注入方式,要先区分它对应的实例化策略,分为普通对象实例化、动态代理对象实例化,动态代理对象实例化类 继承 普通对象实例化类

在 Spring 中,默认的实例化策略实现类是 CGLIB

// bean的生成策略,默认是 CGLIB
private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
• 1
• 2

处理 FactoryMethod、有参构造、无参构造的实例化使用的是 SimpleInstantiationStrategy 简单对象实例化策略类,CGLIB 继承于 Simple,所有 CGLIB 类自然包含了 Simple 可以处理的逻辑,CGLIB 主要处理的就是带有 MethodOverrides 信息的

  • 首先生成 CGLIB 代理子类
  • 最后再设置 Lookup、Replace 拦截器回调

若通过有参构造进行实例化时,会提前将 Bean 进行包装处理,将一些转换器、属性编辑器注入 BeanDefinition 中,但处理无参构造时,是先将其实例化完成,然后再进行设置;两者都是为了对 Bean 进行基础信息的封装,无其他的特殊性

MethodOverrides

MethodOverrides 是 AbstractBeanDefinition 的一个集合属性,它主要是为了记录 lookup-method、replace-method 元素的信息,它在 Spring 几处会进行解析,同时对有修饰此属性的对象,会为它生成代理对象

LookupMethod

主要用来解决单例引用原型 Bean 的问题: 如果未加任何处理的话,单例 Bean 对象中会一直引用最先开始创建的原型 bean 实例,后面原型 Bean 重新获取后单例内的属性引用并不会发生任何的改变,那么我们的原型 Bean 的存在就没有意义了.

这样就违背了原型模式的初衷,没有作任何处理的话,原型 Bean 会随着单例 Bean 引用一并存入到一级缓存中,此时就需要 LookupMethod 了,可以用 loopup-method 标签或 @Lookup 注解来解决此问题

下面通过一个实际的案例来介绍 Spring 中如何使用 lookup-method 标签来解决此问题

1、单例 Bean>Apple,引用原型 Bean>Banana

public class Apple {
  // 普通方式进行引用就会出现原型引用不会进行改变的问题
    private Banana banana;
    public Apple() {
        System.out.println("I got a fresh apple");
    }
    public Banana getBanana() {
        return banana;
    }
    public void setBanana(Banana banana) {
        this.banana = banana;
    }
}
public class Banana {
    public Banana() {
        System.out.println("I got a  fresh bananer");
    }
}

2、methodOverride.xml 配置文件,处理它们之间的依赖关系

<bean id="apple" class="com.vnjohn.methodOverrides.lookup.Apple">
    <!-- 当我们手动进行 getBean 时会去调用 lookupInterceptor 类进行创建 banana,所以通过 lookup 能够达到我们想要的效果 -->
    <lookup-method name="getBanana" bean="banana"/>
</bean>
<bean id="banana" class="com.vnjohn.methodOverrides.lookup.Banana" scope="prototype"/>

3、测试基类

public class TestMethodOverride {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("methodOverride.xml");
        Apple bean1 = ac.getBean(Apple.class);
        System.out.println(bean1);
        Apple bean2 = ac.getBean(Apple.class);
        System.out.println(bean2);
        System.out.println(bean1.getBanana());
        System.out.println(bean1.getBanana());
    }
}

以上的方式可以解决我们在单例 Bean 下引用原型 Bean 的问题,还有其他的方式可以进行处理,比如:调用我们具体的对象时,通过方法手动的去进行 getBean【在原型模式下调用时每次都会创建新的实例】这样也可以解决,但我们目的就是采用 Spring 方式解决该问题.

下面我们来看具体的 LookupOverrideMethodInterceptor 拦截器是如何处理的~

private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
  private final BeanFactory owner;
  public LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
    super(beanDefinition);
    this.owner = owner;
  }
  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
    // 获取到 lookup-method 标签信息:方法名、类名
    LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
    Assert.state(lo != null, "LookupOverride not found");
    Object[] argsToUse = (args.length > 0 ? args : null);  // if no-arg, don't insist on args at all
    // owner 通过 beanFactory 调用 getBean 进行实例化,如果是原型模式每次获取的都是新的实例
    if (StringUtils.hasText(lo.getBeanName())) {
      return (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) :
              this.owner.getBean(lo.getBeanName()));
    }
    else {
      return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) :
              this.owner.getBean(method.getReturnType()));
    }
  }
}

ReplaceMethod

主要是用来替换方法体及返回值,与 LookupMethod 执行逻辑是相似的,都是需要 CGLIB 子类后通过拦截器进行处理的

下面通过一个实际的案例来介绍 Spring 中如何使用 replace-method 标签

1、原始类处理逻辑

public class OriginalDog {
  public void sayHello() {
    System.out.println("Hello,I am a black dog...");
  }
  public void sayHello(String name) {
    System.out.println("Hello,I am a black dog, my name is " + name);
  }
}

2、替换类处理逻辑

public class ReplaceDog implements MethodReplacer {
  @Override
  public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
    System.out.println("Hello, I am a white dog...");
    Arrays.stream(args).forEach(str -> System.out.println("参数:" + str));
    return obj;
  }
}

3、replaceMethodOverride.xml 配置文件,处理它们之间的依赖关系

<bean id="dogReplaceMethod" class="com.vnjohn.methodOverrides.replace.ReplaceDog"/>
<bean id="originalDogReplaceMethod" class="com.vnjohn.methodOverrides.replace.OriginalDog">
  <replaced-method name="sayHello" replacer="dogReplaceMethod">
    <arg-type match="java.lang.String"/>
  </replaced-method>
</bean>

4、测试基类

public class TestReplaceMethodOverride {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("replaceMethodOverride.xml");
        OriginalDog originalDog = ac.getBean(OriginalDog.class);
        originalDog.sayHello("vnjohn");
    }
}

只是处理 lookup-method 的类是 LookupOverrideMethodInterceptor,处理 replace-method 的类是 ReplaceOverrideMethodInterceptor,都是通过 CGLIB 子类拦截器来进行实现,下面我们来看具体的 ReplaceOverrideMethodInterceptor 拦截器是如何处理的~

private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
  private final BeanFactory owner;
  public ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
    super(beanDefinition);
    this.owner = owner;
  }
  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
    ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
    Assert.state(ro != null, "ReplaceOverride not found");
    // 获取到自定义的 replacer 替换实现类后进行调用
    MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
    return mr.reimplement(obj, method, args);
  }
}

总结

该篇博文,先介绍了 BeanDefinition 接口以及与它子类之间的关系,主要分析的是在 Spring 中不同的实例化方式,如:FactoryBean、FactoryMethod、Supplier、自定义 BPP 接口实现类、反射注入,反射注入是 Spring 中比较核心的,它一般可以通过无参构造、有参构造去注入 Bean 实例;最后,MethodOverrides 此属性虽然在工作中很少应用,但我们应该要知道此【单例 Bean 引用原型 Bean 问题】问题的解决方案,希望博文能够帮助你快速的理解 Spring 实例化过程~

关于 Spring 生命周期源码剖析,目前只介绍了它的实例化过程,后面还有填充属性、初始化 Bean 源码流程未作介绍 TODO,请敬请期待

稍微总结一下 Spring 正常创建 Bean 整个生命周期过程如下:

  1. 实例化 bean 对象:getBean->doGetBean->createBean->doCreateBean->createBeanInstance
  2. 填充对象属性
  3. 检查 Aware 相关接口并设置相关依赖
  4. BeanPostProcessors 初始化前置处理方法 postProcessBeforeInitialization
  5. 检查是否有实现 InitializingBean 以决定是否调用 afterPropertiesSet 方法
  6. 检查是否配置有自定义的 init-method,调用执行
  7. BeanPostProcessors 初始化后置处理方法 postProcessAfterInitialization
  8. 实现 DestructionAwareBeanPostProcessor 接口的类,销毁前调用方法 postProcessBeforeDestruction
  9. 检查是否有实现 DisposableBean 以决定是否调用 destroy 方法
  10. 检查是否配置有自定义的 destroy-method,调用执行

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!


目录
相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
14 2
|
19天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
9天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
34 9
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
107 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
72 1
|
1月前
|
设计模式 JavaScript Java
Spring 事件监听机制源码
Spring 提供了事件发布订阅机制,广泛应用于项目中。本文介绍了如何通过自定义事件类、订阅类和发布类实现这一机制,并展示了如何监听 SpringBoot 启动过程中的多个事件(如 `ApplicationStartingEvent`、`ApplicationEnvironmentPreparedEvent` 等)。通过掌握这些事件,可以更好地理解 SpringBoot 的启动流程。示例代码展示了从事件发布到接收的完整过程。
|
1月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
29 1
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
46 0
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
79 0