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="https://juejin.cn/post/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,因此接口里面可以提供默认实现这种方式,可以看到接口确实也有默认的实现代码.
我们可以直接利用接口来进行实现,因此这个操作可以更简化,只实现 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 接口, 那么就是上面实现方法的那个样子,
那么我们分析一下这个方法,这个方法实现的第一印象是有点奇怪的,我们知道 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给组合的.
然后看一下方法体,配置的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容器来进行实例化.
接下来需要把这个BeanPostProcessor传到BeanFactory中,其实有两种方式,本节用了一种.
前面讲到 InstantiationAwareBeanPostProcessor 本来就是个 BeanPostProcessor,因此这里添加的时候就没有一点负担,直接add.
查找InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法的调用栈
接下来就要运行一下看看效果了, 看看是怎么拦截的:
看到控制台输出, SuperUser的所有的属性的全部为空,那么为什么为空是因为你做了拦截,有了这个表现之后,我们就好分析源码了,可以大胆去猜测,
当 postProcessBeforeInstantiation方法 返回了一个非空对象的时候,我后面的实例化初始化等逻辑全都都不走了,
因此我们可以分析一下 postProcessBeforeInstantiation方法的调用逻辑 alt + F7,注意版本
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation
来看它的调用栈,这里会有个方法,applyBeanPostProcessorsBeforeInstantiation,可以看到方法内实际是遍历了 BeanPostProcessor, 去看是否有 InstantiationAwareBeanPostProcessor 的实现在, 如果有就做一个转换然后
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
复制代码
这里如果 result不等于空,那么直接返回,那么简言之 applyBeanPostProcessorsBeforeInstantiation方法,实际上它也会被其他地方调用,再往上找。
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
看到了 resolveBeforeInstantiation,再往上找,
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
注意这是什么方法? createBean(), 这就是创建Bean的核心方法了. 这里注意标出来的if判断,就是说resolveBeforeInstantiation这个方法如果返回了一个非空的话,下面就不往下走了,不会去走 Object beanInstance = doCreateBean(beanName, mbdToUse, args);
否则的话继续创建,因此在这里打个断点。
另外在 postProcessBeforeInstantiation 的方法实现上也打个断点
debugger:
首先这里是依赖查找所以Bean的加载依然是按照查找顺序来的,我们先找的是User对象,那么这个对象没有经过postProcessBeforeInstantiation方法的拦截,知道这个前提后我们往 resolveBeforeInstantiation 方法内部走,
resolveBeforeInstantiation 方法内部没什么好说的, 再继续往 applyBeanPostProcessorsBeforeInstantiation 方法内部走
可以看到这里就开始遍历 BeanPostProcessor 了,然后获取到自定义的 MyInstantiationAwareBeanPostProcessor,然后
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
复制代码
就进入到了重写的 postProcessBeforeInstantiation方法 中
因为不是SuperUser的Bean, 所以这里不会被拦截, 返回null, 那么符合我们上面的分析,
所以一路返回null, 一直到 createBean方法内, 然后doCreateBean就涉及到后面章节的内容了, 在这里不讲.
然后就是 SuperUser的依赖查找, 那么有了上面的步骤, 毫无疑问SuperUser就会被拦截下来
可以看到SuperUser被直接返回了, doCreateBean方法根本不会去走.
所以这就是个非主流操作,这个操作可以用来提前生成一些我们代理对象,比如说如果是个 远程JRPC 或者是远程的 RPC操作 的话,这是有帮助的,因为本地类没有远程能力,可以通过这种方式来进行拦截。
当然你也需要在这个类里面做一些相关的判断。
总结
通过简单的示例,我们了解到 InstantiationAwareBeanPostProcessor 的一个基本的使用方式,
通过它的postProcessBeforeInstantiation方法,在实例化之前的一个前置操作,可以获取到要实例化的Bean的代理对象去替代目标的Bean, 可以有效的防止容器默认对目标Bean进行实例化.
如果return null 的话, 还是进行默认初始化, 也就还是Spring容器来进行实例化.
那么呼应了我们的标题, Bean的实例化是可以被绕开的, 通过 InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation方法.