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

简介: 讲解 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,后续会有更多实战、源码、架构干货分享!

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


目录
相关文章
|
17天前
|
小程序 JavaScript Java
高校宿舍信息|基于Spring Boot的高校宿舍信息管理系统的设计与实现(源码+数据库+文档)
高校宿舍信息|基于Spring Boot的高校宿舍信息管理系统的设计与实现(源码+数据库+文档)
22 0
|
4天前
|
Java Spring
【JavaEE进阶】 Spring AOP源码简单剖析
【JavaEE进阶】 Spring AOP源码简单剖析
|
9天前
|
存储 Java 数据库
Spring的使用-Bean对象的储存和获取/Bea对象的作用域与生命周期
Spring的使用-Bean对象的储存和获取/Bea对象的作用域与生命周期
|
14天前
|
Java Spring 容器
Spring注解开发,bean的作用范围及生命周期、Spring注解开发依赖注入
Spring注解开发,bean的作用范围及生命周期、Spring注解开发依赖注入
29 1
Spring注解开发,bean的作用范围及生命周期、Spring注解开发依赖注入
|
14天前
|
Java Spring 容器
Spring核心概念、IoC和DI的认识、Spring中bean的配置及实例化、bean的生命周期
Spring核心概念、IoC和DI的认识、Spring中bean的配置及实例化、bean的生命周期
35 0
|
15天前
|
XML Java 数据格式
Spring框架学习 -- Bean的生命周期和作用域
Spring框架学习 -- Bean的生命周期和作用域
18 2
|
17天前
|
小程序 JavaScript Java
小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)
小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)
24 0
小程序商城|基于Spring Boot的智能小程序商城的设计与实现(源码+数据库+文档)
|
17天前
|
安全 JavaScript Java
在线问卷调查|基于Spring Boot的在线问卷调查系统的设计与实现(源码+数据库+文档)
在线问卷调查|基于Spring Boot的在线问卷调查系统的设计与实现(源码+数据库+文档)
28 0
|
23天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
72 0
|
23天前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
157 0