曹工杂谈:为什么很少需要改Spring源码,因为扩展点太多了,说说Spring的后置处理器
前言
最近发了好几篇,都是覆盖框架源码,但是spring的代码,我是从没覆盖过,毕竟,如果方便扩展,没谁想去改源码,而spring就是不需要改源码的那个,真的是“对扩展开放,对修改关闭”的典范。
就我说曾经用过的,spring的扩展点,就包括了listener、beanFactoryPostProcessor、beanPostProcessor,而spring boot的扩展点,除了properties、yml、java config覆盖自动配置、org.springframework.boot.CommandLineRunner,还包括了META-INF下的spring.factory等。
眼下就有以前的一个例子:

这次,只简单说说后置处理器,主要是beanFactoryPostProcessor、beanPostProcessor。
先说说beanFactoryPostProcessor
这两个比较像,都是后置处理器,但是处理的对象不同,前者是针对beanFactory,后者是针对bean实例。
beanFactoryPostProcessor的注释如下:
Allows for custom modification of an application context's bean definitions, adapting the bean property values of the context's underlying bean factory.
Application contexts can auto-detect BeanFactoryPostProcessor beans in their bean definitions and apply them before any other beans get created.
Useful for custom config files targeted at system administrators that override bean properties configured in the application context.
See PropertyResourceConfigurer and its concrete implementations for out-of-the-box solutions that address such configuration needs.
A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing BeanPostProcessor instead.
简单来说,允许对bean definition进行修改。
bean definition定义
所谓的bean definition呢,就是bean的元数据,比如bean的name、scope、class、是否延迟初始化(is-lazy-init)、依赖的bean等等。
负责维护bean definition 的注册表
bean definition放在哪里呢,就在org.springframework.beans.factory.support.BeanDefinitionRegistry里,看名字可以知道,这是一个注册表,具体存储来说,一般会选择我们熟悉的hashmap,key是beanDefinition的类名,value就是beanDefinition。
当然,这只是个接口,其提供了增删改查的方法:
public interface BeanDefinitionRegistry extends AliasRegistry {
    //注册bean Definition
	void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException;
	//删除bean Definition
	void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
	//获取beanDefinition
	BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
	//判断是否包含指定的bean Definition
	boolean containsBeanDefinition(String beanName);
	//获取所有的beanDefinition的名称
	String[] getBeanDefinitionNames();
	//获取beanDefinition的数量
	int getBeanDefinitionCount();
}
那我们再看看这个接口的实现:

这里面可以看出来,ApplicationContext就是这个接口的实现,这里可以稍微看下registerBean的实现:
org.springframework.context.support.GenericApplicationContext#registerBeanDefinition
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
	//代理给beanFactory
    this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
}
这里的beanFactory类型为:org.springframework.beans.factory.support.DefaultListableBeanFactory
	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition != null) {
			//...省略无关代码
			//往hashmap里存放beanName--》beanDefinition
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		//...省略无关代码
	}
beanFactoryPostProcessor`的实现类
经过上面的介绍,想来大家比较了解beanFactoryPostProcessor了,我们看看这个接口的实现类呢:

拿以上实现类来说,
org.springframework.boot.web.servlet.ServletComponentRegisteringPostProcessor:
这个是处理ServletComponentScan注解,将@WebServlet,@WebFilter等注解的servlet组件,注册到applicationContext。
默认情况下,spring boot web应用,会有如下这个实现类:
org.springframework.context.annotation.ConfigurationClassPostProcessor
主要就是用于处理@Configuration注解的java类。
org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory
	/**
	 * Prepare the Configuration classes for servicing bean requests at runtime
	 * by replacing them with CGLIB-enhanced subclasses.
	 */
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		int factoryId = System.identityHashCode(beanFactory);
		this.factoriesPostProcessed.add(factoryId);
		if (!this.registriesPostProcessed.contains(factoryId)) {
			// BeanDefinitionRegistryPostProcessor hook apparently not supported...
			// Simply call processConfigurationClasses lazily at this point then.
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		}
		//对configuration注解的类进行cglib代理,保证@bean注解的方法,即使多次调用,也只会有一个实例
		enhanceConfigurationClasses(beanFactory);
		//新增一个bean后置处理器
		beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
	}
自定义beanFactoryPostProcessor,并使之生效
很简单,像下面这样,定义一个类,实现BeanFactoryPostProcessor,并保证被扫描到即可。
@Component
@Slf4j
public class CustomBeanDefinitionPostProcessor implements BeanFactoryPostProcessor{
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition definition = beanFactory.getBeanDefinition("customBeanDefinitionPostProcessor");
        log.info("definition:{}",definition);
    }
}
启动时,输出如下:
11-12 15:49:48.627 [restartedMain] INFO  c.c.cad.config.CustomBeanDefinitionPostProcessor
                    - definition:Generic bean: class [com.ceiec.cad.config.CustomBeanDefinitionPostProcessor]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [F:\working_code\****\CustomBeanDefinitionPostProcessor.class] [CustomBeanDefinitionPostProcessor.java:20]
再说说beanPostProcessor
接口定义
Factory hook that allows for custom modification of new bean instances, e.g. checking for marker interfaces or wrapping them with proxies.
ApplicationContexts can autodetect BeanPostProcessor beans in their bean definitions and apply them to any beans subsequently created. Plain bean factories allow for programmatic registration of post-processors, applying to all beans created through this factory.
Typically, post-processors that populate beans via marker interfaces or the like will implement postProcessBeforeInitialization(java.lang.Object, java.lang.String), while post-processors that wrap beans with proxies will normally implement postProcessAfterInitialization(java.lang.Object, java.lang.String).
//对bean的实例进行修改,或者用一个代理来包装它们,这个和上面的重要差别就出来了,一个是在bean还没实例化之前,处理beanFactory里的bean definition;一个是处理实例化后的bean。
public interface BeanPostProcessor {
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}
接口的实现类

以上有大家熟悉的,比如 ApplicationContextAwareProcessor:
	org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces
	private void invokeAwareInterfaces(Object bean) {
		if (bean instanceof Aware) {
			//....省略无关
			if (bean instanceof ApplicationContextAware) {
				((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
			}
		}
	}
aop代理的实现
另一个广泛应用的,就是aop用到的org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor
AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization
public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (this.advisor == null || bean instanceof AopInfrastructureBean) {
			// Ignore AOP infrastructure such as scoped proxies.
			return bean;
		}
		if (bean instanceof Advised) {
			Advised advised = (Advised) bean;
			if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
				// Add our local Advisor to the existing proxy's Advisor chain...
				if (this.beforeExistingAdvisors) {
					advised.addAdvisor(0, this.advisor);
				}
				else {
					advised.addAdvisor(this.advisor);
				}
				return bean;
			}
		}
		if (isEligible(bean, beanName)) {
			ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
			//是否对目标类进行代理(cglib),如果不是的话,则获取bean的接口,进行接口代理,即jdk代理
			if (!proxyFactory.isProxyTargetClass()) {
				evaluateProxyInterfaces(bean.getClass(), proxyFactory);
			}
			proxyFactory.addAdvisor(this.advisor);
			customizeProxyFactory(proxyFactory);
			return proxyFactory.getProxy(getProxyClassLoader());
		}
		// No proxy needed.
		return bean;
	}
	protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);
		proxyFactory.setTarget(bean);
		return proxyFactory;
	}
自定义beanPostProcessor,并生效
很简单,直接在你的代码里,像下面这样写一个类,实现BeanPostProcessor,并保证被扫描到即可。
@Component
@Slf4j
public class CustomBeanPostProcessor implements BeanPostProcessor{
    @Nullable
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof RabbitTemplate) {
            log.info("hahah RabbitTemplate");
        }
        return bean;
    }
}
下面是我这边运行的效果:
总结
相同点
1、操作对象不同
前面也说了,beanFactoryPostProcessor对bean的图纸进行修改,beanPostProcessor则是对生产出来的东西,进行修改或者替换(为什么说替换,因为也可能照着生产出来的产品,搞一个代理,比如aop就是基于此实现。)
2、生效时机不同

相同点
相同点呢,就是,这都是spring给我们提供出来的扩展点,相当方便,不是吗?
曹工杂谈:为什么很少需要改Spring源码,因为扩展点太多了,说说Spring的后置处理器的更多相关文章
- 【曹工杂谈】Mysql-Connector-Java时区问题的一点理解--写入数据库的时间总是晚13小时问题
		
背景 去年写了一篇"[曹工杂谈]Mysql客户端上,时间为啥和本地差了整整13个小时,就离谱",结果最近还真就用上了. 不是我用上,是组内一位同事,他也是这样:有个服务往数据库in ...
 - 【曹工杂谈】Maven源码调试工程搭建
		
Maven源码调试工程搭建 思路 我们前面的文章<[曹工杂谈]Maven和Tomcat能有啥联系呢,都穿打补丁的衣服吗>分析了Maven大体的执行阶段,主要包括三个阶段: 启动类阶段,负责 ...
 - 曹工杂谈--使用mybatis的同学,进来看看怎么在日志打印完整sql吧,在数据库可执行那种
		
前言 今天新年第一天,给大家拜个年,祝大家新的一年里,技术突突突,头发长长长! 咱们搞技术的,比较直接,那就开始吧.我给大家看看我demo工程的效果(代码下边会给大家的): 技术栈是mybatis/m ...
 - 【曹工杂谈】详解Maven插件调试方法
		
前言 今年的更新频率简直是降至冰点了,一方面平时加班相对多一些了,下班只想玩手机:另一方面,好像进了大厂后,学习动力也很低了,总之就,很懒散,博客的话,今年都才只更新了不到5篇. 现在慢慢有一点状态, ...
 - 曹工杂谈:Java 类加载还会死锁?这是什么情况?
		
一.前言 今天事不是很多,正好在Java交流群里,看到一个比较有意思的问题,于是花了点时间研究了一下,这里做个简单的分享. 先贴一份测试代码,大家可以先猜测一下,执行结果会是怎样的: import j ...
 - 曹工杂谈:花了两天时间,写了一个netty实现的http客户端,支持同步转异步和连接池(1)--核心逻辑讲解
		
背景 先说下写这个的目的,其实是好奇,dubbo是怎么实现同步转异步的,然后了解到,其依赖了请求中携带的请求id来完成这个连接复用:然后我又发现,redisson这个redis客户端,底层也是用的ne ...
 - 曹工杂谈--只用一个命令,centos系统里装了啥软件,啥时候装的,全都清清楚楚
		
前言 一直以来,对linux的掌握就是半桶水的状态,经常yum装个东西,结果依赖一堆东西:然后再用源码装个东西,只知道make.make install,背后干了啥也不清楚了,卸载也不方便. 这几天工 ...
 - 【曹工杂谈】Maven IOC 容器-- Guice内部有什么
		
Google Guice容器内部有什么 前言 Maven系列,好几天没写了,主要是这几天被Google Guice卡住了,本来是可以随便带过Guice,讲讲guice的用法就够了(这个已经讲了,在前面 ...
 - 曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了
		
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
 
随机推荐
- 深度长文回顾web基础组件
			
什么是Serlvet ? 全称 server applet 运行在服务端的小程序: 首先来说,这个servlet是java语言编写的出来的应用程序,换句话说servlet拥有java语言全部的优点,比 ...
 - Celery的使用完成异步任务与定时任务
			
0917自我总结 Celery的使用 一.官方文档 Celery 官网:http://www.celeryproject.org/ Celery 官方文档英文版:http://docs.celeryp ...
 - Ubuntu安装exfat工具
			
sudo apt-get undate sudo apt-get install exfat-utils
 - 自学web前端达到什么水平,才能满足求职的标准?
			
大多数野生程序员最棘手的问题就是如何依靠技术解决温饱,通俗来讲就是技术折现的问题. 如果是单纯出于兴趣,或者只是为了突击某一阶段或者某一项目技术壁垒,不跟就业挂钩的自学倒也是无关痛痒.但是当上岗成为自 ...
 - shark恒破解笔记6-BC++假自效验
			
这小节介绍了查壳(peid) 查软件编写语言(die)以及用esp定律脱aspack壳,最后是破解bc++的自校验部分 目标: 首先查看软件 peid查壳 有壳 ,但是不知道是什么语言写的,这里使用D ...
 - PHP krsort
			
1.什么都不想说了,干么没事放那么悲伤的歌呢?回忆里我还是对代码懵懵懂懂的无知青年!也许不是青年,只是少年... <?php $arr = [ 1 => 'Zhangbiyu', 2 =& ...
 - [BZOJ3029] 守卫者的挑战
			
Description 打开了黑魔法师Vani的大门,队员们在迷宫般的路上漫无目的地搜寻着关押applepi的监狱的所在地.突然,眼前一道亮光闪过.“我,Nizem,是黑魔法圣殿的守卫者.如果你能通过 ...
 - pytest6-Fixture finalization / executing teardown code(使用yield来实现)
			
Fixture finalization / executing teardown code By using a yield statement instead of return, all the ...
 - Kubernetes2-K8s的集群部署
			
一.简介 1.架构参考 Kubernetes1-K8s的简单介绍 2.实例架构 192.168.216.51 master etcd 192.168.216.53 node1 192.168.216 ...
 - Spring cloud 学习笔记
			
前奏 1. 什么是微服务? 微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,==每一个微服务提供单个业务功能的服务==,一个服务做一件事,从技术角度看就是一种 ...