听说 Spring Bean 的创建还有一条捷径?

2023年 7月 31日 77.6k 0

@


在 Spring Bean 的创建方法中,有如下一段代码:

AbstractAutowireCapableBeanFactory#createBean:

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	//...
	try {
		// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
		Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
		if (bean != null) {
			return bean;
		}
	}
	try {
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		return beanInstance;
	}
	//...
}

我们平时说的 Bean 的创建逻辑都是指 doCreateBean 方法中的逻辑,在松哥前面几篇文章中,凡是涉及到 Bean 的创建流程的,我说的也都是 doCreateBean 方法的流程。

但是小伙伴们注意,在 doCreateBean 方法执行之前,其实还有一个 resolveBeforeInstantiation 方法会先执行,而这个方法可能就直接产生一个 Bean 了!如果这个方法直接产生一个 Bean 了,那么 doCreateBean 方法中的逻辑就不会生效了。

那么 resolveBeforeInstantiation 方法存在的意义是什么呢?其实大家从该方法的注释上大概也能看出一些端倪出来了:

给 BeanPostProcessor 一个机会去创建一个代理对象,用这个代理对象来代替目标 Bean。

1. resolveBeforeInstantiation

看下面的源码小伙伴们一定要先搞清楚两个比较相似的单词,否则看到后面就乱了:

  • instantiation:实例化,从 Class 到 Bean 就是实例化。
  • initialization:初始化,给 Bean 做各种配置就是初始化。

搞明白这两个单词,我们来看源码。

首先我先来和小伙伴们稍微梳理一下 resolveBeforeInstantiation 方法。

AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation:

@Nullable
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
	Object bean = null;
	if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
		// Make sure bean class is actually resolved at this point.
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			Class targetType = determineTargetType(beanName, mbd);
			if (targetType != null) {
				bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
				if (bean != null) {
					bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
				}
			}
		}
		mbd.beforeInstantiationResolved = (bean != null);
	}
	return bean;
}

小伙伴们看一下,这里有一个判断条件,mbd.isSynthetic 是判断是否是一个合成 Bean,这种一般都是 Spring 定义的,我们自定义的 Bean 一般都不属于这一类,然后后面的 hasInstantiationAwareBeanPostProcessors 方法则是判断当前是否存在 InstantiationAwareBeanPostProcessor 类型的后置处理器,如果存在,则进入到 if 分支中。

如果我们想要在 resolveBeforeInstantiation 方法中就完成 Bean 的处理,那么就需要自己提供一个 InstantiationAwareBeanPostProcessor 类型的后置处理器。

接下来会调用两个方法:

  • applyBeanPostProcessorsBeforeInstantiation:从名字可以看出来,这个是在实例化之前触发的方法,所以这个方法的参数还是 Class,因为还未实例化。
  • applyBeanPostProcessorsAfterInitialization:从名字可以看出来,这个是在初始化之后出发的方法,所以这个方法的参数是 Bean,因为此时已经完成了初始化了。

1.1 applyBeanPostProcessorsBeforeInstantiation

@Nullable
protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) {
	for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
		Object result = bp.postProcessBeforeInstantiation(beanClass, beanName);
		if (result != null) {
			return result;
		}
	}
	return null;
}

这个地方就很简单了,就是执行 InstantiationAwareBeanPostProcessor 类型的后置处理器的 postProcessBeforeInstantiation 方法。

1.2 applyBeanPostProcessorsAfterInitialization

@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
		throws BeansException {
	Object result = existingBean;
	for (BeanPostProcessor processor : getBeanPostProcessors()) {
		Object current = processor.postProcessAfterInitialization(result, beanName);
		if (current == null) {
			return result;
		}
		result = current;
	}
	return result;
}

这个是执行 BeanPostProcessor 的 postProcessAfterInitialization 方法。

所以这块的源码其实并不难,道理很简单。

1.3 案例

松哥写一个简单的案例小伙伴们来看下。

假设我有一个 BookService,如下:

public class BookService {

    public void hello() {
        System.out.println("hello javaboy");
    }
}

然后我再创建一个 InstantiationAwareBeanPostProcessor 类型的后置处理器,并且重写前面提到的 postProcessBeforeInstantiation 和 postProcessAfterInitialization 方法:

public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    @Override
    public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException {
        if (beanClass == BookService.class) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(beanClass);
            enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
                String name = method.getName();
                System.out.println(name + " 方法开始执行了...");
                Object invoke = proxy.invokeSuper(obj, args);
                System.out.println(name + " 方法执行结束了...");
                return invoke;
            });
            BookService bookService = (BookService) enhancer.create();
            return bookService;
        }
        return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean.getClass() ========= " + bean.getClass());
        return InstantiationAwareBeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

在 postProcessBeforeInstantiation 方法中,如果要创建的 Bean 是 BookService,则这里通过 Enhancer 来创建一个 CGLIB 的代理对象,如果是其他的 Bean 的创建,则调用父类方法即可。这样重写之后,就会导致在 1.1 小节中,获取到的 result 就是一个代理的 BookService 对象。

在 postProcessAfterInitialization 方法中,我未做任何额外处理,就是把拿到的 Bean 打印了一下,此时我们拿到手的 Bean 其实就是前面 postProcessBeforeInstantiation 方法生成的代理对象,然后这里调用父类方法去返回,实际上就是把参数 Bean 原封不动返回。

最后将这两个 Bean 注册到 Spring 容器中:



    
    

然后初始化 Spring 容器:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop2.xml");
BookService bs = ctx.getBean(BookService.class);
System.out.println("bs.getClass() = " + bs.getClass());
bs.hello();

最终执行结果如下:

可以看到,最终拿到的 BookService 就是一个代理对象,从源码层面来讲,这个代理对象在 resolveBeforeInstantiation 方法中就生成了,后续的 doCreateBean 方法实际上并未执行。

这就是 resolveBeforeInstantiation 方法的作用,实际上就是给 BeanPostProcessor 一个机会去创建一个代理对象,用这个代理对象来代替目标 Bean。

2. 源码实践

松哥为什么会关注到这个方法呢?

如果有小伙伴研究过 Spring AOP 源码,就会发现这个方法在处理 Spring AOP 的时候,有一个用武之地。

当我们在 Spring AOP 中,往往通过如下代码来定义切面:

@Component
@Aspect
@EnableAspectJAutoProxy
public class LogAspect {
    //...
}

这个类上面有一个 @Aspect 注解,那么问题来了,Spring 是如何识别出这是一个切面而非普通的 Bean 的?

答案就是在 1.1 小节中的 applyBeanPostProcessorsBeforeInstantiation 方法中,这个方法会遍历所有 InstantiationAwareBeanPostProcessor 类型的后置处理器,InstantiationAwareBeanPostProcessor 有一个子类是 AnnotationAwareAspectJAutoProxyCreator,在这个处理器中,识别出来了 LogAspect 是一个切面。

具体识别方法如下:

首先调用 AnnotationAwareAspectJAutoProxyCreator 的 postProcessBeforeInstantiation 方法(实际上是 AnnotationAwareAspectJAutoProxyCreator 的父类 AbstractAutoProxyCreator 中的方法):

@Override
public Object postProcessBeforeInstantiation(Class beanClass, String beanName) {
	Object cacheKey = getCacheKey(beanClass, beanName);
	if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
		if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return null;
		}
	}
	//...
}

从这个地方开始,分成了两条线:

  • 如果是一个切面 Bean 的话,则执行第一个方法 isInfrastructureClass 就可以返回 true 了。
  • 如果是一个普通 Bean 的话,则第一个方法会返回 false,此时就会执行第二个方法 shouldSkip(虽然该方法也会返回 false),但是该方法有一些其他的价值在里边。
  • 2.1 切面 Bean

    我们先来看 isInfrastructureClass 方法,先来看切面 Bean 是怎么处理的。

    这个方法我摘了一部分出来,我们重点关注 isInfrastructureClass 方法,这个方法用来判断当前类是否是一个 Aspect:

    @Override
    protected boolean isInfrastructureClass(Class beanClass) {
    	return (super.isInfrastructureClass(beanClass) ||
    			(this.aspectJAdvisorFactory != null && this.aspectJAdvisorFactory.isAspect(beanClass)));
    }
    

    这里的判断主要是两方面:

  • 调用父类的方法去判断当前类是否和 AOP 相关:
  • protected boolean isInfrastructureClass(Class beanClass) {
    	boolean retVal = Advice.class.isAssignableFrom(beanClass) ||
    			Pointcut.class.isAssignableFrom(beanClass) ||
    			Advisor.class.isAssignableFrom(beanClass) ||
    			AopInfrastructureBean.class.isAssignableFrom(beanClass);
    	return retVal;
    }
    

    这个就不用我解释了,这些都是我们在 AOP 中的老熟人了。

  • 调用 aspectJAdvisorFactory.isAspect 方法去判断当前类是否包含 @Aspect 注解:
  • AbstractAspectJAdvisorFactory#isAspect

    @Override
    public boolean isAspect(Class clazz) {
    	return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
    }
    private boolean hasAspectAnnotation(Class clazz) {
    	return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
    }
    

    这代码可太好理解了,就是检查当前类是否有 @Aspect 注解。后面那个 compiledByAjc 方法是检查是否需要 ajc 编译,这个我们一般都不需要,所以,只要 hasAspectAnnotation 方法返回 true,整体上就会返回 true。

    如果我们的类上包含 @Aspect 注解,那么最终就会在将当前类名加入到 advisedBeans Map 中,在 advisedBeans 这个 Map 中,key 是当前 Bean 的名称,value 则是 false 是一个标记,表示当前类不需要生成代理类。

    这就是 isInfrastructureClass 方法执行的大致逻辑。

    2.2 普通 Bean

    如果是普通 Bean 的话,很明显 isInfrastructureClass 方法会返回 false,这就会导致 shouldSkip 方法去执行,这个方法名虽然叫 shouldSkip,但是却干了不少实事。

    这个方法我会在下篇文章中和小伙伴们分享 AOP 的创建过程中再和大家详解,这里先说一句,这个方法会把各种 Aspect Bean 都收集整理起来,将来根据这些 Bean 去生成 Advisor。

    好啦,这就是 resolveBeforeInstantiation 方法的作用,感兴趣的小伙伴可以自己 DEBUG 看一些哦~

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论