Spring的那些开发小技巧(中)
创始人
2025-05-30 03:18:28

BeanPostProcessor

BeanPostProcessor,中文名 Bean的后置处理器,在Bean创建的过程中起作用。

BeanPostProcessor是Bean在创建过程中一个非常重要的扩展点,因为每个Bean在创建的各个阶段,都会回调BeanPostProcessor及其子接口的方法,传入正在创建的Bean对象,这样如果想对Bean创建过程中某个阶段进行自定义扩展,那么就可以自定义BeanPostProcessor来完成。

说得简单点,BeanPostProcessor就是在Bean创建过程中留的口子,通过这个口子可以对正在创建的Bean进行扩展。只不过Bean创建的阶段比较多,然后BeanPostProcessor接口以及他的子接口InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor就提供了很多方法,可以使得在不同的阶段都可以拿到正在创建的Bean进行扩展。

来个Demo

现在需要实现一个这样的需求,如果Bean的类型是User,那么就设置这个对象的username属性为 ”三友的java日记“。

那么就可以这么写:

public class UserBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof User) {//如果当前的Bean的类型是 User ,就把这个对象 username 的属性赋值为 三友的java日记((User) bean).setUsername("三友的java日记");}return bean;}}

测试:

public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();//将 UserBeanPostProcessor 和  User 注册到容器中applicationContext.register(UserBeanPostProcessor.class);applicationContext.register(User.class);applicationContext.refresh();User user = applicationContext.getBean(User.class);System.out.println("获取到的Bean为" + user + ",属性username值为:" + user.getUsername());}}

测试结果:

获取到的Bean为com.sanyou.spring.extension.User@21a947fe,属性username值为:三友的java日记

从结果可以看出,每个生成的Bean在执行到某个阶段的时候,都会回调UserBeanPostProcessor,然后UserBeanPostProcessor就会判断当前创建的Bean的类型,如果是User类型,那么就会将username的属性设置为 ”三友的java日记“。

Spring内置的BeanPostProcessor

这里我列举了常见的一些BeanPostProcessor的实现以及它们的作用

通过列举的这些BeanPostProcessor的实现可以看出,Spring Bean的很多注解的处理都是依靠BeanPostProcessor及其子类的实现来完成的,这也回答了上一小节的疑问,处理@Autowired、@PostConstruct、@PreDestroy注解是如何起作用的,其实就是通过BeanPostProcessor,在Bean的不同阶段来调用对应的方法起作用的。

BeanPostProcessor在Dubbo中的使用

在Dubbo中可以通过@DubboReference(@Reference)来引用生产者提供的接口,这个注解的处理也是依靠ReferenceAnnotationBeanPostProcessor,也就是 BeanPostProcessor 的扩展来实现的。

public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements ApplicationContextAware, BeanFactoryPostProcessor {// 忽略
}

当Bean在创建的某一阶段,走到了ReferenceAnnotationBeanPostProcessor这个类,就会根据反射找出这个类有没有@DubboReference(@Reference)注解,有的话就构建一个动态搭理注入就可以了。

BeanPostProcessor在Spring Bean的扩展中扮演着重要的角色,是Spring Bean生命周期中很重要的一部分。正是因为有了BeanPostProcessor,你就可以在Bean创建过程中的任意一个阶段扩展自己想要的东西。

BeanFactoryPostProcessor

通过上面一节我们知道 BeanPostProcessor 是对Bean的处理,那么BeanFactoryPostProcessor很容易就猜到是对BeanFactory,也就是Spring容器的处理。

举个例子,如果我们想禁止循环依赖,那么就可以这么写。

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// 禁止循环依赖((DefaultListableBeanFactory) beanFactory).setAllowCircularReferences(false);}}

后面只需要将注入到Spring容器中就会生效。

BeanFactoryPostProcessor是可以对Spring容器做处理的,方法的入参就是Spring的容器,通过这个接口,就对容器进行为所欲为的操作。

Spring SPI机制

SPI全称为 (Service Provider Interface),是一种动态替换发现的机制,一种解耦非常优秀的思想,SPI可以很灵活的让接口和实现分离, 让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。

JDK有内置的SPI机制的实现ServiceLoader,Dubbo也有自己的SPI机制的实现ExtensionLoader,但是这里我们都不讲。。

SpringFactoriesLoader

Spring的SPI机制规定,配置文件必须在classpath路径下的META-INF文件夹内,文件名必须为spring.factories,文件内容为键值对,一个键可以有多个值,只需要用逗号分割就行,同时键值都需要是类的全限定名。但是键和值可以没有任何关系,当然想有也可以有。

show me the code

这里我自定义一个类,MyEnableAutoConfiguration作为键,值就是User

public class MyEnableAutoConfiguration {
}

spring.factories文件

com.sanyou.spring.extension.spi.MyEnableAutoConfiguration=com.sanyou.spring.extension.User

然后放在META-INF底下

测试:

public class Application {public static void main(String[] args) {List classNames = SpringFactoriesLoader.loadFactoryNames(MyEnableAutoConfiguration.class, MyEnableAutoConfiguration.class.getClassLoader());classNames.forEach(System.out::println);}}

结果:

com.sanyou.spring.extension.User

可以看出,通过SpringFactoriesLoader的确可以从spring.factories文件中拿到MyEnableAutoConfiguration键对应的值。

到这你可能说会,这SPI机制也没啥用啊。的确,我这个例子比较简单,拿到就是遍历,但是在Spring中,如果Spring在加载类的话使用SPI机制,那我们就可以扩展,接着往下看。

SpringBoot启动扩展点

SpringBoot项目在启动的过程中有很多扩展点,这里就来盘点一下几个常见的扩展点。

1、自动装配

说到SpringBoot的扩展点,第一时间肯定想到的就是自动装配机制,面试贼喜欢问,但是其实就是一个很简单的东西。当项目启动的时候,会去从所有的spring.factories文件中读取@EnableAutoConfiguration键对应的值,拿到配置类,然后根据一些条件判断,决定哪些配置可以使用,哪些不能使用。

spring.factories文件?键值?不错,自动装配说白了就是SPI机制的一种运用场景。

@EnableAutoConfiguration注解:

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {//忽略
}

我擦,这个注解也是使用@Import注解,而且配置类还实现了ImportSelector接口,跟前面也都对上了。在SpringBoot中,@EnableAutoConfiguration是通过@SpringBootApplication来使用的。

在AutoConfigurationImportSelector中还有这样一段代码

所以,这段代码也明显地可以看出,自动装配也是基于SPI机制实现的。

那么我想实现自动装配怎么办呢?很简单,只需两步。

第一步,写个配置类:

@Configuration
public class UserAutoConfiguration {@Beanpublic UserFactoryBean userFactoryBean() {return new UserFactoryBean();}}

这里我为了跟前面的知识有关联,配置了一个UserFactoryBean。

第二步,往spring.factories文件配置一下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.sanyou.spring.extension.springbootextension.UserAutoConfiguration

到这就已经实现了自动装配的扩展。

接下来进行测试:

@SpringBootApplication
public class Application {public static void main(String[] args) {ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class);User user = applicationContext.getBean(User.class);System.out.println("获取到的Bean为" + user);}}

运行结果:

调用 UserFactoryBean 的 getObject 方法生成 Bean:com.sanyou.spring.extension.User@3406472c
获取到的Bean为com.sanyou.spring.extension.User@3406472c

从运行结果可以看出,自动装配起了作用,并且虽然往容器中注入的Bean的class类型为UserFactoryBean,但是最终会调用UserFactoryBean的getObject的实现获取到User对象。

自动装配机制是SpringBoot的一个很重要的扩展点,很多框架在整合SpringBoot的时候,也都通过自动装配来的,实现项目启动,框架就自动启动的,这里我举个Mybatis整合SpringBoot。

2、PropertySourceLoader

PropertySourceLoader,这是干啥的呢?

我们都知道,在SpringBoot环境下,外部化的配置文件支持properties和yaml两种格式。但是,现在不想使用properties和yaml格式的文件,想使用json格式的配置文件,怎么办?

当然是基于该小节讲的PropertySourceLoader来实现的。

public interface PropertySourceLoader {//可以支持哪种文件格式的解析String[] getFileExtensions();// 解析配置文件,读出内容,封装成一个PropertySource结合返回回去List> load(String name, Resource resource) throws IOException;}

对于PropertySourceLoader的实现,SpringBoot两个实现

PropertiesPropertySourceLoader:可以解析properties或者xml结尾的配置文件

YamlPropertySourceLoader:解析以yml或者yaml结尾的配置文件

所以可以看出,要想实现json格式的支持,只需要自己实现可以用来解析json格式的配置文件的PropertySourceLoader就可以了。

动手来一个。

实现可以读取json格式的配置文件

实现这个功能,只需要两步就可以了

第一步:自定义一个PropertySourceLoader

JsonPropertySourceLoader,实现PropertySourceLoader接口

public class JsonPropertySourceLoader implements PropertySourceLoader {@Overridepublic String[] getFileExtensions() {//这个方法表明这个类支持解析以json结尾的配置文件return new String[]{"json"};}@Overridepublic List> load(String name, Resource resource) throws IOException {ReadableByteChannel readableByteChannel = resource.readableChannel();ByteBuffer byteBuffer = ByteBuffer.allocate((int) resource.contentLength());//将文件内容读到 ByteBuffer 中readableByteChannel.read(byteBuffer);//将读出来的字节转换成字符串String content = new String(byteBuffer.array());// 将字符串转换成 JSONObjectJSONObject jsonObject = JSON.parseObject(content);Map map = new HashMap<>(jsonObject.size());//将 json 的键值对读出来,放入到 map 中for (String key : jsonObject.keySet()) {map.put(key, jsonObject.getString(key));}return Collections.singletonList(new MapPropertySource("jsonPropertySource", map));}}
第二步:配置PropertySourceLoader

JsonPropertySourceLoader 已经有了,那么怎么用呢?当然是SPI机制了,SpringBoot对于配置文件的处理,就是依靠SPI机制,这也是能扩展的重要原因。

SpringBoot会先通过SPI机制加载所有PropertySourceLoader,然后遍历每个PropertySourceLoader,判断当前遍历的PropertySourceLoader,通过getFileExtensions获取到当前PropertySourceLoader能够支持哪些配置文件格式的解析,让后跟当前需要解析的文件格式进行匹配,如果能匹配上,那么就会使用当前遍历的PropertySourceLoader来解析配置文件。

PropertySourceLoader其实就属于策略接口,配置文件的解析就是策略模式的运用。

所以,只需要按照这种格式,在spring.factories文件中配置一下就行了。

org.springframework.boot.env.PropertySourceLoader=\
com.sanyou.spring.extension.springbootextension.propertysourceloader.JsonPropertySourceLoader

到此,其实就扩展完了,接下来就来测试一下。

测试

先创建一个application.json的配置文件

改造User

public class User {// 注入配置文件的属性@Value("${sanyou.username:}")private String username;
}

启动项目

@SpringBootApplication
public class Application {public static void main(String[] args) {ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class);User user = applicationContext.getBean(User.class);System.out.println("获取到的Bean为" + user + ",属性username值为:" + user.getUsername());}@Beanpublic User user() {return new User();}}

运行结果:

获取到的Bean为com.sanyou.spring.extension.User@481ba2cf,属性username值为:三友的java日记

成功将json配置文件的属性注入到User对象中。

至此,SpringBoot就支持了以json为结尾的配置文件格式。

相关内容

热门资讯

Harmony(鸿蒙)开发手机... Harmony环境使用Bee入门向导 一、添加jar包 将bee相关的3个jar包复制到entry包...
到底线程要怎么去编程?线程类有... 上篇文章我们介绍了到底什么是线程,线程与进程的区别,线程怎么去创建的五种...
linux环境下利用rsync... 文章目录前言插曲根据时间段同步按时间过滤文件使用 mtime 参数查找使用 newermt 进行更精...
学习周报3.19 文章目录前言文献阅读摘要简介问题定义方法结论克里金插值法(Kriging法࿰...
Java设计模式 03-原型模... 原型模式 一、克隆羊问题 现在有一只羊 tom,姓名为: tom, 年龄为࿱...
数据库select操作 插入数据时,一次插入多条数据,比多次插入多条同样的数据消耗时间少很多。 ...
搭建 Spring 源码阅读环... 1、下载gradle https://gradle.org/releases/ 解压到磁盘 2、配...
北斗星1.4排量百公里油耗(北... 本篇文章极速百科给大家谈谈北斗星1.4排量百公里油耗,以及北斗星14真实油耗72什么情况对应的知识点...
汽车安全性能排行榜(车辆安全性... 本篇文章极速百科给大家谈谈汽车安全性能排行榜,以及车辆安全性能排名对应的知识点,希望对各位有所帮助,...
门客是什么意思(君子不打上门客... 今天给各位分享门客是什么意思的知识,其中也会对君子不打上门客是什么意思进行解释,如果能碰巧解决你现在...
灶糖是啥(灶糖的功效和作用) ... 本篇文章极速百科给大家谈谈灶糖是啥,以及灶糖的功效和作用对应的知识点,希望对各位有所帮助,不要忘了收...
[CMake] CMake 进... CMake 进阶 代码生成 代码生成是指使用一些通用的描述文件,可以自动生成源代码&#...
文法的形式定义 一、序列的集合称为形式语言序列的集合称为形式语言二、形式语言的描述当语言是有穷集合时,...
人事部是干什么的(人力资源管理... 今天给各位分享人事部是干什么的的知识,其中也会对人力资源管理吃香吗进行解释,如果能碰巧解决你现在面临...
透明的反义词是什么(透明的反义... 本篇文章极速百科给大家谈谈透明的反义词是什么,以及透明的反义词是什么最佳答案对应的知识点,希望对各位...
清道夫鱼能吃吗(清道夫鱼能吃吗... 今天给各位分享清道夫鱼能吃吗的知识,其中也会对清道夫鱼能吃吗?有什么功效进行解释,如果能碰巧解决你现...
科研之友是什么(科研之友是干什... 本篇文章极速百科给大家谈谈科研之友是什么,以及科研之友是干什么的对应的知识点,希望对各位有所帮助,不...
Android framewo... 启动篇 android-12.0.0_r28\frameworks/base/services/ja...
Windows10及其Wind... 一、需求说明在日常实施运维工作中,通常需要设置指定的软件在Windows系统或Wind...
StampedLock 一、邮戳锁的介绍 邮戳锁(StampedLock)是一个java.util.concurrent.l...
宝马X5宝马X5最新报价-图片... 本篇文章极速百科给大家谈谈宝马X5宝马X5最新报价-图片-参数,以及宝马x5l报价及图片2021款对...
电机与减速机(电机与减速机如何... 本篇文章极速百科给大家谈谈电机与减速机,以及电机与减速机如何搭配对应的知识点,希望对各位有所帮助,不...
车标大全:这些汽车车标你全都认... 今天给各位分享车标大全:这些汽车车标你全都认识吗?的知识,其中也会对车标车标志大全及名字进行解释,如...
abcb式的成语(abcb式的... 本篇文章极速百科给大家谈谈abcb式的成语,以及abcb式的成语二年级对应的知识点,希望对各位有所帮...
string类(上) string类(上)1.标准库中的string类2.string类对象的...
七夕什么节(七夕什么节曰) 七... 今天给各位分享七夕什么节的知识,其中也会对七夕什么节曰进行解释,如果能碰巧解决你现在面临的问题,别忘...
高尔夫7代怎么样(高尔夫7怎么... 今天给各位分享高尔夫7代怎么样的知识,其中也会对高尔夫7怎么样14T进行解释,如果能碰巧解决你现在面...
打表出租车怎么收费(长沙打表出... 本篇文章极速百科给大家谈谈打表出租车怎么收费,以及长沙打表出租车怎么收费对应的知识点,希望对各位有所...
一大代表结局(13位一大代表的... 本篇文章极速百科给大家谈谈一大代表结局,以及13位一大代表的人生经历对应的知识点,希望对各位有所帮助...
关于实施企业项目管理系统的收益...   引言 在国外找了一篇文件,自觉总结的不错,这里分享给大家࿰...