前言
Spring扩展点较多,本文主要从BeanFactoryPostProcessor作用、解析及是实例化入手,通过源码解读分析自定义BeanFactoryPostProcessor加载流程。同时在Spring单测模块自定义BeanFactoryPostProcessor,屏蔽Dubbo的ServiceBean、定时任务Job及阿里云消息,加速Spring test容器启动流程。后续有关其他扩展点解析会陆续更新。
BeanFactoryPostProcessor扩展点解析
有关自定义BeanFactoryPostProcessor解析,主要包括BeanDefinition生成和后置处理器方法回调解析这两块。
1.解析生成BeanDefinition
自定义BeanFactoryPostProcessor主要通过包扫描+@Component注解的方式引入到Spring中。包扫描主要通过ComponentScanBeanDefinitionParser#parse进行,下面分析下该方法的执行。
1.1 解析器生成
- Spring将xml转换成Document后,遍历节点,当处理到[context:component-scan: null]时,根据节点获取namespaceUri,即 www.springframework.org/schema/cont…
- 根据namespaceUri获取NameSpaceHandler,即ContextNamespaceHandler,其实Spring自己在META-INF/spring.handlers中维护了namespaceUri,在解析Element时,已经load到内存里放到handlerMappings里了。



- 解析时,首先调用ContextNamespaceHandler的init方法,初始化elementName对应的解析器;

1.2 包扫描生成BD
ComponentScanBeanDefinitionParser通过获取包数据,创建一个包扫描器ClassPathBeanDefinitionScanner,然后扫描包里的类。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 获取base-package属性,得到包的字符串,以","分割
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
// 切割成字符串数组
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
// 通过ClassPathBeanDefinitionScanner扫描包
Set<BeanDefinitionHolder> beanDefinitions =
scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
分析: ClassPathBeanDefinitionScanner并不是使用ApplicationContext中的ClassPathBeanDefinitionScanner,而是自己创建了一个ClassPathBeanDefinitionScanner。
下面简单分析下ClassPathBeanDefinitionScanner#doScan解析流程
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
// 遍历所有的包
for (String basePackage : basePackages) {
// 这个方法主要是先拿出包下所有的类,然后再去匹配,看是否含有@Component注解
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 解析scope-proxy,关于scope-proxy下文有补充
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 通过beanName生成器生成beanName,先判断注解上有没有显示设置beanName,没有的话,就以类名小写为beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
如果这个类是AbstractBeanDefinition类型,则为他设置默认值,比如lazy/init/destroy,
通过扫描出来的bd是ScannedGenericBeanDefinition,实现了AbstractBeanDefinition
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
// 把常用注解设置到AnnotationBeanDefinition中
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//beanDefinition注册到了DefaultListableBeanFactory中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
补充: scoped-proxy: scope代理,有三个值选项,no(默认值),interfaces(接口代理),targetClass(类代理),那什么时候需要用到scope代理呢,举个例子,我们知道Bean的作用域scope有singleton,prototype,request,session,那有这么一种情况,当你把一个session或者request的Bean注入到singleton的Bean中时,因为singleton的Bean在容器启动时就会创建A,而session的Bean在用户访问时才会创建B,那么当A中要注入B时,有可能B还未创建,这个时候就会出问题,那么代理的时候来了,B如果是个接口,就用interfaces代理,是个类则用targetClass代理。这个例子出处:www.bubuko.com/infodetail-…
下面简单分析findCandidateComponents方法
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
这个主要的作用是扫描包下面所有的文件,然后对每个文件进行判断,看是否存在@Component注解,如果存在,加入到candidates集合里。有个重要的一点是Spring生成的BeanDefinition默认类型都是ScannedGenericBeanDefinition(这点在应用中有使用到)。
2.自定义BeanFactoryPostProcessor回调方法分析
自定义工厂后置器的解析流程是个非常复杂的过程,其在AbstractApplicationContext#invokeBeanFactoryPostProcessors这个方法中执行,有关这个方法的具体执行逻辑会在其他文章中着重描述,本文只大概说下具体执行代码的逻辑。
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
.........
// Finally, invoke all other BeanFactoryPostProcessors.
// 当处理完实现了PriorityOrdered和Ordered后处理我们自定义的bean工厂后置处理器
List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
for (String postProcessorName : nonOrderedPostProcessorNames) {
nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
// Clear cached merged bean definitions since the post-processors might have
// modified the original metadata, e.g. replacing placeholders in values...
beanFactory.clearMetadataCache();
}
/**
* Invoke the given BeanFactoryPostProcessor beans.
*/
private static void invokeBeanFactoryPostProcessors(
Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
for (BeanFactoryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanFactory(beanFactory);
}
}
三、BeanFactoryPostProcessor扩展点应用
BeanFactoryPostProcessor可以在Bean实例化之前修改BeanDefinition,干扰Bean生命周期。基于此,作者想到在集成测试中,Spring test启动会加载很多配置项,如对外暴露的服务、定时任务及阿里云消息,这些bean的初始化过程会有较多的网络连接耗时,如果对外提供的接口或者阿里云消息过多,整个项目启动耗时会较长,试想你做一个接口的单测,要花这么长时间,肯定是烦躁无比的。因此,作者通过BeanFactoryPostProcessor这种BeanFactory后置处理器人为排除掉Dubbo ServiceBean、OpJob及OnsProducer和OnsConsumer对应的BeanDefinition,使这些bean不再生成,这样就可以大大缩减项目启动时间。同时mock掉阿里云消息发送,缩短单测时间。
1.自定义BeanFactoryPostProcessor
在web项目中,在spring test模块自定义BeanFactoryPostProcessor实现BeanFactoryPostProcessor接口,重写postProcessBeanFactory方法,实现扩展逻辑。注意一点,这个自定义的bean工厂后置处理器要交由Spring管理。
/**
* @author kunlun.tian
* @since 2019-11-27 14:33
*/
@Component
public class SpringTestFactoryBeanPostProcessor implements BeanFactoryPostProcessor {
// 要屏蔽的class
private static final Set<String> EXCLUDE_BEAN_CLASSES = Sets.newHashSet(
"com.ooo.finance.vehicle.disposal.core.job.config.OpjobManagerConfig",
"com.alibaba.dubbo.config.spring.ServiceBean",
"com.ooo.optimus.mq.aliyunons.ONSConsumerInvoker");
// 要屏蔽的含有某些注解的bean
private static final Set<String> ANNOTATION_BEAN_CLASSES = Sets.newHashSet("com.xxx.opjobclient.annotation.JobHandler")
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
DefaultListableBeanFactory defaultBeanFactory = (DefaultListableBeanFactory) beanFactory;
// 通过beanFactory获取所有BeanDefinition名称
ArrayList<String> definitionNames = Lists.newArrayList(defaultBeanFactory.getBeanDefinitionNames());
// 遍历所有definitionName
for (String definitionName : definitionNames) {
// 通过DefaultListableBeanFactory中的beanDefinitionMap获取definitionName对应的BeanDefinition
BeanDefinition beanDefinition = defaultBeanFactory.getBeanDefinition(definitionName);
String beanClassName = beanDefinition.getBeanClassName();
if (EXCLUDE_BEAN_CLASSES.contains(beanClassName)) {
//排除掉beanDefinition,即从维护的beanDefinitionMap和beanDefinitionNames中删除掉对应的beanDefintion和beanDefintionName。
defaultBeanFactory.removeBeanDefinition(definitionName);
}
if (beanDefinition instanceof ScannedGenericBeanDefinition) {
ScannedGenericBeanDefinition sgbd = (ScannedGenericBeanDefinition) beanDefinition;
AnnotationMetadata metadata = sgbd.getMetadata();
for (String annotationBeanClass : ANNOTATION_BEAN_CLASSES) {
//判断是否含有某些注解
if(metadata.hasAnnotation(annotationBeanClass)){
// 排除含有注解的beanDefinition
defaultBeanFactory.removeBeanDefinition(definitionName);
}
}
}
// OpjobManagerConfig可以理解成一个配置类,其通过@Bean注解引入OpjobManager,所以将其排除。
if ("opjobManagerConfig".equals(beanDefinition.getFactoryBeanName())) {
defaultBeanFactory.removeBeanDefinition(definitionName);
}
// 替换ONSProducer对应的beanDefinition的class,这样也不会真正地去发消息了。
if (ONSProducer.class.getName().equals(beanDefinition.getBeanClassName())) {
beanDefinition.setBeanClassName(MockOnsProducer.class.getName());
}
}
}
}
1.1 分析OpjobManagerConfig做了啥?
@Component
public class OpjobManagerConfig {
@Bean(initMethod = "start", destroyMethod = "destroy")
public OpjobManager opjobManager() {
return new OpjobManager();
}
}
通过@Bean的方式引入op-job-client中的OpjobManager,生成bean时调用OpjobManager的start方法。




1.2 如何排除加了@Bean注解的bean?
由上图可以发现有BeanMethod生成的BD,会有一个factoryBeanName,根据factoryBeanName去排除。
自定义BeanFactoryPostProcessor回调方法
自定义工厂后置器的解析流程是个非常复杂的过程,其在AbstractApplicationContext#invokeBeanFactoryPostProcessors这个方法中执行,有关这个方法的具体执行逻辑会在其他文章中着重描述,本文只大概说下具体执行代码的逻辑。
// OpjobManagerConfig可以理解成一个配置类,其通过@Bean注解引入OpjobManager,所以将其排除。
if ("opjobManagerConfig".equals(beanDefinition.getFactoryBeanName())) {
defaultBeanFactory.removeBeanDefinition(definitionName);
}
注意: 在一个实体类中如果有多个@Bean注解的方法返回类型一样,则会创建多个类。
1.3排除有关定时任务的Job
包扫描生成的ScannedGenericBeanDefinition含有元数据metada,其中含有注解信息annotationSet,判断annotationSet中是否含有@JobHandler注解,如果有,排除即可。

if (beanDefinition instanceof ScannedGenericBeanDefinition) {
ScannedGenericBeanDefinition sgbd = (ScannedGenericBeanDefinition) beanDefinition;
AnnotationMetadata metadata = sgbd.getMetadata();
for (String annotationBeanClass : ANNOTATION_BEAN_CLASSES) {
//判断是否含有某些注解
if(metadata.hasAnnotation(annotationBeanClass)){
// 排除含有注解的beanDefinition
defaultBeanFactory.removeBeanDefinition(definitionName);
}
}
}
1.4其他
- EXCLUDE_BEAN_CLASSES这个集合里存放需要排除的bean的className;
- ANNOTATION_BEAN_CLASSES这个集合里存放注解的className,这样做的目的,主要是排除含有指定注解的bean;
- 对于含有某些指定注解的bean,放到EXCLUDE_BEAN_CLASSES中去也是可以的,但每新增指定注解的bean时,要向EXCLUDE_BEAN_CLASSES里手动添加,这样有些麻烦,也不灵活;
2.自定义MockOnsProducer
自定义ONSProducer集成ONSProducer,重写其中发送消息的方法,然后利用bean工厂后置处理器将ONSProducer对应的BeanDefinition的class替换成MockOnsProducer的class,这样在单测时就不会去真正发送消息了。
public class MockOnsProducer extends ONSProducer {
public void send(Map<String, Object> body, String keys) {
System.out.println("mock掉了");
}
public void send(Map<String, Object> body, String keys, String tags) {
System.out.println("mock掉了");
}
public void send(Map<String, Object> body, String keys, Long delay) {
System.out.println("mock掉了");
}
public void send(Map<String, Object> body, String keys, String tags, Long delay) {
System.out.println("mock掉了");
}
}
1.修改class属性
class修改后,实例化时就会以替代后的class进行初始化,也就是MockOnsProducer,这样生成的ONSProducer不会进行相关的注册,同时重写发送消息的方法,那么单测时也不会真正发送消息。
// 替换ONSProducer对应的beanDefinition的class,这样也不会真正地去发消息了。
if (ONSProducer.class.getName().equals(beanDefinition.getBeanClassName())) {
beanDefinition.setBeanClassName(MockOnsProducer.class.getName());
}
2.注意点
- 通过beanDefinition.setBeanClassName方法将子类的class将父类替换掉;
- 注意一点,MockOnsProducer这个子类不应该由Spring 管理了,否则spring不知道该用哪一个类了,在启动时会报错。
3.为什么说可以提高单测效率?
- Dubbo ServiceBean初始时要注册服务到zk,开启netty端口;OpJobManager实例化要注册服务到zk上;同样,屏蔽掉OnsConsumer本身也能减少网络消耗。
- 由于屏蔽些含有大量网络交互的操作可以大大减少项目启动时间,前后耗时对比如下图

总结
bean工厂后置处理器主要作用是在bean实例化之前干扰bean的生命周期,我们利用这点去排除不需要加载的beanDefinition或者修改bean的一些属性,如class属性等;