spring-core-9-93 | Spring Bean实例化前阶段:Bean的实例化能否被绕开?

Spring Bean 实例化前阶段

通过上一节, 我们了解到了 BeanDefinition中关于类的一些信息从文本的形式转换为实在的Class对象,这个过程称为BeanDefinition或者Bean的 ClassLoading,也就是 类加载。

接下来我们继续会讨论关于这个类的信息是如何变为一个Bean的示例,也是Spring Bean 的实例化阶段,这个阶段会分为3个阶段,前阶段、中阶段和后阶段,本节我们讨论的是他的前阶段.

非主流生命周期- Bean 实例化前阶段

事实上 Bean 实例化前阶段 其实是一种非主流的阶段,这个阶段的操作基本上很少有人去直接去涉猎,在日常工作中也很少碰到.

InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

   /**
    * Apply this BeanPostProcessor <i>before the target bean gets instantiated</i>.
    * The returned bean object may be a proxy to use instead of the target bean,
    * effectively suppressing default instantiation of the target bean.
    * <p>If a non-null object is returned by this method, the bean creation process
    * will be short-circuited. The only further processing applied is the
    * {@link #postProcessAfterInitialization} callback from the configured
    * {@link BeanPostProcessor BeanPostProcessors}.
    * <p>This callback will be applied to bean definitions with their bean class,
    * as well as to factory-method definitions in which case the returned bean type
    * will be passed in here.
    * <p>Post-processors may implement the extended
    * {@link SmartInstantiationAwareBeanPostProcessor} interface in order
    * to predict the type of the bean object that they are going to return here.
    * <p>The default implementation returns {@code null}.
    * @param beanClass the class of the bean to be instantiated
    * @param beanName the name of the bean
    * @return the bean object to expose instead of a default instance of the target bean,
    * or {@code null} to proceed with default instantiation
    * @throws org.springframework.beans.BeansException in case of errors
    * @see #postProcessAfterInstantiation
    * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getBeanClass()
    * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getFactoryMethodName()
    */
   @Nullable
   default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
      return null;
   }
   
   .....省略以下
   
 }

那么这个阶段有什么操作? 这个阶段会打破我们既有对Spring Bean 实例化的一个认知,我们看到InstantiationAwareBeanPostProcessor接口,这个接口是扩展了 BeanPostProcessor接口,是个子接口,它里面一个方法叫 postProcessBeforeInstantiation

按照注释意思它是
一个在实例化之前的一个前置操作,可以获取到要实例化的Bean的代理对象去替代目标的Bean, 可以有效的防止容器默认对目标Bean进行实例化.
如果return null 的话, 还是进行默认初始化, 也就还是Spring容器来进行实例化.

这个操作会打破spring默认对Bean的注册和实例化流程,这里可以演示一下,所以还是回到idea.

代码示例

org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

org.geekbang.thinking.in.spring.bean.lifecycle.BeanInstantiationLifecycleDemo
org.geekbang.thinking.in.spring.bean.lifecycle.MyInstantiationAwareBeanPostProcessor

示例类编写

创建一个例子来进行操作,

BeanInstantiationLifecycleDemo 是运行的主类, 大部分复制之前主类的代码

public class BeanInstantiationLifecycleDemo {

    public static void main(String[] args) {
        executeBeanFactory();
    }

    private static void executeBeanFactory() {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 方法一:添加 BeanPostProcessor 实现 MyInstantiationAwareBeanPostProcessor
        // beanFactory.addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor());
        // 方法二:将 MyInstantiationAwareBeanPostProcessor 作为 Bean 注册
        // 基于 XML 资源 BeanDefinitionReader 实现
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String[] locations = {"META-INF/dependency-lookup-context.xml", "META-INF/bean-constructor-dependency-injection.xml"};
        int beanNumbers = beanDefinitionReader.loadBeanDefinitions(locations);
        System.out.println("已加载 BeanDefinition 数量:" + beanNumbers);
        // 通过 Bean Id 和类型进行依赖查找
        User user = beanFactory.getBean("user", User.class);
        System.out.println(user);

        User superUser = beanFactory.getBean("superUser", User.class);
        System.out.println(superUser);

        // 构造器注入按照类型注入,resolveDependency
        UserHolder userHolder = beanFactory.getBean("userHolder", UserHolder.class);
        System.out.println(userHolder);
    }


}

MyInstantiationAwareBeanPostProcessor,是 InstantiationAwareBeanPostProcessor 接口的实现类

/**
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 * @since
 */
class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

    /**
     * 93 | Spring Bean实例化前阶段:Bean的实例化能否被绕开?
     *
     * @param beanClass
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (ObjectUtils.nullSafeEquals("superUser", beanName) && SuperUser.class.equals(beanClass)) {
            // 把配置完成 superUser Bean 覆盖
            return new SuperUser();
        }
        return null; // 保持 Spring IoC 容器的实例化操作
    }
    ....
}

InstantiationAwareBeanPostProcessor 的实现方式

InstantiationAwareBeanPostProcessor的实现方式其实有 两种, 我们可以看到从 Spring 5 之后,它实际上是用了java 8,因此接口里面可以提供默认实现这种方式,可以看到接口确实也有默认的实现代码.

image.png

我们可以直接利用接口来进行实现,因此这个操作可以更简化,只实现 postProcessBeforeInstantiation 方法 就够了。

如果是 Spring 5 之前的版本它会有个Adapter的实现,

org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter

package org.springframework.beans.factory.config;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;

/**
 * Adapter that implements all methods on {@link SmartInstantiationAwareBeanPostProcessor}
 * as no-ops, which will not change normal processing of each bean instantiated
 * by the container. Subclasses may override merely those methods that they are
 * actually interested in.
 *
 * <p>Note that this base class is only recommendable if you actually require
 * {@link InstantiationAwareBeanPostProcessor} functionality. If all you need
 * is plain {@link BeanPostProcessor} functionality, prefer a straight
 * implementation of that (simpler) interface.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since 2.0
 */
public abstract class InstantiationAwareBeanPostProcessorAdapter implements SmartInstantiationAwareBeanPostProcessor {

   ...
   
   @Override
   public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
      return null;
   }

   @Override
   public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
      return true;
   }
   
   ...
   
}

实际上就是把里面的所有的方法做一个简单的实现, 继承这个Adapter然后重写方法是一样的。

复写 postProcessBeforeInstantiation方法, 重新理解它的含义

好了,知道怎么改了,我们要复写 postProcessBeforeInstantiation方法,也就是实现 InstantiationAwareBeanPostProcessor 接口, 那么就是上面实现方法的那个样子,

image.png

那么我们分析一下这个方法,这个方法实现的第一印象是有点奇怪的,我们知道 Class已经在ClassLoading加载完了,beanName实际上是在注册的时候已经知道了,这两个信息基本上就是一个只读的,因为都基本没法改变,而且也没必要改变什么,这个时候我需要返回一个对象,再回去看看Java DOC里面的说法,重新理解一遍

/** 
* ...
* Apply this BeanPostProcessor <i>before the target bean gets instantiated</i>.
* The returned bean object may be a proxy to use instead of the target bean,
* effectively suppressing default instantiation of the target bean.
* ...
*/
@Nullable
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
   return null;
}

按照注释意思它是一个在实例化之前的一个前置操作,可以获取到要实例化的Bean的代理对象去替代目标的Bean, 可以有效的防止容器默认对目标Bean进行实例化.

你看这个说法, 摆明了就是不按照正常套路去实例化Bean.

那么简言之它就是个非主流操作,我可以产生一个Bean的实例,而且这个Bean实例呢还按照我的想法来, 不是Spring给组合的.

image.png

image.png

然后看一下方法体,配置的Bean不是有两个吗?一个是 User,一个是 SuperUser,在这里把这个SuperUser的输入给进行拦截,拦截完之后访问的普通对象不再是我们的配置对象,

这里运用了一些 Spring 的 API,利用

ObjectUtils.nullSafeEquals("superUser", beanName)

这个方法就是说允许两个参数都为null的情况下,也是安全的. 然后这里更严谨一点,去判断一下,

SuperUser.class.equals(beanClass)

判断通过的情况下, 把配置完成 superUser Bean 覆盖, 否则的话返回null, 就是保持由spring容器来进行组装,我们再看API里面这个描述,

/**
 * ...
 * @return the bean object to expose instead of a default instance of the target bean,
 * or {@code null} to proceed with default instantiation
 * ...
 */
@Nullable
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
   return null;
}

如果return null 的话, 还是进行默认初始化, 也就还是Spring容器来进行实例化.

image.png

接下来需要把这个BeanPostProcessor传到BeanFactory中,其实有两种方式,本节用了一种.

前面讲到 InstantiationAwareBeanPostProcessor 本来就是个 BeanPostProcessor,因此这里添加的时候就没有一点负担,直接add.

查找InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法的调用栈

接下来就要运行一下看看效果了, 看看是怎么拦截的:

image.png

看到控制台输出, SuperUser的所有的属性的全部为空,那么为什么为空是因为你做了拦截,有了这个表现之后,我们就好分析源码了,可以大胆去猜测,

当 postProcessBeforeInstantiation方法 返回了一个非空对象的时候,我后面的实例化初始化等逻辑全都都不走了

因此我们可以分析一下 postProcessBeforeInstantiation方法的调用逻辑 alt + F7,注意版本

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation
image.png

来看它的调用栈,这里会有个方法,applyBeanPostProcessorsBeforeInstantiation,可以看到方法内实际是遍历了 BeanPostProcessor, 去看是否有 InstantiationAwareBeanPostProcessor 的实现在, 如果有就做一个转换然后

Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);

这里如果 result不等于空,那么直接返回,那么简言之 applyBeanPostProcessorsBeforeInstantiation方法,实际上它也会被其他地方调用,再往上找。

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
image.png

看到了 resolveBeforeInstantiation,再往上找,

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
image.png

注意这是什么方法? createBean(), 这就是创建Bean的核心方法了. 这里注意标出来的if判断,就是说resolveBeforeInstantiation这个方法如果返回了一个非空的话,下面就不往下走了,不会去走 Object beanInstance = doCreateBean(beanName, mbdToUse, args);

否则的话继续创建,因此在这里打个断点。

image.png

另外在 postProcessBeforeInstantiation 的方法实现上也打个断点

image.png

debugger:

image.png

首先这里是依赖查找所以Bean的加载依然是按照查找顺序来的,我们先找的是User对象,那么这个对象没有经过postProcessBeforeInstantiation方法的拦截,知道这个前提后我们往 resolveBeforeInstantiation 方法内部走,

image.png

resolveBeforeInstantiation 方法内部没什么好说的, 再继续往 applyBeanPostProcessorsBeforeInstantiation 方法内部走

image.png

可以看到这里就开始遍历 BeanPostProcessor 了,然后获取到自定义的 MyInstantiationAwareBeanPostProcessor,然后

Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);

就进入到了重写的 postProcessBeforeInstantiation方法 中

image.png

因为不是SuperUser的Bean, 所以这里不会被拦截, 返回null, 那么符合我们上面的分析,

image.png

image.png

image.png

所以一路返回null, 一直到 createBean方法内, 然后doCreateBean就涉及到后面章节的内容了, 在这里不讲.

然后就是 SuperUser的依赖查找, 那么有了上面的步骤, 毫无疑问SuperUser就会被拦截下来

image.png

image.png

image.png

可以看到SuperUser被直接返回了, doCreateBean方法根本不会去走.

所以这就是个非主流操作,这个操作可以用来提前生成一些我们代理对象,比如说如果是个 远程JRPC 或者是远程的 RPC操作 的话,这是有帮助的,因为本地类没有远程能力,可以通过这种方式来进行拦截。

当然你也需要在这个类里面做一些相关的判断。

总结

通过简单的示例,我们了解到 InstantiationAwareBeanPostProcessor 的一个基本的使用方式,

通过它的postProcessBeforeInstantiation方法,在实例化之前的一个前置操作,可以获取到要实例化的Bean的代理对象去替代目标的Bean, 可以有效的防止容器默认对目标Bean进行实例化.

如果return null 的话, 还是进行默认初始化, 也就还是Spring容器来进行实例化.

那么呼应了我们的标题, Bean的实例化是可以被绕开的, 通过 InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation方法.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值