首页 > 解决方案 > 如何正确覆盖 JacksonAnnotationIntrospector._findAnnotation 以替换元素的注释

问题描述

我正在尝试创建一些杰克逊可序列化的类。我想用标准的杰克逊注释来注释一些元素(让我们考虑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())
    }
}

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

标签: javakotlinjacksonannotations

解决方案


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

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

  1. 您应该始终如一地覆盖_hasAnnotation_getAnnotation

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

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返回类型应该可以为空

基里尔正确地解决了这个问题,但没有明确指出。_getAnnotation有时可以并且会返回 null。

  1. 您(可能)不能有一个魔术MyConditional注释。

基里尔的回答可能会鼓励您为所有杰克逊注释创建类似条件注释的内容,如下所示:

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

你不能有多态注释参数。您必须为My*所需的每个 Jackson 注释创建注释,并且为带有参数的注释创建它不像使用@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直接使用(由于 deprecated 中提到的“各种原因” findEnumValue)。如果您希望条件注释起作用,您应该覆盖(至少)findEnumValues

  1. 小心ObjectMapper::setAnnotationIntrospector

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

  1. 考虑另一种方法:在NopAnnotationIntrospector.

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


推荐阅读