如何正确覆盖 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 反射创建注释实例,但它不是类型安全的)。
以下是我的原始代码的一些问题以及对原始方法的想法:
- 您应该始终覆盖
_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)
}
_getAnnotation
return 类型应该可以为空
Kirill 已正确修复此问题,但未明确指出。 _getAnnotation
有时会 return 为空。
- 你(可能)不能有一个神奇的
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.
]
)
_hasAnnotation
和 _getAnnotation
并不是 JacksonAnnotationIntrospector
用来获取或检查注释的唯一方法
在使用类似方法创建条件 @JsonProperty
注释后,我注意到它不适用于枚举元素。经过一些调试后,我发现 findEnumValues
方法直接使用 java.lang.reflect.Field::getAnnotation
(由于 "various reasons" 在弃用 findEnumValue
中提到)。如果你想让你的条件注释起作用,你应该覆盖(至少)findEnumValues
.
- 小心
ObjectMapper::setAnnotationIntrospector
好吧,它的 Javadoc 明确指出:要小心。它取代了映射器的整个注释内省器,删除了模块内省器添加(链接)的所有内容。它没有出现在我的问题代码中(这是为了创建最小的示例)但实际上我不小心用 KotlinModule
破坏了反序列化。您可能需要考虑实施 JacksonModule 并将您的内省器附加到现有的内省器。
- 考虑另一种方法:在
NopAnnotationIntrospector
. 中实现特定于功能的方法
最后我采用了这种方法(主要是因为 4.)。我需要覆盖 findEnumValues
和 hasIgnoreMarker
,这对我来说已经足够了。它涉及一些来自 JacksonAnnotationMapper
的复制粘贴代码,但除非您必须将大量注释作为条件,否则它可能会起作用(无论如何实现它都涉及大量样板代码)。这样,您很可能真的想链接这个内省器,而不是 set
它。
我正在尝试创建一些 类 可由 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 反射创建注释实例,但它不是类型安全的)。
以下是我的原始代码的一些问题以及对原始方法的想法:
- 您应该始终覆盖
_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)
}
_getAnnotation
return 类型应该可以为空
Kirill 已正确修复此问题,但未明确指出。 _getAnnotation
有时会 return 为空。
- 你(可能)不能有一个神奇的
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.
]
)
_hasAnnotation
和_getAnnotation
并不是JacksonAnnotationIntrospector
用来获取或检查注释的唯一方法
在使用类似方法创建条件 @JsonProperty
注释后,我注意到它不适用于枚举元素。经过一些调试后,我发现 findEnumValues
方法直接使用 java.lang.reflect.Field::getAnnotation
(由于 "various reasons" 在弃用 findEnumValue
中提到)。如果你想让你的条件注释起作用,你应该覆盖(至少)findEnumValues
.
- 小心
ObjectMapper::setAnnotationIntrospector
好吧,它的 Javadoc 明确指出:要小心。它取代了映射器的整个注释内省器,删除了模块内省器添加(链接)的所有内容。它没有出现在我的问题代码中(这是为了创建最小的示例)但实际上我不小心用 KotlinModule
破坏了反序列化。您可能需要考虑实施 JacksonModule 并将您的内省器附加到现有的内省器。
- 考虑另一种方法:在
NopAnnotationIntrospector
. 中实现特定于功能的方法
最后我采用了这种方法(主要是因为 4.)。我需要覆盖 findEnumValues
和 hasIgnoreMarker
,这对我来说已经足够了。它涉及一些来自 JacksonAnnotationMapper
的复制粘贴代码,但除非您必须将大量注释作为条件,否则它可能会起作用(无论如何实现它都涉及大量样板代码)。这样,您很可能真的想链接这个内省器,而不是 set
它。