We’re trying to create a custom annotation that builds on Spring @Autowired
to provide internationalization support for fields. The code comes from https://moelholm.com/2016/10/15/spring-4-3-custom-annotations/, translated to Kotlin.
tl;dr: Kotlin generates a private static $annotations()
method that has our annotation instead of the field we declare it on, and Spring is (naturally) unable to use this method to set the field. How do we get Kotlin to stop creating this static method and/or put the annotation on the field, where (we think) it belongs?
Details with code:
Here’s a sample of intended usage:
class Foo {
@LocalizedMessage("foo.bar")
private lateinit var bar: Message
fun baz(): String {
return bar.getMessage()
}
}
Here’s LocalizedMessage
:
@Autowired
@Retention(AnnotationRetention.RUNTIME)
annotation class LocalizedMessage(val value: String = "")
The Message
class:
class Message(private val messageSource: MessageSource, private val messageKey: String) {
fun getMessage(vararg args: Any): String {
val locale = LocaleContextHolder.getLocale()
return messageSource.getMessage(messageKey, args, "", locale)
}
}
And finally, we have a @Configuration
class with the following function to resolve instances of Message
:
@Bean
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
fun message(ip: InjectionPoint): Message {
val annotation = ip.methodParameter.parameterAnnotations.find { it -> it.annotationClass == LocalizedMessage::class }
val localizedMessage = AnnotationUtils.getAnnotation(annotation, LocalizedMessage::class.java)
val resourceBundleKey = localizedMessage.value
return Message(messageSource(), resourceBundleKey)
}
The trouble we encounter is that when Spring attempts to autowire the private lateinit var bar: Message
we get the following error:
Creating instance of bean 'foo'
2018-01-03 14:31:25 - org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor Autowired annotation is not supported on static methods: private static void foo.Foo.bar$annotations()
Consequently, the bar
field is not initialized, and Kotlin (rightly) complains when it is accessed.
When we dug deeper we found that Kotlin is generating a $annotations
method on the class and putting the LocalizedMessage
annotation on it. So when Spring’s AutowiredAnnotationBeanPostProcessor
class iterates over the methods in the class, it finds this method.
Here’s a decompile of the relevant field and method:
private foo.Message bar;
descriptor: Lfoo/Message;
flags: (0x0002) ACC_PRIVATE
private static void bar$annotations();
descriptor: ()V
flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=0, locals=0, args_size=0
0: return
Deprecated: true
RuntimeVisibleAnnotations:
0: #10(#11=s#12)
Notice the RuntimeVisibleAnnotations
on the $annotations()
method. It points to the LocalizedMessage
annotation in the constants table.
So, our question, as per the tl;dr above, how can we create and use this custom annotation so that it is placed on the field, and not on this Kotlin-generated $annotations
method?
Thanks!