前言
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()中执行后续方法。核心方法说明:
- postProcessBeforeInstantiation:实例化前执行,返回非null则跳过默认实例化
- postProcessAfterInstantiation:实例化后、属性填充前执行,返回false则跳过属性填充
- 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接口说明:
- BeanNameAware:注入当前Bean的名称
- BeanFactoryAware:注入BeanFactory实例
- ApplicationContextAware:注入ApplicationContext上下文
- EnvironmentAware:注入环境配置对象Environment
- 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。核心方法说明:
- postProcessBeforeInitialization:初始化前执行,可修改Bean属性、参数校验
- 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,执行对应的回调方法。核心回调方法:
- afterCommit():事务提交成功后执行,最常用的方法
- beforeCommit():事务提交前执行
- afterCompletion():事务完成后执行(提交/回滚都会执行)
- 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容器关闭时执行,用于释放资源、执行清理操作。核心扩展点:
- DisposableBean:Spring原生接口,destroy()方法在容器关闭时执行
- @PreDestroy:JSR-250注解,执行时机早于DisposableBean.destroy()
- 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的扩展能力。