知其然,知其所以然
Spring 全家桶 2026-10-19

类型转换:ConversionService、Formatter 与 PropertyEditor

实测 Spring 的类型转换:DefaultConversionService 能转基本类型、枚举(大小写敏感)和集合,不含 java.time 与 Duration;Converter 只管一个方向,Formatter 同时管 parse 与 print;DataBinder 把写错的字段变成 typeMismatch 错误并保持默认值;给绑定器注册的 PropertyEditor 优先于 ConversionService。

Spring 类型转换 ConversionService Converter Formatter DataBinder 真实验证 动画演示
Spring 全家桶 2026-10-19

Resource 抽象:classpath:、file:、classpath*: 的区别

实测 Resource 抽象:没有前缀的路径按 classpath 找而不是文件系统;classpath: 只取第一个匹配,classpath*: 把 3 个 jar 里的 spring.factories 都找出来;jar 里的资源 getFile() 抛 FileNotFoundException、getInputStream() 正常;ClassPathResource 只读,InputStreamResource 只能读一次。

Spring Resource ResourceLoader classpath 通配符 真实验证 动画演示
Spring 全家桶 2026-10-19

SmartLifecycle:启动与停止的阶段(phase)

实测 SmartLifecycle:启动按 phase 从小到大、停止从大到小,所有 stop 完成之后才执行 @PreDestroy;refresh 只自动启动 isAutoStartup 为 true 的,context.start() 会启动所有 Lifecycle;同 phase 的异步停止并行(308 ms),不同 phase 串行(918 ms);不回 callback 的 Bean 会拖到 timeoutPerShutdownPhase(1000 ms)才继续。

Spring SmartLifecycle Lifecycle phase 优雅停止 真实验证 动画演示
Spring 全家桶 2026-10-19

@Lazy 与懒加载:启动时间与首次请求代价的取舍

实测 @Lazy:50 个构造要 20 ms 的单例,饿汉 refresh 1263 ms、懒加载 86 ms,但第一次取其中 5 个要 127 ms;配置错误在懒加载下推迟到第一次使用才抛出;注入点 @Lazy 只推迟解析,目标和注入点两处都 @Lazy 才推迟创建;带 @EventListener 的 Bean 不会被 @Lazy 推迟;SpringApplication.setLazyInitialization(true) 让单例从 22 个变成 20 个。

Spring Lazy 懒加载 启动时间 真实验证 动画演示
Spring 全家桶 2026-10-19

Spring AOP 怎么建代理:JDK 与 CGLIB 的选择

实测 Spring AOP 的代理选择:纯 Spring 里有接口的类默认用 JDK 代理(instanceof 实现类为 false、按具体类取 Bean 失败),没有接口用 CGLIB;proxyTargetClass = true 全部走 CGLIB;Spring Boot 不设任何属性也默认走 CGLIB;创建 2000 个代理 JDK 约 130 ms、CGLIB 约 70 ms,每次调用两者都约 40 ns。

Spring AOP 代理 JDK动态代理 CGLIB proxyTargetClass 真实验证 动画演示
Spring 全家桶 2026-10-19

切点表达式:execution、within、@annotation 的匹配范围

实测切点表达式匹配:execution、within、@within、@annotation、target、args、bean 各自命中哪些方法;自调用的内部方法不命中;CGLIB 代理下包可见和 protected 方法也被拦截;带 args、@annotation、target 的切点每次调用比单纯 execution 贵约一倍;静态就不可能匹配的切点不创建代理。

Spring AOP 切点表达式 execution within 真实验证 动画演示
Spring 全家桶 2026-10-19

通知执行顺序:Around、Before、After、AfterReturning 与 @Order

实测通知执行顺序:切面按 @Order 从外到内包裹,同一个切面里是 @Around → @Before → 目标 → @AfterReturning(或 @AfterThrowing)→ @After;@Bean 定义顺序不影响有 @Order 的切面;同类型的两个 @Before 看起来按方法名排序;@Around 可以改参数或不调用 proceed();写在 @Bean 方法上的 @Order 对切面没有生效。

Spring AOP 通知顺序 Around Order 真实验证 动画演示
Spring 全家桶 2026-10-19

自调用失效:同一个类里调用为什么没有代理

实测自调用失效:同一个类里 a() 调用 this.b(),切面对 b() 拦截 0 次(外部调用才是 1 次),因为 this 是目标对象不是代理;注入自己的代理、AopContext.currentProxy()(要开 exposeProxy)、拆成两个 Bean 都能把拦截次数变成 1;通过 CGLIB 代理读 public 字段得到 null。

Spring AOP 自调用 代理 AopContext 真实验证 动画演示
Spring 全家桶 2026-10-19

CGLIB 代理的限制:final、private 与构造器被调用几次

实测 CGLIB 代理的限制:final 方法不被拦截而且在字段未初始化的代理上执行(读到 null);private 与 static 方法不单独拦截;靠 Objenesis 构造器只调用 1 次(关掉后调用 2 次,只有带参构造器的类会失败);final 类抛 Cannot subclass final class。

Spring AOP CGLIB final Objenesis 真实验证 动画演示
Spring 全家桶 2026-10-19

@Async:线程池、异常去向与上下文传播

实测 @Async:纯 Spring 默认每次调用新建一个线程(100 次调用 100 个线程),Boot 默认 8 个线程的 applicationTaskExecutor;void 方法的异常默认只记 ERROR 日志;返回 String 的 @Async 方法直接抛 IllegalArgumentException;ThreadLocal 不会带到异步线程,TaskDecorator 可以传递;执行器满了默认拒绝 6 个任务、CallerRunsPolicy 让调用者自己跑。

Spring Async 线程池 异常处理 TaskDecorator 真实验证 动画演示
Spring 全家桶 2026-10-19

@Scheduled:默认单线程调度器的串行与阻塞

实测 @Scheduled:默认调度器只有一个线程(纯 Spring 的 pool-1-thread-1、Boot 的 scheduling-1),一个耗时 1000 ms 的慢任务让每 200 ms 的快任务错过 5 次并在结束后 30 ms 内补跑 5 次;线程池设成 3 就不互相挡;fixedRate 与 fixedDelay 在任务比间隔慢时周期约 506 ms 对约 806 ms;任务抛异常不影响后续调度;加 @Async 会重叠执行。

Spring Scheduled 定时任务 fixedRate fixedDelay 真实验证 动画演示
Spring 全家桶 2026-10-19

@Cacheable:Key 生成、同步加载与自调用

实测 Spring 缓存抽象:默认键只由参数决定(不含方法名,同缓存同参数的不同方法会冲突);null 默认也被缓存;8 个线程并发取同一个键,没有 sync 目标方法执行 8 次、sync = true 只执行 1 次;@CacheEvict 在方法抛异常时默认不清缓存;ConcurrentMapCacheManager 缓存的是引用;自调用不走缓存。

Spring Cache Cacheable CacheEvict 缓存 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号