掌握 Spring 框架这 10 个扩展点,让你的能力更上一层楼
mhr18 2025-08-01 19:05 3 浏览 0 评论
当我们提到 Spring 时,或许首先映入脑海的是 IOC(控制反转)和 AOP(面向切面编程)。它们可以被视为 Spring 的基石。正是凭借其出色的设计,Spring 才能在众多优秀框架中脱颖而出。
Spring 具有很强的扩展性。许多第三方应用程序,如 rocketmq、mybatis、redis 等,都可以轻松集成到 Spring 系统中。让我们一起来看看 Spring 中最常用的十个扩展点。
1. 全局异常处理
过去,在开发接口时,如果发生异常,我们通常需要给用户一个更友好的提示。但如果不进行错误处理,例如:
@RequestMapping("/test")
@RestController
public class TestController {
@GetMapping("/division")
public String division(@RequestParam("a") int a, @RequestParam("b")int b) {
return String.valueOf(a / b);
}
}
这是一个计算 a/b 结果的方法,通过
127.0.0.1:8080/test/division?a=10&b=0 访问后会出现以下结果:
什么?用户能直接看到如此详细的错误信息吗?
这种报错方式给用户带来了非常糟糕的体验。为了解决这个问题,我们通常在接口中捕获异常。
@GetMapping("/division")
public String division(@RequestParam("a") int a, @RequestParam("b") int b) {
String result = "";
try {
result = String.valueOf(a / b);
} catch (ArithmeticException e) {
result = "params error";
}
return result;
}
接口改造后,当发生异常时,会提示:“params error”,用户体验会更好。
如果只是一个接口,那没问题。但如果项目中有成百上千个接口,我们是否需要为所有接口添加异常处理代码呢?
肯定不能这样做的。这时,全局异常处理就派上用场了:RestControllerAdvice。
@RestControllerAdvice
publicclass GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleException(Exception e) {
if (e instanceof ArithmeticException) {
return"params error";
}
if (e instanceof Exception) {
return"Internal server exception";
}
returnnull;
}
}
只需在 handleException 方法中处理异常情况。业务接口可以放心使用,不再需要捕获异常(遵循统一的处理逻辑)。
2. 自定义拦截器
与 Spring 拦截器相比,Spring MVC 拦截器可以在内部获取 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)) {
returntrue;
}
returnfalse;
}
private boolean checkAuth(String requestUrl) {
System.out.println("===Authority Verification===");
returntrue;
}
}
第二步,在 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 可以通过此拦截器自动拦截接口并验证权限。
3. 获取 Spring 容器对象
在日常开发中,我们经常需要从 Spring 容器中获取 Beans。但是你知道如何获取 Spring 容器对象吗?
3.1 BeanFactoryAware 接口
@Service
public class StudentService implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public void add() {
Student student = (Student) beanFactory.getBean("student");
}
}
实现 BeanFactoryAware 接口,然后重写 setBeanFactory 方法。从这个方法中,可以获取 Spring 容器对象。
3.2 ApplicationContextAware 接口
@Service
public class StudentService2 implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void add() {
Student student = (Student) applicationContext.getBean("student");
}
}
4. 导入配置
有时我们需要在某个配置类中导入其他一些类,并且导入的类也会被添加到 Spring 容器中。此时,可以使用@Import 注解来完成此功能。
如果你看过它的源代码,会发现导入的类支持三种不同的类型。
然而,我认为最好将普通类和带有@Configuration 注解的配置类分开解释。因此,列出了四种不同的类型:
4.1 导入普通类
这种导入方式最简单。导入的类将被实例化为一个 bean 对象。
public class A {
}
@Import(A.class)
@Configuration
public class TestConfiguration {
}
通过@Import 注解导入类 A,Spring 可以自动实例化对象 A。然后,可以在需要的地方通过@Autowired 注解进行注入:
@Autowired
private A a;
是不是很神奇?不需要添加@Bean 注解就可以实例化对象。
4.2 导入带有@Configuration 注解的配置类
这种导入方式最复杂,因为@Configuration 注解还支持多种组合注解,例如:
- @Import
- @ImportResource
- @PropertySource 等
public class A {
}
publicclass B {
}
@Import(B.class)
@Configuration
public class AConfiguration {
@Bean
public A a() {
returnnew A();
}
}
@Import(AConfiguration.class)
@Configuration
public class TestConfiguration {
}
通过@Import 注解导入一个带有@Configuration 注解的配置类,与该配置类相关的@Import、@ImportResource 和@PropertySource 等注解导入的所有类将一次性全部导入。
4.3 ImportSelector
这种导入方式需要实现 ImportSelector 接口:
public class AImportSelector implements ImportSelector {
private static final String CLASS_NAME = "com.demo.cache.service.A";
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{CLASS_NAME};
}
}
@Import(AImportSelector.class)
@Configuration
public class TestConfiguration {
}
这种方法的优点是 selectImports 方法返回一个数组,这意味着可以非常方便的导入多个类。
4.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 {
}
5. 项目启动时的附加功能
有时我们需要在项目启动时自定义一些附加逻辑,例如加载一些系统参数、资源初始化、预热本地缓存等。我们该怎么做呢?Spring Boot 提供了两个接口来帮助我们实现上述要求:
- CommandLineRunner
- ApplicationRunner
它们的用法非常简单。以 ApplicationRunner 接口为例:
@Component
publicclass MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// 在这里编写项目启动时需要执行的代码
System.out.println("项目启动时执行附加功能,加载系统参数...");
// 假设这里从配置文件中加载系统参数并进行处理
Properties properties = new Properties();
try (InputStream inputStream = new FileInputStream("application.properties")) {
properties.load(inputStream);
String systemParam = properties.getProperty("system.param");
System.out.println("加载的系统参数值为:" + systemParam);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,我们实现了 ApplicationRunner 接口,并重写了 run 方法。在 run 方法中,我们可以编写在项目启动时需要执行的附加功能代码,例如加载系统参数、初始化资源、预热缓存等。这里只是简单地模拟了从配置文件中加载系统参数并打印出来,实际应用中可以根据具体需求进行更复杂的操作。
当项目启动时,Spring Boot 会自动检测并执行实现了 ApplicationRunner 或 CommandLineRunner 接口的类中的 run 方法,从而实现项目启动时的附加功能。
这两个接口的区别在于参数类型不同,ApplicationRunner 的 run 方法参数是 ApplicationArguments,它提供了更多关于应用程序参数的信息,而 CommandLineRunner 的 run 方法参数是原始的字符串数组,直接包含了命令行参数。根据具体需求可以选择使用其中一个接口来实现项目启动时的附加功能。
6. 修改 BeanDefinition
在实例化 Bean 对象之前,Spring IOC 需要先读取 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", "Dylan Smith");
defaultListableBeanFactory.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition());
}
}
在 postProcessBeanFactory 方法中,可以获取 BeanDefinition 的相关对象并修改该对象的属性。
7. 初始化方法
目前,Spring 中比较常用的初始化 bean 的方法有:
- 使用@PostConstruct 注解。
- 实现 InitializingBean 接口。
7.1 使用@PostConstruct 注解
@Service
public class AService {
@PostConstruct
public void init() {
System.out.println("===Initializing===");
}
}
在需要初始化的方法上添加@PostConstruct 注解。这样,它就具有了初始化的能力。
7.2 实现 InitializingBean 接口
@Service
public class BService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("===Initializing===");
}
}
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("Dylan Smith");
}
return bean;
}
}
如果 Spring 中有一个 User 对象,将其 userName 设置为:Dylan Smith。
实际上,我们经常使用的注解,如@Autowired、@Value、@Resource、@PostConstruct 等,都是通过
AutowiredAnnotationBeanPostProcessor 和
CommonAnnotationBeanPostProcessor 实现的。
9. 在关闭容器之前添加操作
有时,我们需要在关闭 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 接口,并覆盖初始化方法和销毁方法。
10. 自定义作用域
我们都知道,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 {
privatestaticfinal 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();
returnnull;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
returnnull;
}
@Override
public String getConversationId() {
returnnull;
}
}
第二步,将新定义的“Scope”注入到 Spring 容器中:
@Component
public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope("threadLocalScope", new ThreadLocalScope());
}
}
第三步,使用新定义的“Scope”:
@Scope("threadLocalScope")
@Service
public class CService {
public void add() {
}
}
总结
好了,今天的内容就到这里。对 Spring 框架感兴趣的读者可以关注我,后续会分享更多有关 Spring 的相关知识。
相关推荐
- 保持SSH隧道活跃:一个实用的Bash监控脚本
-
引言如果您正在使用AWSDocumentDB或任何位于堡垒主机后面的云托管服务等远程资源,您可能正在使用SSH隧道来安全地访问它们。虽然设置SSH隧道很简单,但保持其活跃状态并监控其状态可能会有些棘...
- 京东大佬问我,为什么说连接池是微服务的关键,你是如何理解的?
-
京东大佬问我,为什么说连接池是微服务的关键,你是如何理解的?我应该如何理解。首先,我需要回忆一下连接池和微服务的基本概念,然后思考它们在微服务架构中的作用和重要性。连接池,数据库连接池,用来管理数据库...
- OOM 血案:5 小时绝地求生,MAT+Arthas 终极排查指南
-
一、血案现场:线上服务突然暴毙2025年4月12日凌晨3点15分,服务突发大规模OOM,三个Pod在10分钟内连续崩溃,Prometheus告警显示JVM堆内存使用率...
- 记Tomcat优化方案
-
Tomcat服务吞吐量评估方案问题:评估方案在一台8核16G的linux服务器上,使用tomcat容器部署服务。在正常情况下如何评估这个tomcat服务可处理的连接数,即服务的吞吐量,请在正常情况下考...
- Java高级面试,常见数据结构的实现原理详细说明及面试总结
-
一、List接口实现类1.ArrayList底层结构:动态数组(Object[]数组)。核心原理:o动态扩容:初始容量为10(JDK1.8),当元素超过容量时,新容量为原容量的1.5倍(old...
- SpringBoot敏感配置项加密与解密实战
-
一、为什么要加密配置?先说说SpringBoot的配置加载机制。我们知道,SpringBoot支持多种配置加载方式,优先级从高到低大概是:命令行参数环境变量application-{profile}....
- 【面试题】nacos 配置管理类型-主配置、共享配置、扩展配置
-
nacos配置管理类型-主配置、共享配置、扩展配置Nacos的配置管理支持多种类型,其中共享配置及其扩展机制(如shared-configs和extension-configs)是微服...
- Spring Boot 的 RedisAutoConfiguration 配置:自动装配到自定义扩展
-
在SpringBoot开发中,Redis作为高性能缓存和分布式数据存储方案被广泛使用。而RedisAutoConfiguration作为SpringBoot自动装配体系的重要组成部分,能...
- Docker图像处理:扩展您的优化工作流程
-
随着应用程序的增长和图像处理需求的增加,传统的优化方法遇到了扩展瓶颈。内存限制、环境不一致和处理瓶颈将图像优化从一个已解决的问题变成了生产环境的噩梦。Docker改变了游戏规则。通过容器化图像处理工作...
- 掌握 Spring 框架这 10 个扩展点,让你的能力更上一层楼
-
当我们提到Spring时,或许首先映入脑海的是IOC(控制反转)和AOP(面向切面编程)。它们可以被视为Spring的基石。正是凭借其出色的设计,Spring才能在众多优秀框架中脱颖而出...
- 简简单单在线文件浏览的功能搞起来很头疼
-
您的系统支持在线预览文件吗?一个小小的问题,背后是无数程序员的爆肝研究,有人说了,我平时打开个文件不是很容易吗?其实不然。文件格式代表着软件行业的底层、高端产出,也代表着经久不衰的使用场景,也是我国底...
- 没硬盘、网盘也能看片自由!NAS一键部署MoonTV,随时随地爽看。
-
本内容来源于@什么值得买APP,观点仅代表作者本人|作者:羊刀仙有没有一个应用服务,能满足既没有足够预算购置硬盘,也不想依托网盘的朋友的家庭观影需求?之前我介绍过LibreTV,本篇再来看看另一个更...
- 阿里云ECS代理商:如何使用ECS部署Node.js应用?
-
Node.js作为一种高性能、事件驱动的JavaScript运行环境,广泛用于构建实时通信、微服务接口、后台管理系统等现代Web应用。而阿里云ECS服务器以高可用性、灵活配置、安全稳定等优势,为部署N...
- 阿里云数据库代理商:如何提高数据库的查询效率?
-
在现代企业应用中,数据库查询效率对整体系统性能的影响巨大。特别是随着数据量的不断增加,如何提升数据库查询的响应速度,成为了数据库优化的关键任务。阿里云提供了一系列工具和策略,帮助用户提升数据库的查询效...
- 阿里云代理商:阿里云G6ne实例如何承载1.4亿QPS?
-
一、阿里云G6ne实例概述1.1G6ne实例的背景与定位阿里云G6ne实例是基于阿里云自主研发的“飞天”架构设计的高性能云服务器实例,专为大规模、需要高IOPS和低延迟的业务场景设计。它采用了更强大的...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle主从同步 (56)
- oracle 乐观锁 (53)
- redis 命令 (83)
- php redis (97)
- redis 存储 (67)
- redis 锁 (74)
- 启动 redis (73)
- redis 时间 (60)
- redis 删除 (69)
- redis内存 (64)
- redis并发 (53)
- redis 主从 (71)
- redis同步 (53)
- redis结构 (53)
- redis 订阅 (54)
- redis 登录 (62)
- redis 面试 (58)
- redis问题 (54)
- 阿里 redis (67)
- redis的缓存 (57)
- lua redis (59)
- redis 连接池 (64)