Spring的那些开发小技巧(中)

news/2024/11/7 17:59:43/

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<String> 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<PropertySource<?>> 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<PropertySource<?>> 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<String, Object> 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为结尾的配置文件格式。


http://www.ppmy.cn/news/934409.html

相关文章

前端 js通过汉字实时识别出全拼

根据汉字识别出拼音 通过汉字实时识别出全拼效果直接上代码 通过汉字实时识别出全拼 在一次移动端项目中&#xff0c;因为是和银行对接项目&#xff0c;所以有了这一个需求&#xff0c;实时带出汉字全拼。用的库是VUX。 效果 直接上代码 <x-inputtitle"中文姓名&quo…

csapp实验bomb lab(反汇编技术与gdb调试)

一.实验目的 该实验要在linux环境下做&#xff0c;因为我的虚拟机VMware不能与宿主机之间传输文件、复制黏贴&#xff08;弄了好长时间也没弄成&#xff09;&#xff0c;所以实验包我是在虚拟机中登录官网下载的&#xff0c;下载地址为&#xff1a; CS:APP3e, Bryant and OHal…

常见行业缩略语

缩略语全称释义MMPMinimal Marketable Product最小適銷產品。保有最小量而適切的功能需求產品&#xff0c;可以滿足用戶基本需求和體驗。MVPMinimum Viable Product最小可行性產品。是可以讓目標用戶使用的前期產品&#xff0c;幫助開發團隊蒐集回饋&#xff0c;並從中學習。在…

dns服务器系统架构,详解 DNS 与 CoreDNS 的实现原理

原文链接:https://draveness.me/dns-coredns 【编者的话】域名系统(Domain Name System)是整个互联网的电话簿,它能够将可被人理解的域名翻译成可被机器理解 IP 地址,使得互联网的使用者不再需要直接接触很难阅读和理解的 IP 地址。 我们在这篇文章中的第一部分会介绍 DNS 的…

matlab ssd算法,【图像配准】基于灰度的模板匹配算法(一):MAD、SAD、SSD、MSD、NCC、SSDA、SATD算法...

简介: 本文主要介绍几种基于灰度的图像匹配算法:平均绝对差算法(MAD)、绝对误差和算法(SAD)、误差平方和算法(SSD)、平均误差平方和算法(MSD)、归一化积相关算法(NCC)、序贯相似性检测算法(SSDA)、hadamard变换算法(SATD)。下面依次对其进行讲解。 MAD算法 介绍 平均绝对差算…

Java根据国家二字码获取国家英文名称,中文名称实例

参考&#xff1a;https://blog.csdn.net/weixin_30872157/article/details/96948745 https://www.cnblogs.com/zhc-hnust/p/10280761.html package com.ppmath.mathanalytic.tool;import com.alibaba.excel.util.StringUtils;public class CountryUtil {/*** 根据国家二字码获…

前端js中文转拼音(例:张三转为ZhangSan)

如图&#xff0c;咱们需要实现中文汉字转成拼音&#xff0c;非中文汉字部分则保留原格式&#xff0c;兼容各类情况。 实际就是匹配字符编码转成相应的拼音&#xff0c;那么当然我们就需要对应的字符编码&#xff08;ChineseHelperStr.js&#xff09; 字符编码ChineseHelperStr…

wwid、uuid、lun、multipath、hba、udev总结

wwid、uuid、lun、multipath、hba、udev总结 wwid: scsi_id命令执行后&#xff0c;只有磁盘、存储盘才可以显示wwid&#xff0c;多路径的存储盘显示的wwid一样 本地磁盘分区、存储盘分区都没有wwid 存储盘分区后&#xff0c;存储盘本身的wwid不变 存储盘分区且格式化后&#xf…