【问题标题】:How to properly override JacksonAnnotationIntrospector._findAnnotation to replace an annotation of the element如何正确覆盖 JacksonAnnotationIntrospector._findAnnotation 以替换元素的注释
【发布时间】:2019-10-21 23:54:23
【问题描述】:

我正在尝试创建一些杰克逊可序列化的类。我想用标准的 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())
    }
}

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

【问题讨论】:

    标签: java kotlin jackson annotations


    【解决方案1】:

    这里的主要问题是你不能实例化注解类。不过有一个解决方案:您可以像这样存储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 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"}
    

    【讨论】:

      【解决方案2】:

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

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

      1. 您应该始终覆盖_hasAnnotation_getAnnotation

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

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

      Kirill 已正确修复此问题,但未明确指出。 _getAnnotation 有时会返回 null。

      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(由于不推荐使用的findEnumValue中提到的“各种原因”)。如果您希望条件注释起作用,您应该(至少)覆盖findEnumValues

      1. 小心ObjectMapper::setAnnotationIntrospector

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-07-30
        • 1970-01-01
        • 2021-03-03
        • 2014-10-16
        • 2015-04-19
        • 1970-01-01
        • 2011-12-19
        • 2016-07-06
        相关资源
        最近更新 更多