找回密码
 立即注册
首页 业界区 业界 SpringBoot扩展点全攻略:让你的代码像积木一样灵活组装 ...

SpringBoot扩展点全攻略:让你的代码像积木一样灵活组装

迁岂罚 6 天前
SpringBoot扩展点全攻略:让你的代码像积木一样灵活组装

小李正在开发一个电商系统,老板突然说:"我们要在用户登录时发送短信通知,在订单支付后要积分奖励,在系统启动时要预热缓存..."小李一脸懵逼:这么多需求,到处改代码?不对!Spring Boot早就为我们准备好了"扩展点"这个神器,让你的代码像搭积木一样灵活!今天就让我们一起探索Spring Boot中那些强大又实用的扩展点!
一、什么是扩展点?为什么要用它?

扩展点的本质

扩展点就像是代码中预留的"插座",当你需要新功能时,直接"插"一个组件进去就行,而不用动原有代码。
传统做法 VS 扩展点做法:
  1. // ❌ 传统做法:到处修改代码
  2. @Service
  3. public class UserService {
  4.     public void login(User user) {
  5.         // 登录逻辑
  6.         validateUser(user);
  7.         updateLoginTime(user);
  8.       
  9.         // 新需求来了,要发短信
  10.         sendSMS(user);  // 直接在这里加代码
  11.       
  12.         // 又来需求,要记录日志
  13.         logLoginEvent(user);  // 又在这里加代码
  14.       
  15.         // 再来需求,要统计登录次数
  16.         updateLoginCount(user);  // 继续加代码...
  17.     }
  18. }
  19. // ✅ 扩展点做法:优雅解决
  20. @Service
  21. public class UserService {
  22.     @Autowired
  23.     private ApplicationEventPublisher eventPublisher;
  24.   
  25.     public void login(User user) {
  26.         // 核心登录逻辑
  27.         validateUser(user);
  28.         updateLoginTime(user);
  29.       
  30.         // 发布事件,让扩展点处理其他逻辑
  31.         eventPublisher.publishEvent(new UserLoginEvent(user));
  32.     }
  33. }
  34. // 各种扩展功能独立实现
  35. @EventListener
  36. public void sendSmsOnLogin(UserLoginEvent event) {
  37.     smsService.sendLoginNotification(event.getUser());
  38. }
  39. @EventListener  
  40. public void logLoginEvent(UserLoginEvent event) {
  41.     logService.recordLogin(event.getUser());
  42. }
复制代码
扩展点的优势

对比项传统做法扩展点做法代码耦合度高,功能混杂在一起低,各功能独立维护难度难,改一处影响多处简单,各司其职功能扩展需要修改原代码新增组件即可单元测试复杂,需要mock很多依赖简单,功能独立测试团队协作容易冲突可并行开发二、Spring Boot启动过程中的扩展点

Spring Boot在启动过程中为我们提供了多个时机来插入自定义逻辑,这些扩展点让我们能够在应用启动的不同阶段执行初始化操作,比如缓存预热、数据初始化、健康检查等。
应用启动监听器 - ApplicationListener

扩展点说明: ApplicationListener是Spring Boot中最常用的启动扩展点,它能监听应用启动过程中的各种事件。当应用完全启动并准备好接收请求时,我们可以通过监听ApplicationReadyEvent事件来执行一些初始化工作。
适用场景: 缓存预热、外部服务连接检查、定时任务启动、系统状态初始化等需要在应用完全启动后执行的操作。
  1. /**
  2. * 应用启动完成后的扩展点
  3. * 常用于:缓存预热、定时任务启动、外部服务连接等
  4. */
  5. @Component
  6. public class ApplicationStartupListener implements ApplicationListener {
  7.   
  8.     @Autowired
  9.     private RedisTemplate redisTemplate;
  10.   
  11.     @Autowired
  12.     private DataWarmupService dataWarmupService;
  13.   
  14.     @Override
  15.     public void onApplicationEvent(ApplicationReadyEvent event) {
  16.         log.info("应用启动完成,开始执行启动后任务...");
  17.       
  18.         // 1. 缓存预热
  19.         warmupCache();
  20.       
  21.         // 2. 检查外部服务连接
  22.         checkExternalServices();
  23.       
  24.         // 3. 启动后台任务
  25.         startBackgroundTasks();
  26.       
  27.         log.info("应用启动后任务执行完成");
  28.     }
  29.   
  30.     private void warmupCache() {
  31.         try {
  32.             // 预热热门商品数据
  33.             List<Product> hotProducts = dataWarmupService.getHotProducts();
  34.             hotProducts.forEach(product -> {
  35.                 String key = "product:" + product.getId();
  36.                 redisTemplate.opsForValue().set(key, product, Duration.ofHours(1));
  37.             });
  38.          
  39.             log.info("缓存预热完成,预热商品数量:{}", hotProducts.size());
  40.         } catch (Exception e) {
  41.             log.error("缓存预热失败", e);
  42.         }
  43.     }
  44.   
  45.     private void checkExternalServices() {
  46.         // 检查支付服务
  47.         if (paymentService.isAvailable()) {
  48.             log.info("支付服务连接正常");
  49.         } else {
  50.             log.warn("支付服务连接异常,请检查配置");
  51.         }
  52.       
  53.         // 检查短信服务
  54.         if (smsService.isAvailable()) {
  55.             log.info("短信服务连接正常");
  56.         } else {
  57.             log.warn("短信服务连接异常,请检查配置");
  58.         }
  59.     }
  60. }
复制代码
CommandLineRunner - 命令行启动扩展

扩展点说明: CommandLineRunner接口允许我们在Spring Boot应用启动完成后立即执行一些代码。它的特点是可以接收命令行参数,并且支持通过@Order注解控制执行顺序。多个CommandLineRunner会按照@Order指定的顺序依次执行。
适用场景: 数据库初始化、系统配置检查、基础数据导入等需要在应用启动时一次性执行的任务。
  1. /**
  2. * 命令行启动扩展点
  3. * 特点:按@Order顺序执行,可以接收命令行参数
  4. */
  5. @Component
  6. @Order(1)  // 执行顺序,数字越小越先执行
  7. public class DatabaseInitCommandLineRunner implements CommandLineRunner {
  8.   
  9.     @Autowired
  10.     private DataInitService dataInitService;
  11.   
  12.     @Override
  13.     public void run(String... args) throws Exception {
  14.         log.info("开始执行数据库初始化...");
  15.       
  16.         // 检查是否需要初始化数据
  17.         if (shouldInitializeData(args)) {
  18.             dataInitService.initializeDefaultData();
  19.             log.info("数据库初始化完成");
  20.         } else {
  21.             log.info("跳过数据库初始化");
  22.         }
  23.     }
  24.   
  25.     private boolean shouldInitializeData(String[] args) {
  26.         // 检查命令行参数
  27.         return Arrays.asList(args).contains("--init-data");
  28.     }
  29. }
  30. @Component
  31. @Order(2)
  32. public class HealthCheckCommandLineRunner implements CommandLineRunner {
  33.   
  34.     @Override
  35.     public void run(String... args) throws Exception {
  36.         log.info("执行系统健康检查...");
  37.       
  38.         // 检查数据库连接
  39.         checkDatabaseConnection();
  40.       
  41.         // 检查Redis连接
  42.         checkRedisConnection();
  43.       
  44.         log.info("系统健康检查完成");
  45.     }
  46. }
复制代码
ApplicationRunner - 应用启动扩展

扩展点说明: ApplicationRunner与CommandLineRunner非常相似,主要区别在于它提供了更强大的参数解析功能。通过ApplicationArguments对象,我们可以更方便地处理命令行选项参数和非选项参数。
适用场景: 需要复杂命令行参数处理的启动任务,如根据不同参数执行不同的初始化逻辑。
  1. /**
  2. * ApplicationRunner与CommandLineRunner类似
  3. * 区别:可以解析命令行参数为ApplicationArguments对象
  4. */
  5. @Component
  6. public class SystemConfigApplicationRunner implements ApplicationRunner {
  7.   
  8.     @Override
  9.     public void run(ApplicationArguments args) throws Exception {
  10.         log.info("开始加载系统配置...");
  11.       
  12.         // 检查特定参数
  13.         if (args.containsOption("profile")) {
  14.             String profile = args.getOptionValues("profile").get(0);
  15.             log.info("使用指定环境配置: {}", profile);
  16.         }
  17.       
  18.         // 检查非选项参数
  19.         List<String> nonOptionArgs = args.getNonOptionArgs();
  20.         if (!nonOptionArgs.isEmpty()) {
  21.             log.info("接收到非选项参数: {}", nonOptionArgs);
  22.         }
  23.       
  24.         // 加载配置
  25.         loadSystemConfiguration();
  26.     }
  27.   
  28.     private void loadSystemConfiguration() {
  29.         // 加载系统级配置
  30.         log.info("系统配置加载完成");
  31.     }
  32. }
复制代码
三、Bean生命周期中的扩展点

Bean生命周期扩展点让我们能够在Spring容器创建和销毁Bean的过程中插入自定义逻辑。这些扩展点是实现AOP、依赖注入增强、Bean装饰等高级功能的基础。
BeanPostProcessor - Bean处理器

扩展点说明: BeanPostProcessor是Spring框架中最强大的扩展点之一,它允许我们在Bean初始化前后对Bean进行修改或包装。Spring的很多核心功能(如AOP、@Autowired注入等)都是通过BeanPostProcessor实现的。
适用场景: 实现AOP切面、Bean属性注入、性能监控、缓存代理、Bean验证等需要对多个Bean进行统一处理的场景。
  1. /**
  2. * Bean后处理器:在Bean初始化前后进行扩展
  3. * 常用于:AOP、属性注入、Bean增强等
  4. */
  5. @Component
  6. public class CustomBeanPostProcessor implements BeanPostProcessor {
  7.   
  8.     /**
  9.      * Bean初始化之前调用
  10.      */
  11.     @Override
  12.     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  13.       
  14.         // 为带有@PerformanceMonitor注解的Bean添加性能监控
  15.         if (bean.getClass().isAnnotationPresent(PerformanceMonitor.class)) {
  16.             log.info("为Bean [{}] 添加性能监控", beanName);
  17.             return createPerformanceProxy(bean);
  18.         }
  19.       
  20.         return bean;
  21.     }
  22.   
  23.     /**
  24.      * Bean初始化之后调用
  25.      */
  26.     @Override
  27.     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  28.       
  29.         // 为Service类添加缓存功能
  30.         if (bean.getClass().getSimpleName().endsWith("Service")) {
  31.             log.info("为Service Bean [{}] 添加缓存支持", beanName);
  32.             return createCacheProxy(bean);
  33.         }
  34.       
  35.         return bean;
  36.     }
  37.   
  38.     private Object createPerformanceProxy(Object target) {
  39.         return Proxy.newProxyInstance(
  40.             target.getClass().getClassLoader(),
  41.             target.getClass().getInterfaces(),
  42.             (proxy, method, args) -> {
  43.                 long startTime = System.currentTimeMillis();
  44.                 try {
  45.                     Object result = method.invoke(target, args);
  46.                     long endTime = System.currentTimeMillis();
  47.                     log.info("方法 [{}] 执行耗时: {}ms", method.getName(), endTime - startTime);
  48.                     return result;
  49.                 } catch (Exception e) {
  50.                     throw e;
  51.                 }
  52.             }
  53.         );
  54.     }
  55. }
  56. // 自定义注解
  57. @Target(ElementType.TYPE)
  58. @Retention(RetentionPolicy.RUNTIME)
  59. public @interface PerformanceMonitor {
  60. }
  61. // 使用示例
  62. @Service
  63. @PerformanceMonitor
  64. public class OrderService {
  65.     public void createOrder(Order order) {
  66.         // 业务逻辑
  67.     }
  68. }
复制代码
InitializingBean和DisposableBean

扩展点说明: 这两个接口提供了Bean生命周期的初始化和销毁钩子。InitializingBean的afterPropertiesSet方法在Bean的所有属性设置完成后调用,DisposableBean的destroy方法在Bean销毁前调用,主要用于资源清理。
适用场景: 连接池管理、定时任务启停、文件句柄释放、网络连接关闭等需要在Bean创建时初始化资源,销毁时清理资源的场景。
  1. /**
  2. * Bean初始化和销毁的扩展点
  3. */
  4. @Component
  5. public class ConnectionPoolManager implements InitializingBean, DisposableBean {
  6.   
  7.     private ExecutorService executorService;
  8.     private ScheduledExecutorService scheduledExecutor;
  9.   
  10.     /**
  11.      * Bean属性设置完成后调用
  12.      */
  13.     @Override
  14.     public void afterPropertiesSet() throws Exception {
  15.         log.info("初始化连接池管理器...");
  16.       
  17.         // 创建线程池
  18.         executorService = Executors.newFixedThreadPool(10);
  19.         scheduledExecutor = Executors.newScheduledThreadPool(5);
  20.       
  21.         // 启动定时任务
  22.         startScheduledTasks();
  23.       
  24.         log.info("连接池管理器初始化完成");
  25.     }
  26.   
  27.     /**
  28.      * Bean销毁前调用
  29.      */
  30.     @Override
  31.     public void destroy() throws Exception {
  32.         log.info("开始销毁连接池管理器...");
  33.       
  34.         // 优雅关闭线程池
  35.         if (executorService != null) {
  36.             executorService.shutdown();
  37.             if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {
  38.                 executorService.shutdownNow();
  39.             }
  40.         }
  41.       
  42.         if (scheduledExecutor != null) {
  43.             scheduledExecutor.shutdown();
  44.             if (!scheduledExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
  45.                 scheduledExecutor.shutdownNow();
  46.             }
  47.         }
  48.       
  49.         log.info("连接池管理器销毁完成");
  50.     }
  51.   
  52.     private void startScheduledTasks() {
  53.         // 定时清理任务
  54.         scheduledExecutor.scheduleAtFixedRate(() -> {
  55.             log.info("执行定时清理任务...");
  56.             // 清理逻辑
  57.         }, 0, 1, TimeUnit.HOURS);
  58.     }
  59. }
复制代码
四、事件驱动扩展点

事件驱动扩展点是Spring Boot中实现业务解耦的重要机制。通过发布-订阅模式,我们可以将复杂的业务流程拆分成独立的事件处理器,提高代码的可维护性和可扩展性。
自定义事件和监听器

扩展点说明: Spring的事件机制允许我们定义自己的事件类型和对应的监听器。事件发布者通过ApplicationEventPublisher发布事件,事件监听器通过@EventListener注解自动接收并处理事件。这种方式可以很好地解耦业务逻辑。
适用场景: 订单处理后的库存扣减、积分增加、消息通知等需要在主业务完成后触发的后续操作,以及需要解耦的业务逻辑处理。
  1. /**
  2. * 自定义事件
  3. */
  4. public class OrderCreatedEvent extends ApplicationEvent {
  5.     private final Order order;
  6.     private final User user;
  7.   
  8.     public OrderCreatedEvent(Object source, Order order, User user) {
  9.         super(source);
  10.         this.order = order;
  11.         this.user = user;
  12.     }
  13.   
  14.     // getter methods...
  15. }
  16. /**
  17. * 订单服务:发布事件
  18. */
  19. @Service
  20. public class OrderService {
  21.   
  22.     @Autowired
  23.     private ApplicationEventPublisher eventPublisher;
  24.   
  25.     public Order createOrder(CreateOrderRequest request) {
  26.         // 1. 创建订单
  27.         Order order = buildOrder(request);
  28.         orderRepository.save(order);
  29.       
  30.         // 2. 发布订单创建事件
  31.         eventPublisher.publishEvent(new OrderCreatedEvent(this, order, request.getUser()));
  32.       
  33.         return order;
  34.     }
  35. }
  36. /**
  37. * 库存服务:监听订单事件,扣减库存
  38. */
  39. @Component
  40. public class InventoryEventListener {
  41.   
  42.     @Autowired
  43.     private InventoryService inventoryService;
  44.   
  45.     @EventListener
  46.     @Async  // 异步处理,不影响主流程
  47.     public void handleOrderCreated(OrderCreatedEvent event) {
  48.         log.info("订单创建事件:开始扣减库存,订单ID: {}", event.getOrder().getId());
  49.       
  50.         try {
  51.             Order order = event.getOrder();
  52.             order.getItems().forEach(item -> {
  53.                 inventoryService.reduceStock(item.getProductId(), item.getQuantity());
  54.             });
  55.          
  56.             log.info("库存扣减完成,订单ID: {}", order.getId());
  57.         } catch (Exception e) {
  58.             log.error("库存扣减失败,订单ID: {}", event.getOrder().getId(), e);
  59.             // 可以发布库存扣减失败事件,触发补偿逻辑
  60.         }
  61.     }
  62. }
  63. /**
  64. * 积分服务:监听订单事件,增加积分
  65. */
  66. @Component
  67. public class PointsEventListener {
  68.   
  69.     @Autowired
  70.     private PointsService pointsService;
  71.   
  72.     @EventListener
  73.     @Async
  74.     public void handleOrderCreated(OrderCreatedEvent event) {
  75.         log.info("订单创建事件:开始计算积分,订单ID: {}", event.getOrder().getId());
  76.       
  77.         try {
  78.             Order order = event.getOrder();
  79.             User user = event.getUser();
  80.          
  81.             // 计算积分(订单金额的1%)
  82.             int points = (int) (order.getTotalAmount().doubleValue() * 0.01);
  83.             pointsService.addPoints(user.getId(), points, "订单奖励");
  84.          
  85.             log.info("积分增加完成,用户ID: {}, 积分: {}", user.getId(), points);
  86.         } catch (Exception e) {
  87.             log.error("积分增加失败,订单ID: {}", event.getOrder().getId(), e);
  88.         }
  89.     }
  90. }
  91. /**
  92. * 通知服务:发送订单确认消息
  93. */
  94. @Component  
  95. public class NotificationEventListener {
  96.   
  97.     @Autowired
  98.     private EmailService emailService;
  99.   
  100.     @Autowired
  101.     private SmsService smsService;
  102.   
  103.     @EventListener
  104.     @Async
  105.     public void handleOrderCreated(OrderCreatedEvent event) {
  106.         log.info("订单创建事件:发送通知,订单ID: {}", event.getOrder().getId());
  107.       
  108.         try {
  109.             Order order = event.getOrder();
  110.             User user = event.getUser();
  111.          
  112.             // 发送邮件通知
  113.             emailService.sendOrderConfirmation(user.getEmail(), order);
  114.          
  115.             // 发送短信通知  
  116.             smsService.sendOrderConfirmation(user.getPhone(), order);
  117.          
  118.             log.info("订单通知发送完成,订单ID: {}", order.getId());
  119.         } catch (Exception e) {
  120.             log.error("订单通知发送失败,订单ID: {}", event.getOrder().getId(), e);
  121.         }
  122.     }
  123. }
复制代码
事件监听器的高级用法

扩展点说明: Spring提供了多种高级事件监听功能,包括条件化监听(只处理满足特定条件的事件)和事务性监听(在事务的特定阶段执行)。这些功能让事件处理更加灵活和可靠。
适用场景: VIP用户特殊处理、大额订单风控、事务成功后的外部系统通知、事务失败后的补偿操作等需要根据条件或事务状态进行处理的场景。
  1. /**
  2. * 条件化事件监听
  3. */
  4. @Component
  5. public class ConditionalEventListener {
  6.   
  7.     /**
  8.      * 只处理VIP用户的订单
  9.      */
  10.     @EventListener(condition = "#event.user.vipLevel > 0")
  11.     public void handleVipOrderCreated(OrderCreatedEvent event) {
  12.         log.info("VIP用户订单处理,用户等级: {}", event.getUser().getVipLevel());
  13.       
  14.         // VIP专属处理逻辑
  15.         processVipOrder(event.getOrder());
  16.     }
  17.   
  18.     /**
  19.      * 只处理大额订单(金额>1000)
  20.      */
  21.     @EventListener(condition = "#event.order.totalAmount > 1000")
  22.     public void handleLargeOrderCreated(OrderCreatedEvent event) {
  23.         log.info("大额订单处理,订单金额: {}", event.getOrder().getTotalAmount());
  24.       
  25.         // 大额订单特殊处理
  26.         processLargeOrder(event.getOrder());
  27.     }
  28. }
  29. /**
  30. * 事务性事件监听
  31. */
  32. @Component
  33. public class TransactionalEventListener {
  34.   
  35.     /**
  36.      * 在事务提交后执行
  37.      */
  38.     @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
  39.     public void handleOrderCreatedAfterCommit(OrderCreatedEvent event) {
  40.         log.info("订单事务提交后处理,订单ID: {}", event.getOrder().getId());
  41.       
  42.         // 只有在订单真正保存成功后才执行
  43.         sendOrderCreatedNotificationToExternalSystem(event.getOrder());
  44.     }
  45.   
  46.     /**
  47.      * 在事务回滚后执行
  48.      */
  49.     @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
  50.     public void handleOrderCreatedAfterRollback(OrderCreatedEvent event) {
  51.         log.info("订单事务回滚后处理,订单ID: {}", event.getOrder().getId());
  52.       
  53.         // 事务回滚时的补偿逻辑
  54.         compensateForFailedOrder(event.getOrder());
  55.     }
  56. }
复制代码
五、Web层扩展点

Web层扩展点专门用于处理HTTP请求的生命周期,让我们能够在请求处理的各个阶段插入自定义逻辑,实现统一的请求处理、权限控制、日志记录等功能。
拦截器 - HandlerInterceptor

扩展点说明: HandlerInterceptor是Spring MVC提供的请求拦截机制,它提供了三个时机:请求处理前(preHandle)、请求处理后视图渲染前(postHandle)、请求完全结束后(afterCompletion)。拦截器在Spring容器内工作,可以很方便地注入Spring Bean。
适用场景: 权限认证、请求日志记录、性能监控、参数校验、响应数据统一处理等需要对Web请求进行统一处理的场景。
  1. /**
  2. * 请求日志拦截器
  3. */
  4. @Component
  5. public class RequestLoggingInterceptor implements HandlerInterceptor {
  6.   
  7.     private static final String START_TIME = "startTime";
  8.     private static final String REQUEST_ID = "requestId";
  9.   
  10.     /**
  11.      * 请求处理前
  12.      */
  13.     @Override
  14.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
  15.                            Object handler) throws Exception {
  16.       
  17.         String requestId = UUID.randomUUID().toString();
  18.         long startTime = System.currentTimeMillis();
  19.       
  20.         // 设置请求ID和开始时间
  21.         request.setAttribute(REQUEST_ID, requestId);
  22.         request.setAttribute(START_TIME, startTime);
  23.       
  24.         // 设置MDC,方便日志追踪
  25.         MDC.put("requestId", requestId);
  26.       
  27.         log.info("请求开始 - URL: {}, Method: {}, IP: {}, User-Agent: {}",
  28.                 request.getRequestURL(),
  29.                 request.getMethod(),
  30.                 getClientIpAddress(request),
  31.                 request.getHeader("User-Agent"));
  32.       
  33.         return true;
  34.     }
  35.   
  36.     /**
  37.      * 请求处理后,视图渲染前
  38.      */
  39.     @Override
  40.     public void postHandle(HttpServletRequest request, HttpServletResponse response,
  41.                           Object handler, ModelAndView modelAndView) throws Exception {
  42.       
  43.         long startTime = (Long) request.getAttribute(START_TIME);
  44.         long endTime = System.currentTimeMillis();
  45.       
  46.         log.info("请求处理完成 - 耗时: {}ms, Status: {}",
  47.                 endTime - startTime,
  48.                 response.getStatus());
  49.     }
  50.   
  51.     /**
  52.      * 请求完全结束后
  53.      */
  54.     @Override
  55.     public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
  56.                                Object handler, Exception ex) throws Exception {
  57.       
  58.         if (ex != null) {
  59.             log.error("请求处理异常", ex);
  60.         }
  61.       
  62.         // 清理MDC
  63.         MDC.clear();
  64.       
  65.         log.info("请求结束");
  66.     }
  67.   
  68.     private String getClientIpAddress(HttpServletRequest request) {
  69.         String xForwardedFor = request.getHeader("X-Forwarded-For");
  70.         if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
  71.             return xForwardedFor.split(",")[0].trim();
  72.         }
  73.       
  74.         String xRealIp = request.getHeader("X-Real-IP");
  75.         if (xRealIp != null && !xRealIp.isEmpty()) {
  76.             return xRealIp;
  77.         }
  78.       
  79.         return request.getRemoteAddr();
  80.     }
  81. }
  82. /**
  83. * 权限检查拦截器
  84. */
  85. @Component
  86. public class AuthenticationInterceptor implements HandlerInterceptor {
  87.   
  88.     @Autowired
  89.     private JwtTokenUtil jwtTokenUtil;
  90.   
  91.     @Override
  92.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
  93.                            Object handler) throws Exception {
  94.       
  95.         // 检查是否需要权限验证
  96.         if (handler instanceof HandlerMethod) {
  97.             HandlerMethod handlerMethod = (HandlerMethod) handler;
  98.          
  99.             // 检查方法或类上是否有@NoAuth注解
  100.             if (handlerMethod.hasMethodAnnotation(NoAuth.class) ||
  101.                 handlerMethod.getBeanType().isAnnotationPresent(NoAuth.class)) {
  102.                 return true;
  103.             }
  104.         }
  105.       
  106.         // 获取token
  107.         String token = request.getHeader("Authorization");
  108.         if (token == null || !token.startsWith("Bearer ")) {
  109.             response.setStatus(HttpStatus.UNAUTHORIZED.value());
  110.             response.getWriter().write("{"error":"未授权访问"}");
  111.             return false;
  112.         }
  113.       
  114.         token = token.substring(7); // 去掉"Bearer "
  115.       
  116.         // 验证token
  117.         try {
  118.             String userId = jwtTokenUtil.getUserIdFromToken(token);
  119.             if (userId != null) {
  120.                 // 将用户信息存入request
  121.                 request.setAttribute("userId", userId);
  122.                 return true;
  123.             }
  124.         } catch (Exception e) {
  125.             log.warn("Token验证失败", e);
  126.         }
  127.       
  128.         response.setStatus(HttpStatus.UNAUTHORIZED.value());
  129.         response.getWriter().write("{"error":"Token无效"}");
  130.         return false;
  131.     }
  132. }
  133. // 注册拦截器
  134. @Configuration
  135. public class WebConfig implements WebMvcConfigurer {
  136.   
  137.     @Autowired
  138.     private RequestLoggingInterceptor requestLoggingInterceptor;
  139.   
  140.     @Autowired
  141.     private AuthenticationInterceptor authenticationInterceptor;
  142.   
  143.     @Override
  144.     public void addInterceptors(InterceptorRegistry registry) {
  145.         // 请求日志拦截器:拦截所有请求
  146.         registry.addInterceptor(requestLoggingInterceptor)
  147.                 .addPathPatterns("/**");
  148.       
  149.         // 权限拦截器:拦截API请求,排除登录等公开接口
  150.         registry.addInterceptor(authenticationInterceptor)
  151.                 .addPathPatterns("/api/**")
  152.                 .excludePathPatterns("/api/login", "/api/register", "/api/public/**");
  153.     }
  154. }
复制代码
过滤器 - Filter

扩展点说明: Filter是Servlet规范中的过滤器,它在请求到达Spring容器之前就开始工作。相比拦截器,Filter的执行时机更早,功能更底层,但无法直接使用Spring的依赖注入功能。
适用场景: 跨域处理、字符编码设置、请求体缓存、安全过滤等需要在最早期处理请求的场景,以及需要处理静态资源请求的情况。
  1. /**
  2. * 跨域处理过滤器
  3. */
  4. @Component
  5. @Order(1)
  6. public class CorsFilter implements Filter {
  7.   
  8.     @Override
  9.     public void doFilter(ServletRequest request, ServletResponse response,
  10.                         FilterChain chain) throws IOException, ServletException {
  11.       
  12.         HttpServletResponse httpResponse = (HttpServletResponse) response;
  13.         HttpServletRequest httpRequest = (HttpServletRequest) request;
  14.       
  15.         // 设置跨域头
  16.         httpResponse.setHeader("Access-Control-Allow-Origin", "*");
  17.         httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  18.         httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  19.         httpResponse.setHeader("Access-Control-Max-Age", "3600");
  20.       
  21.         // 处理预检请求
  22.         if ("OPTIONS".equals(httpRequest.getMethod())) {
  23.             httpResponse.setStatus(HttpServletResponse.SC_OK);
  24.             return;
  25.         }
  26.       
  27.         chain.doFilter(request, response);
  28.     }
  29. }
  30. /**
  31. * 请求体包装过滤器
  32. */
  33. @Component
  34. @Order(2)
  35. public class RequestWrapperFilter implements Filter {
  36.   
  37.     @Override
  38.     public void doFilter(ServletRequest request, ServletResponse response,
  39.                         FilterChain chain) throws IOException, ServletException {
  40.       
  41.         if (request instanceof HttpServletRequest) {
  42.             HttpServletRequest httpRequest = (HttpServletRequest) request;
  43.          
  44.             // 只包装POST和PUT请求
  45.             if ("POST".equals(httpRequest.getMethod()) || "PUT".equals(httpRequest.getMethod())) {
  46.                 // 使用自定义的请求包装器,支持多次读取请求体
  47.                 CachedBodyHttpServletRequest wrappedRequest =
  48.                     new CachedBodyHttpServletRequest(httpRequest);
  49.                 chain.doFilter(wrappedRequest, response);
  50.                 return;
  51.             }
  52.         }
  53.       
  54.         chain.doFilter(request, response);
  55.     }
  56. }
  57. /**
  58. * 可重复读取的请求包装器
  59. */
  60. public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
  61.   
  62.     private byte[] cachedBody;
  63.   
  64.     public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
  65.         super(request);
  66.       
  67.         // 缓存请求体
  68.         InputStream requestInputStream = request.getInputStream();
  69.         this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
  70.     }
  71.   
  72.     @Override
  73.     public ServletInputStream getInputStream() throws IOException {
  74.         return new CachedBodyServletInputStream(this.cachedBody);
  75.     }
  76.   
  77.     @Override
  78.     public BufferedReader getReader() throws IOException {
  79.         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
  80.         return new BufferedReader(new InputStreamReader(byteArrayInputStream));
  81.     }
  82.   
  83.     public byte[] getCachedBody() {
  84.         return cachedBody;
  85.     }
  86. }
复制代码
六、自动配置扩展点

自动配置扩展点是Spring Boot的核心特性之一,它允许我们根据不同的条件(如类路径、配置属性、环境等)自动装配不同的Bean,实现"约定优于配置"的理念。
自定义Starter

扩展点说明: 自定义Starter是Spring Boot提供的模块化配置机制,通过@Conditional系列注解,我们可以根据类路径中是否存在特定类、是否有特定配置等条件来决定是否自动装配某些Bean。这是实现"开箱即用"的关键技术。
适用场景: 开发可复用的功能模块、第三方组件集成、企业内部通用组件封装等需要根据环境自动配置的场景。
  1. /**
  2. * 自动配置类
  3. */
  4. @Configuration
  5. @ConditionalOnClass(RedisTemplate.class)
  6. @EnableConfigurationProperties(CacheProperties.class)
  7. public class CacheAutoConfiguration {
  8.   
  9.     @Bean
  10.     @ConditionalOnMissingBean
  11.     public CacheManager customCacheManager(CacheProperties properties) {
  12.         return new CustomCacheManager(properties);
  13.     }
  14.   
  15.     @Bean
  16.     @ConditionalOnProperty(prefix = "custom.cache", name = "enabled", havingValue = "true")
  17.     public CacheService cacheService(CacheManager cacheManager) {
  18.         return new CacheService(cacheManager);
  19.     }
  20. }
  21. /**
  22. * 配置属性
  23. */
  24. @ConfigurationProperties(prefix = "custom.cache")
  25. @Data
  26. public class CacheProperties {
  27.   
  28.     /**
  29.      * 是否启用缓存
  30.      */
  31.     private boolean enabled = true;
  32.   
  33.     /**
  34.      * 缓存过期时间(秒)
  35.      */
  36.     private int ttl = 3600;
  37.   
  38.     /**
  39.      * 缓存前缀
  40.      */
  41.     private String prefix = "app:cache:";
  42.   
  43.     /**
  44.      * 最大缓存数量
  45.      */
  46.     private int maxSize = 1000;
  47. }
  48. /**
  49. * 自定义缓存服务
  50. */
  51. public class CacheService {
  52.   
  53.     private final CacheManager cacheManager;
  54.   
  55.     public CacheService(CacheManager cacheManager) {
  56.         this.cacheManager = cacheManager;
  57.     }
  58.   
  59.     public void put(String key, Object value) {
  60.         cacheManager.getCache("default").put(key, value);
  61.     }
  62.   
  63.     public <T> T get(String key, Class<T> type) {
  64.         Cache.ValueWrapper wrapper = cacheManager.getCache("default").get(key);
  65.         return wrapper != null ? type.cast(wrapper.get()) : null;
  66.     }
  67. }
复制代码
条件化配置

扩展点说明: Spring Boot提供了丰富的条件注解(@Profile、@ConditionalOnProperty等),让我们能够根据不同的环境、配置属性、Bean存在情况等条件来装配不同的Bean。这种机制让同一套代码能够适应不同的运行环境。
适用场景: 开发环境与生产环境的配置差异化、功能开关控制、可选组件的条件装配等需要根据运行环境动态调整配置的场景。
  1. /**
  2. * 环境相关的自动配置
  3. */
  4. @Configuration
  5. public class EnvironmentSpecificConfiguration {
  6.   
  7.     /**
  8.      * 开发环境配置
  9.      */
  10.     @Configuration
  11.     @Profile("dev")
  12.     static class DevelopmentConfiguration {
  13.       
  14.         @Bean
  15.         public DataSource devDataSource() {
  16.             // 开发环境数据源配置
  17.             HikariDataSource dataSource = new HikariDataSource();
  18.             dataSource.setJdbcUrl("jdbc:h2:mem:devdb");
  19.             dataSource.setUsername("sa");
  20.             dataSource.setPassword("");
  21.             return dataSource;
  22.         }
  23.       
  24.         @Bean
  25.         public LogLevel devLogLevel() {
  26.             return LogLevel.DEBUG;
  27.         }
  28.     }
  29.   
  30.     /**
  31.      * 生产环境配置
  32.      */
  33.     @Configuration
  34.     @Profile("prod")
  35.     static class ProductionConfiguration {
  36.       
  37.         @Bean
  38.         public DataSource prodDataSource(@Value("${spring.datasource.url}") String url,
  39.                                        @Value("${spring.datasource.username}") String username,
  40.                                        @Value("${spring.datasource.password}") String password) {
  41.             HikariDataSource dataSource = new HikariDataSource();
  42.             dataSource.setJdbcUrl(url);
  43.             dataSource.setUsername(username);
  44.             dataSource.setPassword(password);
  45.             dataSource.setMaximumPoolSize(20);
  46.             return dataSource;
  47.         }
  48.       
  49.         @Bean
  50.         public LogLevel prodLogLevel() {
  51.             return LogLevel.INFO;
  52.         }
  53.     }
  54.   
  55.     /**
  56.      * 基于属性的条件配置
  57.      */
  58.     @Bean
  59.     @ConditionalOnProperty(name = "feature.async.enabled", havingValue = "true")
  60.     public AsyncTaskExecutor asyncTaskExecutor() {
  61.         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  62.         executor.setCorePoolSize(5);
  63.         executor.setMaxPoolSize(10);
  64.         executor.setQueueCapacity(100);
  65.         executor.setThreadNamePrefix("async-");
  66.         executor.initialize();
  67.         return executor;
  68.     }
  69.   
  70.     /**
  71.      * 基于Bean存在与否的条件配置
  72.      */
  73.     @Bean
  74.     @ConditionalOnMissingBean(name = "emailService")
  75.     public EmailService defaultEmailService() {
  76.         return new DefaultEmailService();
  77.     }
  78. }
复制代码
七、扩展点最佳实践

扩展点设计原则

graph TD    A[扩展点设计原则] --> B[单一职责]    A --> C[开闭原则]    A --> D[依赖倒置]    A --> E[接口隔离]      B --> B1[每个扩展点只负责一个功能]    B --> B2[避免功能耦合]      C --> C1[对扩展开放]    C --> C2[对修改关闭]      D --> D1[依赖抽象而非具体实现]    D --> D2[通过接口定义扩展点]      E --> E1[接口要精简]    E --> E2[避免臃肿接口]扩展点使用建议

场景推荐扩展点原因应用启动初始化ApplicationListener生命周期清晰业务事件处理@EventListener解耦,异步处理跨切面功能BeanPostProcessor统一处理多个BeanWeb请求处理HandlerInterceptor专为Web设计全局请求过滤Filter在Spring容器外也能工作环境相关配置@Profile + @Conditional灵活的条件装配性能优化建议
  1. /**
  2. * 异步事件处理配置
  3. */
  4. @Configuration
  5. @EnableAsync
  6. public class AsyncEventConfiguration {
  7.   
  8.     /**
  9.      * 事件处理专用线程池
  10.      */
  11.     @Bean("eventTaskExecutor")
  12.     public TaskExecutor eventTaskExecutor() {
  13.         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  14.         executor.setCorePoolSize(5);
  15.         executor.setMaxPoolSize(20);
  16.         executor.setQueueCapacity(500);
  17.         executor.setThreadNamePrefix("event-");
  18.         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
  19.         executor.initialize();
  20.         return executor;
  21.     }
  22.   
  23.     /**
  24.      * 异步事件异常处理
  25.      */
  26.     @Bean
  27.     public AsyncUncaughtExceptionHandler asyncExceptionHandler() {
  28.         return new SimpleAsyncUncaughtExceptionHandler();
  29.     }
  30. }
  31. /**
  32. * 高性能事件监听器示例
  33. */
  34. @Component
  35. public class HighPerformanceEventListener {
  36.   
  37.     private final TaskExecutor eventTaskExecutor;
  38.   
  39.     public HighPerformanceEventListener(@Qualifier("eventTaskExecutor") TaskExecutor eventTaskExecutor) {
  40.         this.eventTaskExecutor = eventTaskExecutor;
  41.     }
  42.   
  43.     /**
  44.      * 使用专门的线程池处理事件
  45.      */
  46.     @EventListener
  47.     @Async("eventTaskExecutor")
  48.     public void handleOrderEvent(OrderCreatedEvent event) {
  49.         // 业务处理逻辑
  50.         processOrderEvent(event);
  51.     }
  52.   
  53.     /**
  54.      * 批量处理事件,提高吞吐量
  55.      */
  56.     @EventListener
  57.     public void handleBatchEvent(List<OrderCreatedEvent> events) {
  58.         eventTaskExecutor.execute(() -> {
  59.             // 批量处理逻辑
  60.             processBatchEvents(events);
  61.         });
  62.     }
  63. }
复制代码
常见问题与解决方案

问题原因解决方案事件监听器执行顺序不确定默认无序执行使用@Order注解指定顺序异步事件处理异常被忽略异常在子线程中配置AsyncUncaughtExceptionHandler扩展点过多导致启动缓慢初始化逻辑复杂使用@Lazy延迟初始化循环依赖问题Bean之间相互依赖使用@EventListener解耦八、总结

Spring Boot的扩展点就像是代码世界中的"瑞士军刀",每个扩展点都有其独特的用途:
核心扩展点一览

graph LR    A[Spring Boot扩展点] --> B[启动阶段]    A --> C[Bean生命周期]    A --> D[事件驱动]    A --> E[Web层]    A --> F[自动配置]      B --> B1[ApplicationListener]    B --> B2[CommandLineRunner]      B --> B3[ApplicationRunner]      C --> C1[BeanPostProcessor]    C --> C2[InitializingBean]    C --> C3[DisposableBean]      D --> D1[EventListener注解]    D --> D2[ApplicationEventPublisher]    D --> D3[TransactionalEventListener]      E --> E1[HandlerInterceptor]    E --> E2[Filter]    E --> E3[ControllerAdvice注解]      F --> F1[Conditional系列注解]    F --> F2[Profile注解]    F --> F3[AutoConfiguration]使用建议

1. 选择合适的扩展点:

  • 应用启动时的初始化 → ApplicationListener
  • 业务逻辑解耦 → @EventListener
  • 横切关注点 → BeanPostProcessor
  • Web请求处理 → HandlerInterceptor
2. 性能考虑:

  • 使用异步处理避免阻塞主流程
  • 合理配置线程池大小
  • 避免在扩展点中执行耗时操作
3. 错误处理:

  • 扩展点中的异常要妥善处理
  • 使用try-catch避免影响主流程
  • 记录详细的错误日志
4. 测试策略:

  • 单独测试每个扩展点
  • 使用@MockBean模拟依赖
  • 验证扩展点的执行顺序
掌握了这些扩展点,你的Spring Boot应用就能像变形金刚一样灵活变换,轻松应对各种业务需求的变化。记住:好的架构不是一开始就设计完美,而是能够优雅地适应变化!
想要掌握更多Spring Boot高级技巧和企业级开发经验?欢迎关注我的微信公众号【一只划水的程序猿】,这里有最实用的Java技术干货,最贴近实战的编程经验,让你的代码水平快速提升!记得点赞收藏,分享给更多热爱编程的小伙伴!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册