Gatling场景与链路开发中def与val的使用时机最佳实践咨询
val/def的使用最佳实践 概念对齐(面向Java开发背景)
先建立和Java语义一致的认知,避免Scala语法带来的认知偏差:
val是类加载阶段完成一次性初始化的不可变常量,全程内存中只存在一个实例,等价于Java中类加载时就赋值的static final常量。def是每次被调用时才会执行函数体、生成全新对象的方法,每次调用都会产生独立的实例,等价于Java中每次调用都会new新对象的普通实例方法。
Gatling的DSL组件(ChainBuilder、ScenarioBuilder、HTTP请求构造器等)本身是不可变的,不存在共享状态污染问题,但两者的实例化时机差异,会直接影响运行时灵活性、内存开销和最终执行行为。
分场景适配规则
结构固定、无动态依赖的链路,优先用
val
这类组件本身逻辑不变,不需要在运行时动态拼接内容,用val可以避免重复实例化的内存开销。
参考现有代码中的定义,这类写法完全符合val的适用场景:val loginFlowWithExit = exec(loginFlow).exitHereIfFailed val teacherBranching = group("teacherBranching") { exec(loginFlow) .exec(session => sessionSetSessionVariable(session)) .exec(execFlaggedScenario(teacherDashboard)) .exec(logout()) }另外顶层
Scenario定义(比如代码中的rootTeacherScenario)因为只需要在Simulation启动时注册一次,必须用val声明——如果写成def,Simulation初始化时会因为反复调用生成重复的场景定义,导致框架加载异常。依赖外部配置、需要动态拼接逻辑的链路,必须用
def
针对提到的randomSwitch这类分支结构是否适合放在def里的问题:完全适合,甚至多数场景下必须用def。
核心原因是分支链路通常需要根据业务需求调整权重、替换挂载的子流程,用def声明时可以把可变的配置、子流程作为方法参数传入,每次调用时动态生成匹配当前需求的分支链路,不会因为val的单次初始化特性导致配置变更不生效。
比如现有代码中的teacherViewResources,如果后续需要给不同角色配置不同的分支占比、替换分支内的子流程,用def的扩展性会远好于val:// 适合用def的分支结构:可灵活调整子链路、分支权重 private def teacherViewResources( launchChain: ChainBuilder, assignChain: ChainBuilder, weightConfig: Map[String, Double] ): ChainBuilder = exec(viewResourcesFlow) .randomSwitch( weightConfig("launch") -> pause(1,2).exec(launchChain), weightConfig("assign") -> pause(1,2).exec(assignChain), weightConfig("next") -> pause(1,2).exec(teacherResourcesNext) )当然,如果你的
randomSwitch分支权重、挂载的子流程永远固定,没有调整需求,写成val也不会有功能问题,只是会损失灵活调整的扩展性。
常见避坑点
- 不要把依赖Session动态取值的链路硬写成
val:如果链路里需要从Session读取变量拼接请求路径、请求头,用def才能保证每个虚拟用户走到该节点时,都能读取到自己Session里的最新值,不会因为类加载时的单次初始化,把请求参数写死成第一个初始化实例的内容。 - 不要把所有链路都写成
def:比如顶层场景、无动态逻辑的公共子链路,如果每次调用都重新生成DSL对象,会在Simulation启动阶段产生大量重复实例,拖慢框架加载速度。 - 现有代码中的
teacherLaunchResource这类无参数、逻辑固定的单步链路,完全可以从def改成val,减少不必要的对象生成和方法调用开销。
内容的提问来源于stack exchange,提问作者Francislainy Campos

