case JsonTokenId.ID_NULL: _verifyNullForPrimitive(ctxt); return0;
(中略) }
/** * Method called to verify that {@code null} token from input is acceptable * for primitive (unboxed) target type. It should NOT be called if {@code null} * was received by other means (coerced due to configuration, or even from * optionally acceptable String {@code "null"} token). */ protectedfinalvoid_verifyNullForPrimitive(DeserializationContext ctxt) throws DatabindException { if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) { ctxt.reportInputMismatch(this, "Cannot coerce `null` to %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)", _coercedTypeDesc()); } } (中略)
}
_parseIntPrimitive 调用的地方在 tools.jackson.databind.deser.jdk.NumberDeserializers.IntegerDeserializer.deserialize ,也就是jackson对于json number 类型的预设解析器中(其他类型同理)
可以看出到来,对于 int ,当 FAIL_ON_NULL_FOR_PRIMITIVES 没开启时jackson就直接返回 0 了。
@RestControllerAdvice(basePackages = ["com.example.app"]) classGlobalExceptionHandler { /** * 对于kotlin非空基本类型编译后成为java基本类型的字段校验,jackson会直接返回默认值,导致逃过了 `@NotNull` 校验。 * 开启jackson的 `FAIL_ON_NULL_FOR_PRIMITIVES` 后则会抛出异常,然后在全局异常处理中伪装成和spring校验失败时一样的response, * 以此来解决该问题。 * * @see tools.jackson.databind.deser.std.StdDeserializer._verifyNullForPrimitive */ @ExceptionHandler(HttpMessageNotReadableException::class) funhandleHttpMessageNotReadable(ex: HttpMessageNotReadableException): ResponseEntity<Any?> { val cause = ex.cause if (cause is MismatchedInputException) { val fieldName = cause.path.joinToString(".") { it.fieldName ?: "" }
val errorMsg = if (cause.targetType?.isPrimitive == true && cause.message?.contains("FAIL_ON_NULL_FOR_PRIMITIVES") == true ) "字段 [$fieldName] 缺失或不能为 null" else"字段 [$fieldName] 类型错误"
return ResponseEntity.badRequest().body(mapOf( "code" to 400, "field" to fieldName, "message" to errorMsg )) } return ResponseEntity.badRequest().body(mapOf("message" to "请求体格式错误")) } }
@JvmInline value classSInt( @get:JsonValueval value: Int? ) : Comparable<SInt>, Number() { // 继承 Number 甚至可以让它混入一些泛型计算
companionobject { @JsonCreator @JvmStatic funof(v: Int?): SInt = SInt(v) }
// --- 核心:手动赋予它 Int 的能力 ---
// 1. 强制非空获取 (业务层确信已校验过时使用) // 类似于 Integer.intValue() funv(): Int = value ?: throw IllegalStateException("SInt value is null (Validation failed?)")
// 2. 支持 + - * / 运算 // 允许: SInt + Int operatorfunplus(other: Int): Int = v() + other operatorfunminus(other: Int): Int = v() - other operatorfuntimes(other: Int): Int = v() * other operatorfundiv(other: Int): Int = v() / other
// 允许: SInt + SInt operatorfunplus(other: SInt): Int = v() + other.v() operatorfunminus(other: SInt): Int = v() - other.v()
// 3. 支持比较 (> < >= <=) // 允许: val isAdult = age >= 18 operatorfuncompareTo(other: Int): Int = v().compareTo(other) overrideoperatorfuncompareTo(other: SInt): Int = v().compareTo(other.v())
// 4. 实现 Number 抽象类的必须方法 overridefuntoByte(): Byte = v().toByte() overridefuntoDouble(): Double = v().toDouble() overridefuntoFloat(): Float = v().toFloat() overridefuntoInt(): Int = v() overridefuntoLong(): Long = v().toLong() overridefuntoShort(): Short = v().toShort() // 5. toString 优化 overridefuntoString(): String = value?.toString() ?: "null" }