吃透 Spring 12 个核心扩展点:从源码底层到生产级实战,90% 的高级开发都在用

简介: 本文系统详解Spring 12个核心扩展点,覆盖Bean生命周期全阶段:从BeanDefinition注册(BeanDefinitionRegistryPostProcessor)、实例化控制(InstantiationAwareBeanPostProcessor),到初始化(@PostConstruct/InitializingBean)、AOP代理(BeanPostProcessor)、事务同步(TransactionSynchronization)及容器关闭(DisposableBean)等,配实战代码与对比表格,助你深入掌握Spring扩展本质。

前言

Spring之所以能成为Java生态的事实标准,核心在于其极致灵活的扩展能力。几乎所有主流中间件(MyBatis、Dubbo、RocketMQ等)的Spring整合包,都是基于Spring原生扩展点实现的。掌握这些扩展点,不仅能让你写出更优雅的Spring应用,更是阅读源码、二次开发框架、排查疑难问题的核心能力。

一、Spring Bean完整生命周期全景

所有Spring扩展点都是围绕Bean生命周期的回调机制实现的,先通过流程图建立全局认知:

二、项目基础环境配置

pom.xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>3.4.3</version>
       <relativePath/>
   </parent>
   <groupId>com.jam</groupId>
   <artifactId>spring-extension-demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>spring-extension-demo</name>
   <description>Spring扩展点实战demo</description>
   <properties>
       <java.version>17</java.version>
       <lombok.version>1.18.34</lombok.version>
       <guava.version>33.2.1-jre</guava.version>
       <fastjson2.version>2.0.52</fastjson2.version>
       <mybatis-plus.version>3.5.7</mybatis-plus.version>
       <springdoc.version>2.6.0</springdoc.version>
   </properties>
   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-jdbc</artifactId>
       </dependency>
       <dependency>
           <groupId>com.mysql</groupId>
           <artifactId>mysql-connector-j</artifactId>
           <version>8.4.0</version>
       </dependency>
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>${mybatis-plus.version}</version>
       </dependency>
       <dependency>
           <groupId>org.springdoc</groupId>
           <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
           <version>${springdoc.version}</version>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>${lombok.version}</version>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>com.google.guava</groupId>
           <artifactId>guava</artifactId>
           <version>${guava.version}</version>
       </dependency>
       <dependency>
           <groupId>com.alibaba.fastjson2</groupId>
           <artifactId>fastjson2</artifactId>
           <version>${fastjson2.version}</version>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>
   <build>
       <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
               <configuration>
                   <excludes>
                       <exclude>
                           <groupId>org.projectlombok</groupId>
                           <artifactId>lombok</artifactId>
                       </exclude>
                   </excludes>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

application.yml基础配置:

spring:
 datasource:
   url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
   username: root
   password: MTIzNDU2
 application:
   name: spring-extension-demo

encrypt:
 db-password: MTIzNDU2

三、核心扩展点详解(按生命周期顺序)

3.1 BeanDefinition注册阶段:容器最前置扩展

该阶段Bean还未实例化,是修改Bean定义的黄金时期,也是框架整合的核心扩展点。

3.1.1 BeanDefinitionRegistryPostProcessor

核心作用:动态注册、修改、删除BeanDefinition,是Spring容器启动最先执行的扩展点,优先级高于BeanFactoryPostProcessor。源码触发时机:AbstractApplicationContext#refresh()方法中,invokeBeanFactoryPostProcessors()优先执行该接口的postProcessBeanDefinitionRegistry方法。适用场景:动态注册Bean、扫描自定义注解注册Bean、框架整合时批量注册Mapper/Service。

实战示例:动态注册Bean

package com.jam.demo.processor;

import com.jam.demo.service.DynamicService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.stereotype.Component;

/**
* 自定义BeanDefinition注册后置处理器
* @author ken
*/

@Slf4j
@Component
public class CustomBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

   /**
    * 注册BeanDefinition的核心方法,优先于postProcessBeanFactory执行
    * @param registry Bean定义注册器,提供BeanDefinition的增删改查能力
    * @throws BeansException 处理过程中抛出的异常
    */

   @Override
   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
       log.info("执行CustomBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry");
       BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DynamicService.class);
       beanDefinitionBuilder.addPropertyValue("serviceName", "dynamicRegisterService");
       beanDefinitionBuilder.setScope(BeanDefinition.SCOPE_SINGLETON);
       registry.registerBeanDefinition("dynamicService", beanDefinitionBuilder.getBeanDefinition());
       log.info("动态注册BeanDefinition[dynamicService]完成");

       if (registry.containsBeanDefinition("dataSource")) {
           BeanDefinition dataSourceBeanDefinition = registry.getBeanDefinition("dataSource");
           dataSourceBeanDefinition.setLazyInit(true);
           log.info("修改dataSource的懒加载属性为true完成");
       }
   }

   /**
    * 父接口BeanFactoryPostProcessor的方法,在postProcessBeanDefinitionRegistry之后执行
    * @param beanFactory 可配置的Bean工厂
    * @throws BeansException 处理过程中抛出的异常
    */

   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
       log.info("执行CustomBeanDefinitionRegistryPostProcessor.postProcessBeanFactory");
   }
}

package com.jam.demo.service;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
* 动态注册的服务类
* @author ken
*/

@Slf4j
@Data
public class DynamicService {

   private String serviceName;

   /**
    * 测试方法
    */

   public void test() {
       log.info("DynamicService.test执行,serviceName:{}", serviceName);
   }
}

3.1.2 BeanFactoryPostProcessor

核心作用:修改已注册的BeanDefinition,无BeanDefinition注册能力。源码触发时机:AbstractApplicationContext#refresh()方法中,所有BeanDefinitionRegistryPostProcessor执行完成后,执行该接口的postProcessBeanFactory方法。适用场景:批量修改已注册Bean的属性、作用域、懒加载等配置。

实战示例:修改已注册Bean的属性

package com.jam.demo.processor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

/**
* 自定义Bean工厂后置处理器
* @author ken
*/

@Slf4j
@Component
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

   /**
    * 对BeanFactory进行后置处理的核心方法
    * @param beanFactory 可配置的Bean工厂,包含所有已注册的BeanDefinition
    * @throws BeansException 处理过程中抛出的异常
    */

   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
       log.info("执行CustomBeanFactoryPostProcessor.postProcessBeanFactory");
       if (beanFactory.containsBeanDefinition("dynamicService")) {
           beanFactory.getBeanDefinition("dynamicService")
                   .getPropertyValues()
                   .addPropertyValue("serviceName", "modifiedByBeanFactoryPostProcessor");
           log.info("修改dynamicService的serviceName属性完成");
       }
   }
}

3.2 Bean实例化阶段:控制Bean的创建流程

该阶段在Bean实例化前后执行,可跳过Spring默认的实例化流程,实现自定义对象创建。

3.2.1 InstantiationAwareBeanPostProcessor

核心作用:Bean实例化前后、属性填充前后的回调,可控制Bean的实例化和属性填充流程。源码触发时机:AbstractAutowireCapableBeanFactory#createBean()中,先执行postProcessBeforeInstantiation,若返回非null对象,直接跳过默认实例化流程;实例化后在populateBean()中执行后续方法。核心方法说明

  1. postProcessBeforeInstantiation:实例化前执行,返回非null则跳过默认实例化
  2. postProcessAfterInstantiation:实例化后、属性填充前执行,返回false则跳过属性填充
  3. postProcessProperties:属性填充前执行,实现自定义属性注入逻辑

实战示例:加密配置自动解密注入自定义注解:

package com.jam.demo.annotation;

import java.lang.annotation.*;

/**
* 加密配置值注解,标注在字段上实现加密配置自动解密注入
* @author ken
*/

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptValue {

   /**
    * 配置文件中的key
    * @return 配置key
    */

   String value();
}

处理器实现:

package com.jam.demo.processor;

import com.jam.demo.annotation.EncryptValue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.Base64;

/**
* 自定义实例化感知Bean后置处理器
* @author ken
*/

@Slf4j
@Component
public class CustomInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

   private final Environment environment;

   public CustomInstantiationAwareBeanPostProcessor(Environment environment) {
       this.environment = environment;
   }

   @Override
   public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
       return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);
   }

   @Override
   public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
       return InstantiationAwareBeanPostProcessor.super.postProcessAfterInstantiation(bean, beanName);
   }

   @Override
   public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
       Field[] declaredFields = bean.getClass().getDeclaredFields();
       if (ObjectUtils.isEmpty(declaredFields)) {
           return pvs;
       }
       for (Field field : declaredFields) {
           EncryptValue encryptValue = field.getAnnotation(EncryptValue.class);
           if (ObjectUtils.isEmpty(encryptValue)) {
               continue;
           }
           String encryptKey = encryptValue.value();
           String encryptValueStr = environment.getProperty(encryptKey);
           if (!org.springframework.util.StringUtils.hasText(encryptValueStr)) {
               log.warn("配置key:{}对应的加密值为空", encryptKey);
               continue;
           }
           String decryptValue = new String(Base64.getDecoder().decode(encryptValueStr));
           ReflectionUtils.makeAccessible(field);
           ReflectionUtils.setField(field, bean, decryptValue);
           log.info("Bean[{}]的字段[{}]解密注入完成", beanName, field.getName());
       }
       return pvs;
   }
}

测试类:

package com.jam.demo.config;

import com.jam.demo.annotation.EncryptValue;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
* 加密配置测试类
* @author ken
*/

@Slf4j
@Data
@Component
public class EncryptConfigTest {

   @EncryptValue("encrypt.db-password")
   private String dbPassword;

   public void printPassword() {
       log.info("解密后的数据库密码:{}", dbPassword);
   }
}

3.3 属性填充完成阶段:容器核心组件感知

该阶段在Bean属性填充完成后执行,通过Aware接口获取Spring容器的核心组件,实现对容器的感知。

3.3.1 Aware接口家族

核心作用:回调注入Spring容器的核心组件,解除Bean对容器的耦合。源码触发时机:AbstractAutowireCapableBeanFactory#initializeBean()中,先执行invokeAwareMethods()处理Aware接口,执行时机在初始化之前。高频Aware接口说明

  1. BeanNameAware:注入当前Bean的名称
  2. BeanFactoryAware:注入BeanFactory实例
  3. ApplicationContextAware:注入ApplicationContext上下文
  4. EnvironmentAware:注入环境配置对象Environment
  5. ResourceLoaderAware:注入资源加载器ResourceLoader

实战示例:Aware接口综合使用

package com.jam.demo.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

/**
* Aware接口回调测试类
* @author ken
*/

@Slf4j
@Component
public class AwareTestService implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, EnvironmentAware {

   private String beanName;
   private BeanFactory beanFactory;
   private ApplicationContext applicationContext;
   private Environment environment;

   @Override
   public void setBeanName(String name) {
       this.beanName = name;
       log.info("BeanNameAware.setBeanName执行,beanName:{}", name);
   }

   @Override
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
       this.beanFactory = beanFactory;
       log.info("BeanFactoryAware.setBeanFactory执行");
   }

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       this.applicationContext = applicationContext;
       log.info("ApplicationContextAware.setApplicationContext执行");
   }

   @Override
   public void setEnvironment(Environment environment) {
       this.environment = environment;
       log.info("EnvironmentAware.setEnvironment执行");
   }

   public void printContainerInfo() {
       log.info("当前Bean名称:{}", beanName);
       log.info("应用名称:{}", environment.getProperty("spring.application.name"));
       log.info("容器中Bean的数量:{}", applicationContext.getBeanDefinitionCount());
   }
}

3.4 Bean初始化阶段:Spring最核心的扩展点

该阶段是Bean生命周期中最核心的扩展阶段,Spring AOP、注解处理等核心能力均基于此实现。

3.4.1 BeanPostProcessor

核心作用:Bean初始化前后的回调,可对Bean进行包装、修改、生成代理对象,是Spring AOP的底层实现核心。源码触发时机:AbstractAutowireCapableBeanFactory#initializeBean()中,先执行applyBeanPostProcessorsBeforeInitialization,再执行初始化方法,最后执行applyBeanPostProcessorsAfterInitialization。核心方法说明

  1. postProcessBeforeInitialization:初始化前执行,可修改Bean属性、参数校验
  2. postProcessAfterInitialization:初始化后执行,生成AOP代理对象的核心位置

实战示例:操作日志注解实现自定义注解:

package com.jam.demo.annotation;

import java.lang.annotation.*;

/**
* 操作日志注解,标注在方法上自动打印方法执行日志
* @author ken
*/

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {

   /**
    * 操作描述
    * @return 操作描述
    */

   String value();
}

处理器实现:

package com.jam.demo.processor;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.annotation.OperateLog;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;

/**
* 自定义Bean后置处理器
* @author ken
*/

@Slf4j
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {

   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
       return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
   }

   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
       Method[] declaredMethods = bean.getClass().getDeclaredMethods();
       boolean hasOperateLog = false;
       for (Method method : declaredMethods) {
           if (!ObjectUtils.isEmpty(method.getAnnotation(OperateLog.class))) {
               hasOperateLog = true;
               break;
           }
       }
       if (hasOperateLog) {
           ProxyFactory proxyFactory = new ProxyFactory(bean);
           proxyFactory.addAdvice((MethodInterceptor) invocation -> {
               Method method = invocation.getMethod();
               OperateLog operateLog = method.getAnnotation(OperateLog.class);
               String operateDesc = operateLog.value();
               String className = method.getDeclaringClass().getName();
               String methodName = method.getName();
               Object[] args = invocation.getArguments();
               long startTime = System.currentTimeMillis();
               log.info("【操作日志】开始执行,操作描述:{}, 类名:{}, 方法名:{}, 入参:{}",
                       operateDesc, className, methodName, JSON.toJSONString(args));
               Object result = null;
               try {
                   result = invocation.proceed();
                   log.info("【操作日志】执行成功,操作描述:{}, 耗时:{}ms, 出参:{}",
                           operateDesc, System.currentTimeMillis() - startTime, JSON.toJSONString(result));
                   return result;
               } catch (Throwable e) {
                   log.error("【操作日志】执行异常,操作描述:{}, 耗时:{}ms, 异常信息:{}",
                           operateDesc, System.currentTimeMillis() - startTime, e.getMessage(), e);
                   throw e;
               }
           });
           return proxyFactory.getProxy();
       }
       return bean;
   }
}

测试服务:

package com.jam.demo.service;

import com.jam.demo.annotation.OperateLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
* 操作日志测试服务类
* @author ken
*/

@Slf4j
@Service
public class OperateLogTestService {

   /**
    * 测试方法
    * @param username 用户名
    * @param age 年龄
    * @return 拼接后的字符串
    */

   @OperateLog("测试用户信息查询")
   public String testOperateLog(String username, Integer age) {
       log.info("执行testOperateLog方法,username:{}, age:{}", username, age);
       return "用户名:" + username + ", 年龄:" + age;
   }
}

3.4.2 InitializingBean + @PostConstruct

核心作用:Bean属性填充完成后执行初始化逻辑,完成资源加载、参数校验等操作。执行顺序(100%准确):@PostConstruct → InitializingBean.afterPropertiesSet → init-method核心区别

  • @PostConstruct:JSR-250标准注解,无Spring耦合,由CommonAnnotationBeanPostProcessor处理
  • InitializingBean:Spring原生接口,耦合度高,无需反射,性能更好
  • init-method:XML/注解配置的初始化方法,完全解耦

实战示例:InitializingBean初始化线程池

package com.jam.demo.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* InitializingBean测试类
* @author ken
*/

@Slf4j
@Component
public class InitializingBeanTestService implements InitializingBean {

   private ThreadPoolExecutor threadPoolExecutor;

   @Override
   public void afterPropertiesSet() throws Exception {
       log.info("执行InitializingBean.afterPropertiesSet方法,初始化线程池");
       this.threadPoolExecutor = new ThreadPoolExecutor(
               2,
               8,
               60,
               TimeUnit.SECONDS,
               new ArrayBlockingQueue<>(100),
               r -> new Thread(r, "business-thread-" + r.hashCode()),
               new ThreadPoolExecutor.CallerRunsPolicy()
       );
       log.info("业务线程池初始化完成");
   }

   public void submitTask(Runnable runnable) {
       if (ObjectUtils.isEmpty(threadPoolExecutor) || threadPoolExecutor.isShutdown()) {
           throw new IllegalStateException("线程池未初始化或已关闭");
       }
       threadPoolExecutor.submit(runnable);
   }

   public void shutdownThreadPool() {
       if (!ObjectUtils.isEmpty(threadPoolExecutor) && !threadPoolExecutor.isShutdown()) {
           threadPoolExecutor.shutdown();
           log.info("业务线程池已关闭");
       }
   }
}

3.5 容器启动完成阶段:启动后初始化

该阶段在Spring容器完全启动、所有单例Bean初始化完成后执行,用于执行启动后的初始化任务。

3.5.1 ApplicationRunner + CommandLineRunner

核心作用:容器启动完成后执行自定义逻辑,支持通过@Order注解控制执行顺序。核心区别

  • ApplicationRunner:入参为ApplicationArguments,封装了启动参数,支持--key=value格式参数解析
  • CommandLineRunner:入参为原始String数组,仅支持简单的启动参数处理

实战示例:启动后字典缓存预热MySQL表结构(MySQL 8.0+):

CREATE TABLE `sys_dict` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `dict_code` varchar(64) NOT NULL COMMENT '字典编码',
 `dict_name` varchar(128) NOT NULL COMMENT '字典名称',
 `dict_value` varchar(256) NOT NULL COMMENT '字典值',
 `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态 0-禁用 1-启用',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_dict_code_value` (`dict_code`,`dict_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统字典表';

实体类:

package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.time.LocalDateTime;

/**
* 系统字典实体类
* @author ken
*/

@Data
@TableName("sys_dict")
@Schema(description = "系统字典实体")
public class SysDict {

   @TableId(type = IdType.AUTO)
   @Schema(description = "主键ID", example = "1")
   private Long id;

   @Schema(description = "字典编码", example = "user_status")
   private String dictCode;

   @Schema(description = "字典名称", example = "用户状态")
   private String dictName;

   @Schema(description = "字典值", example = "1")
   private String dictValue;

   @Schema(description = "状态 0-禁用 1-启用", example = "1")
   private Integer status;

   @Schema(description = "创建时间")
   private LocalDateTime createTime;

   @Schema(description = "更新时间")
   private LocalDateTime updateTime;
}

Mapper接口:

package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.SysDict;
import org.apache.ibatis.annotations.Mapper;

/**
* 系统字典Mapper接口
* @author ken
*/

@Mapper
public interface SysDictMapper extends BaseMapper<SysDict> {
}

服务接口:

package com.jam.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.entity.SysDict;

import java.util.Set;

/**
* 系统字典服务接口
* @author ken
*/

public interface SysDictService extends IService<SysDict> {

   /**
    * 根据字典编码获取字典值集合
    * @param dictCode 字典编码
    * @return 字典值集合
    */

   Set<String> getDictValuesByCode(String dictCode);

   /**
    * 刷新字典缓存
    */

   void refreshDictCache();
}

服务实现:

package com.jam.demo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.jam.demo.entity.SysDict;
import com.jam.demo.mapper.SysDictMapper;
import com.jam.demo.service.SysDictService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* 系统字典服务实现类
* @author ken
*/

@Slf4j
@Service
public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> implements SysDictService {

   private final Map<String, Set<String>> dictCache = Maps.newConcurrentMap();

   @Override
   public Set<String> getDictValuesByCode(String dictCode) {
       if (!StringUtils.hasText(dictCode)) {
           return Sets.newHashSet();
       }
       return dictCache.getOrDefault(dictCode, Sets.newHashSet());
   }

   @Override
   public void refreshDictCache() {
       log.info("开始刷新字典缓存");
       LambdaQueryWrapper<SysDict> queryWrapper = new LambdaQueryWrapper<SysDict>()
               .eq(SysDict::getStatus, 1);
       Map<String, Set<String>> newDictCache = this.list(queryWrapper).stream()
               .filter(dict -> StringUtils.hasText(dict.getDictCode()) && StringUtils.hasText(dict.getDictValue()))
               .collect(Collectors.groupingBy(
                       SysDict::getDictCode,
                       Collectors.mapping(SysDict::getDictValue, Collectors.toSet())
               ));
       dictCache.clear();
       dictCache.putAll(newDictCache);
       log.info("字典缓存刷新完成,共加载{}个字典编码", dictCache.size());
   }
}

启动预热实现:

package com.jam.demo.runner;

import com.jam.demo.service.SysDictService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

/**
* 字典缓存预加载处理器
* @author ken
*/

@Slf4j
@Component
public class DictCachePreloadRunner implements ApplicationRunner {

   private final SysDictService sysDictService;

   public DictCachePreloadRunner(SysDictService sysDictService) {
       this.sysDictService = sysDictService;
   }

   @Override
   public void run(ApplicationArguments args) throws Exception {
       log.info("执行DictCachePreloadRunner.run,开始预加载字典缓存");
       sysDictService.refreshDictCache();
       log.info("字典缓存预加载完成");
   }
}

3.6 AOP高级扩展:PointcutAdvisor

核心作用:Spring AOP的原生高级扩展点,比@Aspect注解更灵活、性能更高,是中间件AOP实现的首选方案。底层逻辑:Spring AOP自动代理创建器基于BeanPostProcessor,扫描容器中所有Advisor,匹配目标Bean并生成代理对象。核心组成:Pointcut(切点,定义拦截规则) + Advice(通知,定义拦截逻辑)

实战示例:基于Guava的接口限流实现自定义注解:

package com.jam.demo.annotation;

import java.lang.annotation.*;

/**
* 接口限流注解
* @author ken
*/

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {

   /**
    * 每秒允许的请求数(QPS)
    * @return QPS
    */

   double permitsPerSecond();

   /**
    * 获取令牌的超时时间,单位毫秒
    * @return 超时时间
    */

   long timeout() default 500;
}

限流拦截器:

package com.jam.demo.aop;

import com.google.common.util.concurrent.RateLimiter;
import com.jam.demo.annotation.RateLimit;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 限流拦截器
* @author ken
*/

@Slf4j
public class RateLimitInterceptor implements MethodInterceptor {

   private final Map<String, RateLimiter> rateLimiterCache = new ConcurrentHashMap<>();

   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
       Method method = invocation.getMethod();
       RateLimit rateLimit = method.getAnnotation(RateLimit.class);
       if (ObjectUtils.isEmpty(rateLimit)) {
           return invocation.proceed();
       }
       String methodKey = method.getDeclaringClass().getName() + "#" + method.getName();
       RateLimiter rateLimiter = rateLimiterCache.computeIfAbsent(methodKey,
               k -> RateLimiter.create(rateLimit.permitsPerSecond()));
       boolean acquire = rateLimiter.tryAcquire(rateLimit.timeout(), java.util.concurrent.TimeUnit.MILLISECONDS);
       if (!acquire) {
           log.warn("接口限流触发,方法:{}, QPS限制:{}", methodKey, rateLimit.permitsPerSecond());
           throw new RuntimeException("系统繁忙,请稍后再试");
       }
       return invocation.proceed();
   }
}

切面配置:

package com.jam.demo.aop;

import com.jam.demo.annotation.RateLimit;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.stereotype.Component;

/**
* 限流切面配置
* @author ken
*/

@Component
public class RateLimitAdvisor extends AbstractPointcutAdvisor {

   private final Pointcut pointcut;
   private final RateLimitInterceptor interceptor;

   public RateLimitAdvisor() {
       this.pointcut = new AnnotationMatchingPointcut(null, RateLimit.class);
       this.interceptor = new RateLimitInterceptor();
   }

   @Override
   public Pointcut getPointcut() {
       return this.pointcut;
   }

   @Override
   public org.aopalliance.aop.Advice getAdvice() {
       return this.interceptor;
   }
}

3.7 事务扩展:TransactionSynchronization

核心作用:事务同步回调,在事务的不同阶段执行自定义逻辑,解决事务与外部操作的一致性问题。源码触发时机:Spring事务管理器在事务提交、回滚的各个阶段,会遍历所有注册的TransactionSynchronization,执行对应的回调方法。核心回调方法

  1. afterCommit():事务提交成功后执行,最常用的方法
  2. beforeCommit():事务提交前执行
  3. afterCompletion():事务完成后执行(提交/回滚都会执行)
  4. beforeCompletion():事务完成前执行

实战示例:事务提交后发送消息服务接口:

package com.jam.demo.service;

import com.jam.demo.entity.SysDict;

/**
* 事务测试服务接口
* @author ken
*/

public interface TransactionTestService {

   /**
    * 新增字典并在事务提交后发送消息
    * @param sysDict 字典数据
    */

   void addDictWithMessageSend(SysDict sysDict);
}

服务实现(编程式事务):

package com.jam.demo.service.impl;

import com.jam.demo.entity.SysDict;
import com.jam.demo.mapper.SysDictMapper;
import com.jam.demo.service.TransactionTestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
* 事务测试服务实现类
* @author ken
*/

@Slf4j
@Service
public class TransactionTestServiceImpl implements TransactionTestService {

   private final PlatformTransactionManager transactionManager;
   private final SysDictMapper sysDictMapper;

   public TransactionTestServiceImpl(PlatformTransactionManager transactionManager, SysDictMapper sysDictMapper) {
       this.transactionManager = transactionManager;
       this.sysDictMapper = sysDictMapper;
   }

   @Override
   public void addDictWithMessageSend(SysDict sysDict) {
       DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
       TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
       try {
           sysDictMapper.insert(sysDict);
           log.info("字典数据插入成功,id:{}", sysDict.getId());

           TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
               @Override
               public void afterCommit() {
                   log.info("事务提交成功,开始发送字典新增消息,dictCode:{}", sysDict.getDictCode());
                   sendMessage(sysDict);
                   log.info("字典新增消息发送完成");
               }

               @Override
               public void afterCompletion(int status) {
                   if (status == STATUS_ROLLED_BACK) {
                       log.error("事务回滚,取消消息发送,dictCode:{}", sysDict.getDictCode());
                   }
               }
           });

           transactionManager.commit(transactionStatus);
       } catch (Exception e) {
           transactionManager.rollback(transactionStatus);
           log.error("字典数据插入失败,事务回滚", e);
           throw e;
       }
   }

   private void sendMessage(SysDict sysDict) {
       log.info("模拟发送MQ消息,内容:{}", sysDict);
   }
}

3.8 容器关闭阶段:资源释放扩展

该阶段在Spring容器关闭时执行,用于释放资源、执行清理操作。核心扩展点

  1. DisposableBean:Spring原生接口,destroy()方法在容器关闭时执行
  2. @PreDestroy:JSR-250注解,执行时机早于DisposableBean.destroy()
  3. SmartLifecycle:更灵活的生命周期接口,可控制启动和关闭的执行顺序

实战示例:DisposableBean资源释放

package com.jam.demo.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;

/**
* 容器关闭资源释放测试类
* @author ken
*/

@Slf4j
@Component
public class DisposableBeanTestService implements DisposableBean {

   private final InitializingBeanTestService initializingBeanTestService;

   public DisposableBeanTestService(InitializingBeanTestService initializingBeanTestService) {
       this.initializingBeanTestService = initializingBeanTestService;
   }

   @Override
   public void destroy() throws Exception {
       log.info("执行DisposableBean.destroy方法,释放资源");
       initializingBeanTestService.shutdownThreadPool();
       log.info("所有资源释放完成");
   }
}

四、易混淆扩展点核心区分表

扩展点接口 核心触发时机 核心作用 易混淆点区分
BeanDefinitionRegistryPostProcessor Bean实例化之前,BeanFactoryPostProcessor之前 注册、修改、删除BeanDefinition 比BeanFactoryPostProcessor执行更早,拥有BeanDefinition注册能力,是其子接口
BeanFactoryPostProcessor Bean实例化之前,BeanDefinitionRegistryPostProcessor之后 修改已注册的BeanDefinition 无BeanDefinition注册能力,执行时机更晚
InstantiationAwareBeanPostProcessor Bean实例化前后、属性填充前后 控制Bean实例化和属性填充流程 触发时机远早于BeanPostProcessor,是其子接口
BeanPostProcessor Bean初始化前后 处理已实例化的Bean,生成代理对象 Spring AOP的核心实现点,实例化已完成
InitializingBean.afterPropertiesSet 属性填充完成后,init-method之前 Bean初始化逻辑 Spring原生接口,执行时机晚于@PostConstruct
@PostConstruct 属性填充完成后,afterPropertiesSet之前 Bean初始化逻辑 JSR-250注解,无Spring耦合,执行时机最早
ApplicationRunner 容器完全启动完成后 启动后初始化任务 入参封装了启动参数,解析更方便
CommandLineRunner 容器完全启动完成后 启动后初始化任务 入参为原始启动参数数组

五、测试接口(Swagger3)

package com.jam.demo.controller;

import com.jam.demo.annotation.OperateLog;
import com.jam.demo.annotation.RateLimit;
import com.jam.demo.service.OperateLogTestService;
import com.jam.demo.service.SysDictService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.Set;

/**
* 测试控制器
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/test")
@Tag(name = "测试接口", description = "Spring扩展点测试接口")
public class TestController {

   private final OperateLogTestService operateLogTestService;
   private final SysDictService sysDictService;

   public TestController(OperateLogTestService operateLogTestService, SysDictService sysDictService) {
       this.operateLogTestService = operateLogTestService;
       this.sysDictService = sysDictService;
   }

   @GetMapping("/operate-log")
   @Operation(summary = "操作日志测试", description = "测试@OperateLog注解的功能")
   @OperateLog("测试操作日志接口")
   @RateLimit(permitsPerSecond = 10, timeout = 500)
   public String testOperateLog(
           @Parameter(description = "用户名", required = true, example = "test")
@RequestParam String username,
           @Parameter(description = "年龄", required = true, example = "20") @RequestParam Integer age) {
       return operateLogTestService.testOperateLog(username, age);
   }

   @GetMapping("/dict/{dictCode}")
   @Operation(summary = "字典查询", description = "根据字典编码查询字典值")
   @RateLimit(permitsPerSecond = 100, timeout = 200)
   public Set<String> getDictByCode(
           @Parameter(description = "字典编码", required = true, example = "user_status")
@PathVariable String dictCode) {
       return sysDictService.getDictValuesByCode(dictCode);
   }

   @PostMapping("/dict/refresh")
   @Operation(summary = "刷新字典缓存", description = "手动刷新字典缓存")
   @OperateLog("刷新字典缓存")
   public void refreshDictCache() {
       sysDictService.refreshDictCache();
   }
}

六、总结

Spring扩展点的核心设计思想是开闭原则,对扩展开放,对修改关闭。所有扩展点都严格遵循Bean生命周期的回调机制,掌握了生命周期,就掌握了Spring扩展的核心逻辑。

本文覆盖的12个核心扩展点,覆盖了Spring容器从启动到关闭的全流程,不仅能解决日常开发中的90%的扩展需求,更是阅读Spring生态中间件源码的基础。在实际开发中,应根据业务场景选择合适的扩展点,避免滥用导致生命周期混乱。

学习建议:先通过本文的示例跑通每个扩展点,再去看Spring源码中对应的触发逻辑,最后去看MyBatis Plus、Dubbo等框架的Spring整合源码,看它们是如何利用这些扩展点实现框架整合的,举一反三,真正吃透Spring的扩展能力。

目录
相关文章
|
2月前
|
消息中间件 Java 关系型数据库
吃透 Java 轻量级流程引擎 Easy Work:从核心原理到生产级落地全指南
Easy Work是一款开源轻量级Java流程引擎,基于状态机设计,摒弃BPMN复杂特性,学习成本降90%、性能提升3倍以上,专为中小微流程场景优化,5分钟即可快速集成上线。
357 2
|
20天前
|
算法 Java 关系型数据库
JVM GC 深度破局:G1 与 ZGC 底层原理、生产调优全链路实战
本文深度解析JDK17主流GC:G1(默认,兼顾吞吐与延迟)与ZGC(革命性低延迟,STW&lt;1ms)。涵盖核心理论(可达性分析、三色标记)、内存布局、全流程机制(SATB写屏障 vs 染色指针+读屏障)、关键参数调优及生产选型指南,助你精准定位性能瓶颈,高效优化JVM。
430 4
|
1月前
|
存储 缓存 NoSQL
Redis 生产级实战
Redis作为互联网业务的核心内存数据库,其生产环境的稳定性、性能与可扩展性直接决定了业务的可用性上限。多数开发者仅掌握基础的缓存读写操作,一旦面对集群搭建、数据备份、性能瓶颈排查、在线数据迁移等生产级场景,极易出现踩坑、故障甚至数据丢失问题。Redis作为互联网业务的核心基础设施,其生产环境的稳定性与性能直接决定了业务的上限。本文从集群搭建、冷热备份、性能调优、数据迁移四大核心生产场景出发,讲透了底层实现逻辑,提供了全量可落地、零错误的实战方案。
182 4
|
2月前
|
人工智能 自然语言处理 自动驾驶
企业有哪些agent应用场景(2026年2月最新版)
2026年,AI Agent步入“实战元年”,不再是概念验证,而是企业数字化转型的核心引擎。本文深度解析阿里巴巴瓴羊四大智能产品:Quick Audience(精准营销Agent)、Quick Service(情感化服务Agent)、Quick BI“小Q”(自然语言数据Agent)与Dataphin(自动驾驶数据治理Agent),展现其如何在营销、服务、决策与治理中实现“感知—决策—执行”闭环,构建企业智能化竞争力。(239字)
|
2月前
|
安全 开发工具 git
别再瞎用 Git 合并了!Merge vs Rebase 底层逻辑、适用场景与零坑操作全指南
本文深度解析Git中Merge与Rebase的本质区别:Merge安全可追溯,适合公共分支合并;Rebase线性整洁,仅限本地私有分支整理。从底层对象模型出发,结合实战示例与企业级最佳实践,厘清使用红线、避坑误区,助你彻底掌握分支合并决策逻辑。(239字)
619 2
|
1月前
|
XML Java 数据安全/隐私保护
彻底搞懂 Spring Boot 自动配置原理:从源码拆解到手写 Starter,零废话全干货
本文深入解析SpringBoot自动配置原理,基于SpringBoot 3.4.2版本详细拆解了自动配置的执行流程。主要内容包括:1)自动配置的本质是基于条件注解的动态JavaConfig配置类;2)核心执行流程通过AutoConfigurationImportSelector实现;3)SpringBoot 3.x采用新的自动配置注册方式;4)重点讲解了@Conditional系列条件注解的使用场景与常见坑点;5)通过开发自定义加密Starter实战演示完整实现过程。
487 3
|
24天前
|
存储 缓存 监控
JVM 运行时数据区全解:从底层原理到 OOM 根因定位全链路实战
JVM运行时数据区是Java内存管理的核心,分为线程私有区域(程序计数器、虚拟机栈、本地方法栈)和线程共享区域(堆、方法区)。不同区域有明确的OOM触发规则:堆内存不足引发Java heap space异常,元空间不足导致Metaspace异常,直接内存溢出表现为Direct buffer memory错误。排查OOM需结合异常类型、堆dump、GC日志等现场数据,使用MAT等工具分析内存泄漏点。
411 1
|
2月前
|
SQL 关系型数据库 MySQL
分库分表下的分页查询:底层逻辑、全场景坑点与生产级最优解
分库分表环境下分页查询的挑战与解决方案 在分库分表架构中,传统分页查询面临数据错乱、性能下降等核心问题。本文剖析了五种主流解决方案: 全局视野法:全量查询后归并排序,保证准确性但性能随分页深度下降 游标分页法:基于值定位,性能稳定但仅支持顺序翻页 分片键路由法:精准定位分片,性能最优但需携带分片键 ES索引法:支持复杂查询和跳页,但引入额外组件 范围分片优化:减少扫描分片数,仅适用于范围分片场景 生产实践需注意排序字段唯一性、深分页限制、分片键选择等关键点。
278 2
|
2月前
|
JavaScript 网络安全 数据处理
OpenClaw/Clawdbot阿里云一键部署+Windows本地搭建集成skills,新手10分钟上手
2026年,AI智能代理工具迎来爆发式增长,OpenClaw(原Clawdbot/Moltbot)凭借开源可控、多任务自动化、跨平台协同的核心优势,成为个人办公与轻量团队协作的“数字员工”首选。它不仅能实现自然语言交互,更能自动化完成文件处理、日程管理、邮件整理等实际工作,兼容Qwen、GPT、Claude等多款大模型,落地性极强。
327 0
|
15天前
|
存储 自然语言处理 算法
Elasticsearch 核心命脉:倒排索引、分片机制与全链路高性能调优实战
本文深度解析Elasticsearch三大核心:倒排索引(Term Dict/Posting List/FST压缩)、分片机制(主/副本协同、路由算法)及全链路调优(写入/查询/分片/JVM),辅以ES 8.x实战代码,助开发者突破性能瓶颈,构建高可用、高性能搜索系统。
259 1