首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >吃透 Spring 12 个核心扩展点:从源码底层到生产级实战,90% 的高级开发都在用

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

作者头像
果酱带你啃java
发布2026-04-14 14:50:51
发布2026-04-14 14:50:51
470
举报

前言

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

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

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

二、项目基础环境配置

pom.xml配置如下:

代码语言:javascript
复制
<?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基础配置:

代码语言:javascript
复制
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

代码语言:javascript
复制
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
publicclass 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");
    }
}
代码语言:javascript
复制
package com.jam.demo.service;

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

/**
 * 动态注册的服务类
 * @author ken
 */
@Slf4j
@Data
publicclass 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的属性

代码语言:javascript
复制
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
publicclass 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:属性填充前执行,实现自定义属性注入逻辑

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

代码语言:javascript
复制
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();
}

处理器实现:

代码语言:javascript
复制
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
publicclass CustomInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

    privatefinal 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;
    }
}

测试类:

代码语言:javascript
复制
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
publicclass 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接口综合使用

代码语言:javascript
复制
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
publicclass 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代理对象的核心位置

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

代码语言:javascript
复制
package com.jam.demo.annotation;

import java.lang.annotation.*;

/**
 * 操作日志注解,标注在方法上自动打印方法执行日志
 * @author ken
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {

    /**
     * 操作描述
     * @return 操作描述
     */
    String value();
}

处理器实现:

代码语言:javascript
复制
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
publicclass 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;
    }
}

测试服务:

代码语言:javascript
复制
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
publicclass 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初始化线程池

代码语言:javascript
复制
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
publicclass 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()) {
            thrownew 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+):

代码语言:javascript
复制
CREATE TABLE`sys_dict` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`dict_code`varchar(64) NOTNULLCOMMENT'字典编码',
`dict_name`varchar(128) NOTNULLCOMMENT'字典名称',
`dict_value`varchar(256) NOTNULLCOMMENT'字典值',
`status`tinyintNOTNULLDEFAULT'1'COMMENT'状态 0-禁用 1-启用',
`create_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
`update_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',
  PRIMARY KEY (`id`),
UNIQUEKEY`uk_dict_code_value` (`dict_code`,`dict_value`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统字典表';

实体类:

代码语言:javascript
复制
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 = "系统字典实体")
publicclass 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接口:

代码语言:javascript
复制
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> {
}

服务接口:

代码语言:javascript
复制
package com.jam.demo.service;

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

import java.util.Set;

/**
 * 系统字典服务接口
 * @author ken
 */
publicinterface SysDictService extends IService<SysDict> {

    /**
     * 根据字典编码获取字典值集合
     * @param dictCode 字典编码
     * @return 字典值集合
     */
    Set<String> getDictValuesByCode(String dictCode);

    /**
     * 刷新字典缓存
     */
    void refreshDictCache();
}

服务实现:

代码语言:javascript
复制
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
publicclass SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> implements SysDictService {

    privatefinal 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());
    }
}

启动预热实现:

代码语言:javascript
复制
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
publicclass DictCachePreloadRunner implements ApplicationRunner {

    privatefinal 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的接口限流实现自定义注解:

代码语言:javascript
复制
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;
}

限流拦截器:

代码语言:javascript
复制
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
publicclass RateLimitInterceptor implements MethodInterceptor {

    privatefinal 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());
            thrownew RuntimeException("系统繁忙,请稍后再试");
        }
        return invocation.proceed();
    }
}

切面配置:

代码语言:javascript
复制
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
publicclass RateLimitAdvisor extends AbstractPointcutAdvisor {

    privatefinal Pointcut pointcut;
    privatefinal RateLimitInterceptor interceptor;

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

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

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

3.7 事务扩展:TransactionSynchronization

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

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

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

代码语言:javascript
复制
package com.jam.demo.service;

import com.jam.demo.entity.SysDict;

/**
 * 事务测试服务接口
 * @author ken
 */
public interface TransactionTestService {

    /**
     * 新增字典并在事务提交后发送消息
     * @param sysDict 字典数据
     */
    void addDictWithMessageSend(SysDict sysDict);
}

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

代码语言:javascript
复制
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
publicclass TransactionTestServiceImpl implements TransactionTestService {

    privatefinal PlatformTransactionManager transactionManager;
    privatefinal 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资源释放

代码语言:javascript
复制
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
publicclass DisposableBeanTestService implements DisposableBean {

    privatefinal 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)

代码语言:javascript
复制
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扩展点测试接口")
publicclass TestController {

    privatefinal OperateLogTestService operateLogTestService;
    privatefinal 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的扩展能力。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-02-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、Spring Bean完整生命周期全景
  • 二、项目基础环境配置
  • 三、核心扩展点详解(按生命周期顺序)
    • 3.1 BeanDefinition注册阶段:容器最前置扩展
      • 3.1.1 BeanDefinitionRegistryPostProcessor
      • 3.1.2 BeanFactoryPostProcessor
    • 3.2 Bean实例化阶段:控制Bean的创建流程
      • 3.2.1 InstantiationAwareBeanPostProcessor
    • 3.3 属性填充完成阶段:容器核心组件感知
      • 3.3.1 Aware接口家族
    • 3.4 Bean初始化阶段:Spring最核心的扩展点
      • 3.4.1 BeanPostProcessor
      • 3.4.2 InitializingBean + @PostConstruct
    • 3.5 容器启动完成阶段:启动后初始化
      • 3.5.1 ApplicationRunner + CommandLineRunner
    • 3.6 AOP高级扩展:PointcutAdvisor
    • 3.7 事务扩展:TransactionSynchronization
    • 3.8 容器关闭阶段:资源释放扩展
  • 四、易混淆扩展点核心区分表
  • 五、测试接口(Swagger3)
  • 六、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档