Using Java annotations on a field


#1

I’ve been using a GraphQL / GRPC integration library written in Java with a good deal of success. I’ve recently hit a road block though. It’s called rejoiner and can be found at https://github.com/google/rejoiner

In this library, to remove a GraphQL field you need to create a variable in your module class, and add to this a @SchemaModification annotation. I believe the library treats these annotations differently when attached to a function rather than a variable, hence it is important that a variable is used.

I started along the lines of

@SchemaModification
val removeRegisterUserRemoteAddr = Type
    .find(RegisterUserRequest.getDescriptor())
    .removeField("remoteAddr")!!

But because Kotlin creates a getter function by default this didn’t work. I added the @JvmField annotation to prevent this, which looked about right in the resulting Java code, but then the @SchemaModification annotation wasn’t applied, and instead was against a static method:

/** @deprecated */
// $FF: synthetic method
@SchemaModification
public static void removeRegisterUserRemoteAddr$annotations() {
}

Any suggestions would be greatly appreciated.

Regards
Chris


#2

https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targets

Btw: it’s the first result on google :wink:


#3

Ha, that’s embarrassing! I thought I’d put in a decent search. Anyways, thanks - did the job that I needed.

Although I do have another question, and can’t find anything on google. When declaring the variable inline, Kotlin compiles this to a java variable, which has its value assigned in the constructor. Is there anyway to do this inline as this API that I’m using is pretty strict on the structure, and the author suggested that the assignment may need to be inline rather than via a constructor.

Regards
Chris


#4

I don’t really understand what you want. You can’t declare a variable with a backing field as inline. You can only create a property with just the getter and make this inline

inline val foo get() = 123
inline val bar = 123 // not possible, because bar has a backing field

Kotlin still generates the method for foo, but will inline it where possible. The getter is still there though so you can access it from java or via reflection.


#5

Sorry, I probably wasn’t explicit enough with my question.

The issue here relates more to the resulting Java code. It doesn’t seem great to me, but the syntax required in order to remove a GraphQL field in Java is:

@SchemaModification
TypeModification removePrivateTodoData =
  Type.find(Todo.getDescriptor()).removeField("privateTodoData");

By annotating the Kotlin field with @JvmField, and @field:SchemaModification, I get a result pretty close where it doesn’t create the property and just uses a java field. The Kotlin code below creates a java variable, and Annotates it with SchemaModification, but the value assignment is done in a default constructor rather than as part of the variable declaration. The library author thinks that this may be the cause of the issue.

@JvmField
@field:SchemaModification
val removeRegisterUserRemoteAddr = Type
    .find(RegisterUserRequest.getDescriptor())
    .removeField("remoteAddr")!!

I think this may be a limitation of the combination of Kotlin and a slightly unusual API.


#6

So if I understand you right your problem is that the generated java code looks like this

@SchemaModification
TypeModification removeRegisterUserRemoteAddr;

YourConstructor() {
    removeRegisterUserRemoteAddr = ...;
}

instead of having the value assignment done where the variable is declared (your first example).

I might be wrong (I’m not an expert on JVM bytecode) but I don’t think there is a difference in the actual bytecode.
Do you have any errors because of this?


#7

Yes, that is (possibly) the problem.

The library authors comment was

Does removeRegisterUserRemoteAddr have to be assigned a value in the constructor? I need to check, but maybe something runs in the SchemaModule constructor before it’s assigned.

I’m not by any means a JVM bytecode expert, but I know in C# (my day job) the compiler essentially generates a default constructor for any variables assigned inline with the declaration. Perhaps the sequence of constructor execution is causing the issue.

It’s not that I’m getting any errors (since adding the @field annotation prefix), but it isn’t working as expected.