Spring Controller启用initDirectFieldAccess首次调用耗时异常问题
Spring MVC 开启initDirectFieldAccess后首次接口调用高耗时问题
问题背景
开发Spring Controller接口时,方法入参为带校验注解的JavaBean对象,同时配置@InitBinder方法启用直接字段访问,相关代码如下:
@InitBinder public void initBinder(WebDataBinder binder) { binder.initDirectFieldAccess(); log.info("ending initBinder"); } @PostMapping public @ResponseBody ResponseEntity<OpValidateResponse> validateClient( @RequestBody @Valid OpValidateRequest request) { log.info("Beginning execution of method"); final OpValidateResponse response = validateClientService .validateClient(sanitizeXSSForObject(request)); return ResponseEntity.ok().body(response); }
异常现象
- 仅接口首次被调用时,
@InitBinder方法打印"ending initBinder"日志,到validateClient方法打印"Beginning execution of method"日志之间耗时可达6-10秒 - 接口后续被调用时,两个日志点之间无明显高耗时
- 注释掉
binder.initDirectFieldAccess();代码后,无论第几次调用,两个日志点之间几乎不存在耗时
核心疑问
- 是否是
binder.initDirectFieldAccess();语句涉及的反射操作导致首次调用耗时过长? - 为什么后续调用时两个方法之间几乎无额外耗时?是否存在某种缓存机制?
问题解答
关于首次调用高耗时的原因
首次调用的高耗时确实和initDirectFieldAccess()触发的运行时初始化逻辑直接相关,但不是这行代码本身执行慢,而是它修改了WebDataBinder的默认参数绑定策略,触发了首次请求时的懒加载扫描流程:
- 默认状态下WebDataBinder使用JavaBean属性访问策略,依赖getter/setter方法完成参数绑定,这套逻辑对应的类属性描述符、基础元数据大多在Spring容器启动阶段就已经完成扫描和缓存,不会在首次接口调用时产生大量开销。
- 调用
initDirectFieldAccess()后,binder会切换为直接字段访问策略,不再依赖getter/setter方法,而是直接操作类的字段完成参数绑定、校验逻辑。首次请求触发时,Spring需要完成一系列初始化操作:- 递归扫描入参类
OpValidateRequest及其所有父类的全量字段,过滤静态、瞬态等不需要绑定的字段 - 为每个待绑定字段生成反射
Field对象,调用setAccessible(true)绕过访问权限检查 - 解析字段上标注的所有JSR-303/380校验注解,构建对应校验器的执行链
- 初始化直接字段访问专属的属性访问器、类型转换器实例
如果入参类继承层级较深、字段数量多、校验规则复杂,再叠加JVM首次加载反射相关类、JIT编译器未完成热点代码编译的冷启动开销,总耗时达到6-10秒是完全可能的。
- 递归扫描入参类
关于后续调用无额外耗时的原因
后续调用无高耗时完全是Spring内置缓存机制生效的结果:
- Spring对参数绑定、数据校验过程中产生的类元数据做了类级别的全局缓存,缓存为线程安全的
ConcurrentHashMap结构,key为入参对象的Class类型,value为提前解析完成的字段列表、反射Field对象、校验注解元数据、类型转换器映射关系等全量绑定所需信息。 - 首次请求完成全量扫描解析后,后续所有相同类型入参的绑定、校验流程,都会直接从缓存中读取已构建好的元数据,不需要重复执行反射扫描、注解解析操作,耗时自然会降到极低水平。
- 注释掉
initDirectFieldAccess()后看不到高耗时,是因为默认的JavaBean属性访问策略对应的元数据,要么在Spring容器启动阶段已经完成预扫描,要么在容器初始化其他同结构Bean时已经提前触发了解析和缓存,不会在接口调用时产生冷启动开销。
内容的提问来源于stack exchange,提问作者fernando1979
相关产品推荐
相关产品推荐

