实测 Spring 的类型转换:DefaultConversionService 能转基本类型、枚举(大小写敏感)和集合,不含 java.time 与 Duration;Converter 只管一个方向,Formatter 同时管 parse 与 print;DataBinder 把写错的字段变成 typeMismatch 错误并保持默认值;给绑定器注册的 PropertyEditor 优先于 ConversionService。
实测 Resource 抽象:没有前缀的路径按 classpath 找而不是文件系统;classpath: 只取第一个匹配,classpath*: 把 3 个 jar 里的 spring.factories 都找出来;jar 里的资源 getFile() 抛 FileNotFoundException、getInputStream() 正常;ClassPathResource 只读,InputStreamResource 只能读一次。
实测 SmartLifecycle:启动按 phase 从小到大、停止从大到小,所有 stop 完成之后才执行 @PreDestroy;refresh 只自动启动 isAutoStartup 为 true 的,context.start() 会启动所有 Lifecycle;同 phase 的异步停止并行(308 ms),不同 phase 串行(918 ms);不回 callback 的 Bean 会拖到 timeoutPerShutdownPhase(1000 ms)才继续。
实测 @Lazy:50 个构造要 20 ms 的单例,饿汉 refresh 1263 ms、懒加载 86 ms,但第一次取其中 5 个要 127 ms;配置错误在懒加载下推迟到第一次使用才抛出;注入点 @Lazy 只推迟解析,目标和注入点两处都 @Lazy 才推迟创建;带 @EventListener 的 Bean 不会被 @Lazy 推迟;SpringApplication.setLazyInitialization(true) 让单例从 22 个变成 20 个。
实测 Spring AOP 的代理选择:纯 Spring 里有接口的类默认用 JDK 代理(instanceof 实现类为 false、按具体类取 Bean 失败),没有接口用 CGLIB;proxyTargetClass = true 全部走 CGLIB;Spring Boot 不设任何属性也默认走 CGLIB;创建 2000 个代理 JDK 约 130 ms、CGLIB 约 70 ms,每次调用两者都约 40 ns。
实测切点表达式匹配:execution、within、@within、@annotation、target、args、bean 各自命中哪些方法;自调用的内部方法不命中;CGLIB 代理下包可见和 protected 方法也被拦截;带 args、@annotation、target 的切点每次调用比单纯 execution 贵约一倍;静态就不可能匹配的切点不创建代理。
实测通知执行顺序:切面按 @Order 从外到内包裹,同一个切面里是 @Around → @Before → 目标 → @AfterReturning(或 @AfterThrowing)→ @After;@Bean 定义顺序不影响有 @Order 的切面;同类型的两个 @Before 看起来按方法名排序;@Around 可以改参数或不调用 proceed();写在 @Bean 方法上的 @Order 对切面没有生效。
实测自调用失效:同一个类里 a() 调用 this.b(),切面对 b() 拦截 0 次(外部调用才是 1 次),因为 this 是目标对象不是代理;注入自己的代理、AopContext.currentProxy()(要开 exposeProxy)、拆成两个 Bean 都能把拦截次数变成 1;通过 CGLIB 代理读 public 字段得到 null。
实测 CGLIB 代理的限制:final 方法不被拦截而且在字段未初始化的代理上执行(读到 null);private 与 static 方法不单独拦截;靠 Objenesis 构造器只调用 1 次(关掉后调用 2 次,只有带参构造器的类会失败);final 类抛 Cannot subclass final class。
实测 @Async:纯 Spring 默认每次调用新建一个线程(100 次调用 100 个线程),Boot 默认 8 个线程的 applicationTaskExecutor;void 方法的异常默认只记 ERROR 日志;返回 String 的 @Async 方法直接抛 IllegalArgumentException;ThreadLocal 不会带到异步线程,TaskDecorator 可以传递;执行器满了默认拒绝 6 个任务、CallerRunsPolicy 让调用者自己跑。
实测 @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 缓存抽象:默认键只由参数决定(不含方法名,同缓存同参数的不同方法会冲突);null 默认也被缓存;8 个线程并发取同一个键,没有 sync 目标方法执行 8 次、sync = true 只执行 1 次;@CacheEvict 在方法抛异常时默认不清缓存;ConcurrentMapCacheManager 缓存的是引用;自调用不走缓存。