聊聊 Spring 中最常用的 11 个扩展点?

发布时间:2026/11/5 17:04:24

聊聊 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 { Override public 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 { Bean public AuthInterceptor getAuthInterceptor() { return new AuthInterceptor(); } Override public 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; Override public 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; Override public 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 ApplicationListenerContextRefreshedEvent { private ApplicationContext applicationContext; Override public 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中类型转换器ConverterS,T将 S 类型对象转为 T 类型对象ConverterFactoryS, R将 S 类型对象转为 R 类型及子类对象GenericConverter它支持多个source和目标类型的转化同时还提供了source和目标类型的上下文这个上下文能让你实现基于属性上的注解或信息来进行类型转换。这3种类型转换器使用的场景不一样我们以ConverterS,T为例。假如接口中接收参数的实体对象中有个字段的类型是Date但是实际传参的是字符串类型2021-01-03 10:20:15要如何处理呢第一步定义一个实体UserData public class User { private Long id; private String name; private Date registerDate; }第二步实现Converter接口public class DateConverter implements ConverterString, Date { private SimpleDateFormat simpleDateFormat new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); Override public 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 { Override public 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注解还支持多种组合注解比如ImportImportResourcePropertySource等。public class A { } public class B { } Import(B.class) Configuration public class AConfiguration { Bean public 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 { Override public 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提供了CommandLineRunnerApplicationRunner这两个接口帮助我们实现以上需求。它们的用法还是挺简单的以ApplicationRunner接口为例Component public class TestRunner implements ApplicationRunner { Autowired private LoadDataService loadDataService; public void run(ApplicationArguments args) throws Exception { loadDataService.load(); } }实现ApplicationRunner接口重写run方法在该方法中实现自己定制化需求。如果项目中有多个类实现了ApplicationRunner接口他们的执行顺序要怎么指定呢答案是使用Order(n)注解n的值越小越先执行。当然也可以通过Priority注解指定顺序。7.修改BeanDefinitionSpring IOC在实例化Bean对象之前需要先读取Bean的相关属性保存到BeanDefinition对象中然后通过BeanDefinition对象实例化Bean对象。如果想修改BeanDefinition对象中的属性该怎么办呢答我们可以实现BeanFactoryPostProcessor接口。Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { Override public 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 { Override public 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 { PostConstruct public void init() { System.out.println(初始化); } }在需要初始化的方法上增加PostConstruct注解这样就有初始化的能力。9.2 实现InitializingBean接口Service public class BService implements InitializingBean { Override public void afterPropertiesSet() throws Exception { System.out.println(初始化); } }实现InitializingBean接口重写afterPropertiesSet方法该方法中可以完成初始化功能。10.关闭容器前有时候我们需要在关闭spring容器前做一些额外的工作比如关闭资源文件等。这时可以实现DisposableBean接口并且重写它的destroy方法Service public class DService implements InitializingBean, DisposableBean { Override public void destroy() throws Exception { System.out.println(DisposableBean destroy); } Override public 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(); Override public 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; } Override public Object remove(String name) { THREAD_LOCAL_SCOPE.remove(); return null; } Override public void registerDestructionCallback(String name, Runnable callback) { } Override public Object resolveContextualObject(String key) { return null; } Override public String getConversationId() { return null; } }第二步将新定义的Scope注入到spring容器中Component public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor { Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerScope(threadLocalScope, new ThreadLocalScope()); } }第三步使用新定义的ScopeScope(threadLocalScope) Service public class CService { public void add() { } }
延伸阅读

更多相关文章

2026/10/28 7:05:45

Java比价网Spider工程化实战:从数据模型到反爬与调度

简介:这是一份用Java语言实现的比价网站爬虫开源项目,面向需要构建比价数据采集与分析系统的开发者,适合爬虫技术学习、二次开发和项目实战。压缩包共包含2000个文件,大小约122.75MB,文件类型以JavaScript、HTML、Java…

2026/11/5 14:04:54

CTF中RSA Wiener攻击原理与实战技巧

1. CTF中的RSA Wiener攻击实战解析最近在准备一场CTF比赛时,遇到了一道典型的RSA Wiener攻击题目。这种攻击方式针对的是当RSA私钥指数d过小时的情况,通过连分数展开的方法可以快速破解密钥。下面我将详细记录解题过程,并分享一些实战中的经验…

2026/11/5 16:51:33

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被…

2026/11/5 16:26:13

Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑