如何正确覆盖 JacksonAnnotationIntrospector._findAnnotation 以替换元素的注释

How to properly override JacksonAnnotationIntrospector._findAnnotation to replace an annotation of the element

我正在尝试创建一些 类 可由 Jackson 序列化的内容。我想用标准的 Jackson 注释来注释一些元素(让我们考虑 JsonIgnore 这个例子)但我希望它们只在我的特定映射器中有效。

所以我决定像标准注释一样创建自己的注释(例如 MyJsonIgnore),并仅在我的映射器使用的注释内省器中处理它们。我找到了可重写的方法 _findAnnotation。 Javadoc 说如下:

...overridable that sub-classes may, if they choose to, 
mangle actual access block access ("hide" annotations) 
or perhaps change it.

我找到了一种方法来阻止某些注释(但是它涉及覆盖 _hasAnnotation,而不是 _findAnnotation),但我完全无法更改注释。

这是我正在尝试做的一些最小示例:

object Mappers {
    /**
     * Same as JsonIgnore but mapper-specific
     */
    annotation class MyJsonIgnore(val value: Boolean = true)

    private class MyIntrospector : JacksonAnnotationIntrospector() {
        override fun <A : Annotation> _findAnnotation(
            annotated: Annotated,
            annoClass: Class<A>
        ): A {
            if (annoClass == JsonIgnore::class.java && _hasAnnotation(annotated, MyJsonIgnore::class.java)) {
                val mji = _findAnnotation(annotated, MyJsonIgnore::class.java)
                return JsonIgnore(mji.value) // Does not compile, type mismatch
                // return JsonIgnore(mji.value) as A // Does not compile, annotation class cannot be instantiated, same as Java, see below
            }
            return super._findAnnotation(annotated, annoClass)
        }
    }

    fun myMapper(): ObjectMapper {
        return ObjectMapper().setAnnotationIntrospector(MyIntrospector())
    }
}

我也做不到 Java:

public class Mappers {
    /**
     * Same as JsonIgnore but mapper-specific
     */
    public @interface MyJsonIgnore {
        boolean value() default true;
    }

    private static class MyIntrospector extends JacksonAnnotationIntrospector {
        @Override
        protected <A extends Annotation> A _findAnnotation(Annotated annotated,
                                                           Class<A> annoClass) {
            if (annoClass == JsonIgnore.class && _hasAnnotation(annotated, MyJsonIgnore.class)) {
                MyJsonIgnore mji = _findAnnotation(annotated, MyJsonIgnore.class);
                return new JsonIgnore(mji.value()); // Does not compile, JsonIgnore is abstract
            }
            return super._findAnnotation(annotated, annoClass);
        }
    }

    static ObjectMapper myMapper() {
        return new ObjectMapper().setAnnotationIntrospector(new MyIntrospector())
    }
}

那么通过覆盖此方法来更改注释的假定方法是什么?有没有?我的方法是正确的还是应该换一种方式?

这里的主要问题是您无法实例化注解class。不过有一个解决方案:您可以像这样存储 one annotation as a member of another annotation

@Retention(AnnotationRetention.RUNTIME) // don't forget 
@Target(AnnotationTarget.FIELD)         // these annotations
annotation class MyJsonIgnore(val value: Boolean = true, val jsonIgnore: JsonIgnore = JsonIgnore())

所以MyJsonIgnore里面会有一个实例化的JsonIgnore。然后你可以在你的 AnnotationIntrospector:

中使用它
private class MyIntrospector : JacksonAnnotationIntrospector() {
    override fun <A : Annotation> _findAnnotation(
            annotated: Annotated,
            annoClass: Class<A>
    ): A? {
        if (annoClass == JsonIgnore::class.java && _hasAnnotation(annotated, MyJsonIgnore::class.java)) {
            val mji = _findAnnotation(annotated, MyJsonIgnore::class.java)
            if (mji?.value == true) {
                return mji.jsonIgnore as A // this cast should be safe because we have checked
                                           // the annotation class
            }
        }
        return super._findAnnotation(annotated, annoClass)
    }
}

我用下面的方法测试过 class

class Test {
    @MyJsonIgnore
    val ignoreMe = "IGNORE"
    val field = "NOT IGNORE"
}

和方法

fun main() {
    println(Mappers.myMapper().writeValueAsString(Test()))
    println(ObjectMapper().writeValueAsString(Test()))
}

输出为

{"field":"NOT IGNORE"}
{"ignoreMe":"IGNORE","field":"NOT IGNORE"}

下面是我的进一步想法。 Kirill Simonov 的回答是正确的并且是类型安全的(另一种方法是使用 Kotlin 反射创建注释实例,但它不是类型安全的)。

以下是我的原始代码的一些问题以及对原始方法的想法:

  1. 您应该始终覆盖 _hasAnnotation_getAnnotation

您无法确定 _getAnnotation 会在 _hasAnnotation 检查后调用。在不查看 JacksonAnnotationIntrospector 代码的情况下,您无法确定它们中的哪一个将用于检查替换的注释(在我的例子中是 @JsonIgnore)。似乎一致地覆盖它们将是一个很好的做法。因此,如果我们想使用这种方法,我们还应该将以下覆盖添加到我们的 class 中:

override fun <A : Annotation> _hasAnnotation(
    annotated: Annotated,
    annoClass: Class<A>
): Boolean {
    if (annoClass == JsonIgnore::class.java && _hasAnnotation(annotated, MyJsonIgnore::class.java)) {
        return true
    }
    return super._hasAnnotation(annotated, annoClass)
}
  1. _getAnnotation return 类型应该可以为空

Kirill 已正确修复此问题,但未明确指出。 _getAnnotation 有时会 return 为空。

  1. 你(可能)不能有一个神奇的 MyConditional 注释。

Kirill 的回答可能会鼓励您为所有 jackson 注释创建类似条件注释的东西,可以按如下方式使用:

@MyConditional([
    JsonIgnore, JsonProperty("propertyName")
])

你只是不能有多态注释参数。您必须为每个需要的 Jackson 注释创建 My*,对于带有参数的注释,它不像 @MyJsonIgnore.

那样整洁

您可以尝试制作一个可重复的注释,它会像下面那样应用并使用反射实例化。

@MyConditional(
    clazz = JsonProperty::class.java,
    args = [
        // Ah, you probably cannot have an array of any possible args here, forget it.
    ]
)

  1. _hasAnnotation_getAnnotation 并不是 JacksonAnnotationIntrospector 用来获取或检查注释的唯一方法

在使用类似方法创建条件 @JsonProperty 注释后,我注意到它不适用于枚举元素。经过一些调试后,我发现 findEnumValues 方法直接使用 java.lang.reflect.Field::getAnnotation(由于 "various reasons" 在弃用 findEnumValue 中提到)。如果你想让你的条件注释起作用,你应该覆盖(至少)findEnumValues.

  1. 小心ObjectMapper::setAnnotationIntrospector

好吧,它的 Javadoc 明确指出:要小心。它取代了映射器的整个注释内省器,删除了模块内省器添加(链接)的所有内容。它没有出现在我的问题代码中(这是为了创建最小的示例)但实际上我不小心用 KotlinModule 破坏了反序列化。您可能需要考虑实施 JacksonModule 并将您的内省器附加到现有的内省器。

  1. 考虑另一种方法:在 NopAnnotationIntrospector.
  2. 中实现特定于功能的方法

最后我采用了这种方法(主要是因为 4.)。我需要覆盖 findEnumValueshasIgnoreMarker,这对我来说已经足够了。它涉及一些来自 JacksonAnnotationMapper 的复制粘贴代码,但除非您必须将大量注释作为条件,否则它可能会起作用(无论如何实现它都涉及大量样板代码)。这样,您很可能真的想链接这个内省器,而不是 set 它。