Spring中最常用的11个扩展点
前言
我们一说到spring,可能第一个想到的是 IOC(控制反转) 和 AOP(面向切面编程)。
没错,它们是spring的基石,得益于它们的优秀设计,使得spring能够从众多优秀框架中脱颖而出。
除此之外,我们在使用spring的过程中,有没有发现它的扩展能力非常强。由于这个优势的存在,让spring拥有强大的包容能力,让很多第三方应用能够轻松投入spring的怀抱。比如:rocketmq、mybatis、redis等。
今天跟大家一起聊聊,在Spring中最常用的11个扩展点。

1.自定义拦截器
spring mvc拦截器根spring拦截器相比,它里面能够获取HttpServletRequest和HttpServletResponse等web对象实例。
spring mvc拦截器的顶层接口是:HandlerInterceptor,包含三个方法:
preHandle 目标方法执行前执行
postHandle 目标方法执行后执行
afterCompletion 请求完成时执行
为了方便我们一般情况会用HandlerInterceptor接口的实现类HandlerInterceptorAdapter类。
假如有权限认证、日志、统计的场景,可以使用该拦截器。
第一步,继承HandlerInterceptorAdapter类定义拦截器:
public class AuthInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {String requestUrl = request.getRequestURI();if (checkAuth(requestUrl)) {return true;}return false;}private boolean checkAuth(String requestUrl) {System.out.println("===权限校验===");return true;}
}
第二步,将该拦截器注册到spring容器:
@Configuration
public class WebAuthConfig extends WebMvcConfigurerAdapter {@Beanpublic AuthInterceptor getAuthInterceptor() {return new AuthInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new AuthInterceptor());}
}
第三步,在请求接口时spring mvc通过该拦截器,能够自动拦截该接口,并且校验权限。
2.获取Spring容器对象
在我们日常开发中,经常需要从Spring容器中获取Bean,但你知道如何获取Spring容器对象吗?
2.1 BeanFactoryAware接口
@Service
public class PersonService implements BeanFactoryAware {private BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}public void add() {Person person = (Person) beanFactory.getBean("person");}
}
实现BeanFactoryAware接口,然后重写setBeanFactory方法,就能从该方法中获取到spring容器对象。
2.2 ApplicationContextAware接口
@Service
public class PersonService2 implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}public void add() {Person person = (Person) applicationContext.getBean("person");}
}
实现ApplicationContextAware接口,然后重写setApplicationContext方法,也能从该方法中获取到spring容器对象。
2.3 ApplicationListener接口
@Service
public class PersonService3 implements ApplicationListener<ContextRefreshedEvent> {private ApplicationContext applicationContext;@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {applicationContext = event.getApplicationContext();}public void add() {Person person = (Person) applicationContext.getBean("person");}
}
3.全局异常处理
以前我们在开发接口时,如果出现异常,为了给用户一个更友好的提示,例如:
@RequestMapping("/test")
@RestController
public class TestController {@GetMapping("/add")public String add() {int a = 10 / 0;return "成功";}
}
如果不做任何处理请求add接口结果直接报错:

what?用户能直接看到错误信息?
这种交互方式给用户的体验非常差,为了解决这个问题,我们通常会在接口中捕获异常:
@GetMapping("/add")
public String add() {String result = "成功";try {int a = 10 / 0;} catch (Exception e) {result = "数据异常";}return result;
}
接口改造后,出现异常时会提示:“数据异常”,对用户来说更友好。
看起来挺不错的,但是有问题。。。
如果只是一个接口还好,但是如果项目中有成百上千个接口,都要加上异常捕获代码吗?
答案是否定的,这时全局异常处理就派上用场了:RestControllerAdvice。
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)public String handleException(Exception e) {if (e instanceof ArithmeticException) {return "数据异常";}if (e instanceof Exception) {return "服务器内部异常";}retur nnull;}
}
只需在handleException方法中处理异常情况,业务接口中可以放心使用,不再需要捕获异常(有人统一处理了)。真是爽歪歪。
4.类型转换器
spring目前支持3中类型转换器:
Converter<S,T>:将 S 类型对象转为 T 类型对象
ConverterFactory<S, R>:将 S 类型对象转为 R 类型及子类对象
GenericConverter:它支持多个source和目标类型的转化,同时还提供了source和目标类型的上下文,这个上下文能让你实现基于属性上的注解或信息来进行类型转换。
这3种类型转换器使用的场景不一样,我们以Converter<S,T>为例。假如:接口中接收参数的实体对象中,有个字段的类型是Date,但是实际传参的是字符串类型:2021-01-03 10:20:15,要如何处理呢?
第一步,定义一个实体User:
@Data
public class User {private Long id;private String name;private Date registerDate;
}
第二步,实现Converter接口:
public class DateConverter implements Converter<String, Date> {private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Overridepublic Date convert(String source) {if (source != null && !"".equals(source)) {try {simpleDateFormat.parse(source);} catch (ParseException e) {e.printStackTrace();}}return null;}
}
第三步,将新定义的类型转换器注入到spring容器中:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(new DateConverter());}
}
第四步,调用接口
@RequestMapping("/user")
@RestController
public class UserController {@RequestMapping("/save")public String save(@RequestBody User user) {return "success";}
}
请求接口时User对象中registerDate字段会被自动转换成Date类型。
5.导入配置
有时我们需要在某个配置类中引入另外一些类,被引入的类也加到spring容器中。这时可以使用@Import注解完成这个功能。
如果你看过它的源码会发现,引入的类支持三种不同类型。
但是我认为最好将普通类和@Configuration注解的配置类分开讲解,所以列了四种不同类型:

5.1 普通类
这种引入方式是最简单的,被引入的类会被实例化bean对象。
public class A {
}@Import(A.class)
@Configuration
public class TestConfiguration {
}
通过@Import注解引入A类,spring就能自动实例化A对象,然后在需要使用的地方通过@Autowired注解注入即可:
@Autowired
private A a;
是不是挺让人意外的?不用加@Bean注解也能实例化bean。
5.2 配置类
这种引入方式是最复杂的,因为@Configuration注解还支持多种组合注解,比如:
@Import
@ImportResource
@PropertySource等。
public class A {
}public class B {
}@Import(B.class)
@Configuration
public class AConfiguration {@Beanpublic A a() {return new A();}
}@Import(AConfiguration.class)
@Configuration
public class TestConfiguration {
}
通过@Import注解引入@Configuration注解的配置类,会把该配置类相关@Import、@ImportResource、@PropertySource等注解引入的类进行递归,一次性全部引入。
5.3 ImportSelector
这种引入方式需要实现ImportSelector接口:
public class AImportSelector implements ImportSelector {private static final String CLASS_NAME = "com.sue.cache.service.test13.A";public String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{CLASS_NAME};}
}@Import(AImportSelector.class)
@Configuration
public class TestConfiguration {
}
这种方式的好处是selectImports方法返回的是数组,意味着可以同时引入多个类,还是非常方便的。
5.4 ImportBeanDefinitionRegistrar
这种引入方式需要实现ImportBeanDefinitionRegistrar接口:
public class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(A.class);registry.registerBeanDefinition("a", rootBeanDefinition);}
}@Import(AImportBeanDefinitionRegistrar.class)
@Configuration
public class TestConfiguration {
}
这种方式是最灵活的,能在registerBeanDefinitions方法中获取到BeanDefinitionRegistry容器注册对象,可以手动控制BeanDefinition的创建和注册。
6.项目启动时
有时候我们需要在项目启动时定制化一些附加功能,比如:加载一些系统参数、完成初始化、预热本地缓存等,该怎么办呢?
好消息是springboot提供了:
CommandLineRunner
ApplicationRunner
这两个接口帮助我们实现以上需求。
它们的用法还是挺简单的,以ApplicationRunner接口为例:
@Component
public class TestRunner implements ApplicationRunner {@Autowiredprivate LoadDataService loadDataService;public void run(ApplicationArguments args) throws Exception {loadDataService.load();}
}
实现ApplicationRunner接口,重写run方法,在该方法中实现自己定制化需求。
如果项目中有多个类实现了ApplicationRunner接口,他们的执行顺序要怎么指定呢?
答案是使用@Order(n)注解,n的值越小越先执行。当然也可以通过@Priority注解指定顺序。
7.修改BeanDefinition
Spring IOC在实例化Bean对象之前,需要先读取Bean的相关属性,保存到BeanDefinition对象中,然后通过BeanDefinition对象,实例化Bean对象。
如果想修改BeanDefinition对象中的属性,该怎么办呢?
答:我们可以实现BeanFactoryPostProcessor接口。
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);beanDefinitionBuilder.addPropertyValue("id", 123);beanDefinitionBuilder.addPropertyValue("name", "苏三说技术");defaultListableBeanFactory.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition());}
}
在postProcessBeanFactory方法中,可以获取BeanDefinition的相关对象,并且修改该对象的属性。
8.初始化Bean前后
有时,你想在初始化Bean前后,实现一些自己的逻辑。
这时可以实现:BeanPostProcessor接口。
该接口目前有两个方法:
postProcessBeforeInitialization 该在初始化方法之前调用。
postProcessAfterInitialization 该方法再初始化方法之后调用。
例如:
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof User) {((User) bean).setUserName("苏三说技术");}return bean;}
}
如果spring中存在User对象,则将它的userName设置成:苏三说技术。
其实,我们经常使用的注解,比如:@Autowired、@Value、@Resource、@PostConstruct等,是通过AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor实现的。
9.初始化方法
目前spring中使用比较多的初始化bean的方法有:
使用@PostConstruct注解
实现InitializingBean接口
9.1 使用@PostConstruct注解
@Service
public class AService {@PostConstructpublic void init() {System.out.println("===初始化===");}
}
在需要初始化的方法上增加@PostConstruct注解,这样就有初始化的能力。
9.2 实现InitializingBean接口
@Service
public class BService implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("===初始化===");}
}
实现InitializingBean接口,重写afterPropertiesSet方法,该方法中可以完成初始化功能。
10.关闭容器前
有时候,我们需要在关闭spring容器前,做一些额外的工作,比如:关闭资源文件等。
这时可以实现DisposableBean接口,并且重写它的destroy方法:
@Service
public class DService implements InitializingBean, DisposableBean {@Overridepublic void destroy() throws Exception {System.out.println("DisposableBean destroy");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("InitializingBean afterPropertiesSet");}
}
这样spring容器销毁前,会调用该destroy方法,做一些额外的工作。
通常情况下,我们会同时实现InitializingBean和DisposableBean接口,重写初始化方法和销毁方法。
11.自定义作用域
我们都知道spring默认支持的Scope只有两种:
singleton 单例,每次从spring容器中获取到的bean都是同一个对象。
prototype 多例,每次从spring容器中获取到的bean都是不同的对象。
spring web又对Scope进行了扩展,增加了:
RequestScope 同一次请求从spring容器中获取到的bean都是同一个对象。
SessionScope 同一个会话从spring容器中获取到的bean都是同一个对象。
即便如此,有些场景还是无法满足我们的要求。
比如,我们想在同一个线程中从spring容器获取到的bean都是同一个对象,该怎么办?
这就需要自定义Scope了。
第一步实现Scope接口:
public class ThreadLocalScope implements Scope {private static final ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal();@Overridepublic Object get(String name, ObjectFactory<?> objectFactory) {Object value = THREAD_LOCAL_SCOPE.get();if (value != null) {return value;}Object object = objectFactory.getObject();THREAD_LOCAL_SCOPE.set(object);return object;}@Overridepublic Object remove(String name) {THREAD_LOCAL_SCOPE.remove();return null;}@Overridepublic void registerDestructionCallback(String name, Runnable callback) {}@Overridepublic Object resolveContextualObject(String key) {return null;}@Overridepublic String getConversationId() {return null;}
}
第二步将新定义的Scope注入到spring容器中:
@Component
public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {beanFactory.registerScope("threadLocalScope", new ThreadLocalScope());}
}
第三步使用新定义的Scope:
@Scope("threadLocalScope")
@Service
public class CService {public void add() {}
}
相关文章:

Spring中最常用的11个扩展点
前言我们一说到spring,可能第一个想到的是 IOC(控制反转) 和 AOP(面向切面编程)。没错,它们是spring的基石,得益于它们的优秀设计,使得spring能够从众多优秀框架中脱颖而出。除此之外…...
网络协议丨HTTPS是什么?
我们都知道HTTP协议,那什么是HTTPS? 我们都知道,HTTP有两个缺点——“明文”和“不安全”仅凭 HTTP 自身是无力解决的,需要引入新的 HTTPS 协议。 由于 HTTP 天生“明文”的特点,整个传输过程完全透明,任…...
Anaconda常用命令总结,anaconda、conda、miniconda的关系
Anaconda、conda、miniconda的关系 Anaconda Anaconda 是一个用于数据科学,机器学习和深度学习的开源软件包管理系统,其中包括了许多流行的 Python 包和工具Anaconda主要用于科学计算和数据分析。 conda Conda 是 Anaconda 中的包管理器,…...

【蓝桥杯入门到入土】最基础的数组你真的掌握了吗?
文章目录一:数组理论基础二:数组知识点总结三:数组这种数据结构的优点和缺点是什么?四:实战解题1. 移除元素暴力解法双指针法2.有序数组的平方暴力解法双指针法最后说一句一:数组理论基础 首先要知道数组在…...
Java Set系列集合(Collections集合工具类、可变参数)
目录Set系列集系概述HashSet集合元素无序的底层原理:哈希表HashSet集合元素去重复的底层原理LinkedHashSet有序实现原理TreeSetCollection集合总结可变参数Collections集合工具类Set系列集系概述 Set系列集合特点 无序:存取顺序不一致不重复࿱…...
chromium构建原生AS项目-记录1
构建的chromium版本:待补充重要说明:so文件加载的过程文件:base_java.jar包文件路径:org.chromium.base.library_loader.LibraryLoader方法:loadAlreadyLocked(Context context)line166 :Native…...

Mybatis-Plus 开发提速器:mybatis-plus-generator-ui
Mybatis-Plus 开发提速器:mybatis-plus-generator-ui 1.简介 github地址 : https://github.com/davidfantasy/mybatis-plus-generator-ui 提供交互式的Web UI用于生成兼容mybatis-plus框架的相关功能代码,包括Entity,Mapper,Mapper.xml,Se…...
李迟2023年02月工作生活总结
本文为 2023 年 2 月工作生活总结。 研发编码 Linux Go 某工程使用到一些数据的统计,为方便,使用 map 存储数量,由于其是无序的,输出的列表顺序不固定,将其和历史版本对比不方便,所以需要将 key 排序再输…...
【Python百日进阶-Web开发-Vue3】Day542 - Vue3 商城后台 02:Windi CSS 与 Vue Router4
文章目录 一、WindiCSS 初始1.1 WindiCSS 是什么?1.2 为什么选择 Windi CSS?1.3. 基础用法1.4 集成二、简单按钮2.1 设置背景色2.2 设置字体颜色和上下左右padding2.3 设置圆角2.4 鼠标悬浮,颜色加深2.5 鼠标划入动画2.6 设置阴影2.7 @apply 抽离class代码到 style 块中三、…...

Jupyter Lab | “丢下R,一起来快乐地糟蹋服务器!”
写作前面 工具永远只是为了帮助自己提升工作效率 —— 沃兹基硕得 所以说,为什么要使用jupyterlab呢?当然是因为基于服务器来处理数据就可以使劲造了,而且深切地感觉到,“R这玩意儿是人用的吗”。 jupyter-lab | mamba安装以及…...

分页与分段
前面我们分析了虚拟地址和物理地址 我们这里进行一个简单的分析 这个是程序运行时的地址映射 那么这些碎片,我们现在的操作系统究竟如何处理呢? 我们再引入一个实际问题 我们如何把右边的进程p塞入左边的内存空间里面 有一种方法将p5kill掉ÿ…...

【UE4 】制作螺旋桨飞机
一、素材资源链接:https://pan.baidu.com/s/1xPVYYw05WQ6FABq_ZxFifg提取码:ivv8二、课程视频链接https://www.bilibili.com/video/BV1Bb411h7qw/?spm_id_from333.337.search-card.all.click&vd_source36a3e35639c44bb339f59760641390a8三、最终效果…...
五.家庭:亲情背后有理性
5.1经济学帝国主义【单选题】以下属于经济学研究范围的是( )。A、约束最优化B、稀缺资源配置C、价格竞争与非价格竞争D、以上都对我的答案:D【多选题】为何有学科分类?A、分工B、专业化C、累积创新D、科技进步我的答案:ABCD【判断…...

【Leedcode】栈和队列必备的面试题(第三期)
【Leedcode】栈和队列必备的面试题(第三期) 文章目录【Leedcode】栈和队列必备的面试题(第三期)一、题目(用两个栈实现队列)二、思路图解1.定义两个栈2.初始化两个数组栈3. 将数据放入pushST数组栈中4.删除…...

《图机器学习》-GNN Augmentation and Training
GNN Augmentation and Training一、Graph Augmentation for GNNs1、Feature Augmentation2、Structure augmentation3、Node Neighborhood Sampling一、Graph Augmentation for GNNs 之前的假设: Raw input graph computational graph,即原始图等于计算…...

【Node.js算法题】数组去重、数组删除元素、数组排序、字符串排序、字符串反向、字符串改大写 、数组改大写、字符替换
文章目录前言数组去重数组删除元素数组排序字符串排序字符串反向字符串改大写数组改大写字符替换字符替换运行结果: 总结前言 本期文章是js的一些算法题,包括…...

Win10系统开始菜单无法点击解决方法分享
Win10系统开始菜单无法点击解决方法分享。有用户电脑一开机之后,就出现了开始菜单无法正常点击的情况。我们很多设置项都是通过开始菜单来进行开启的。那么这个功能无法点击了怎么办呢?接下来我们一起来看看以下的解决方法分享吧。 方法一: 1…...
libmodbus从linux访问window上的服务超时问题
window:使用EasyModbusTCP Server Simulator 作为服务。linux:程序:#include <stdio.h> #include <modbus/modbus.h>int main() {modbus_t *ctx;uint16_t holding_registers[1];// Create a new Modbus TCP contextctx modbus_new_tcp(&quo…...

挑战图像处理100问(26)——双线性插值
双线性插值是一种常用的图像插值方法,用于将低分辨率的图像放大到高分辨率。它基于一个假设:在两个相邻像素之间的值是线性的。 双线性插值考察444邻域的像素点,并根据距离设置权值。虽然计算量增大使得处理时间变长,但是可以有效…...
NXP iMX8系列处理器Pin Multiplexing定义说明
By Toradex秦海1). 简介为了提高处理器的设计灵活性和可用性,NXP的所有i.MX系列处理器都配备了基于 IOMUX Controller (IOMUXC)和IOMUX来使能Pin Mux功能,使得一个特定的IO管脚可以选择不同的可能多达8种的功能定义模块(ALT0, ALT1, ALT2, ALT3...)&…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...

基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...