Home Safe Reflection Annotations
Post
Cancel

Safe Reflection Annotations

Today, I write about an annotation system called “Safe Reflection” which helps reduce runtime errors that can occur when using reflection.

Personally, I think reflection is a pretty crazy tool. It lets you perform action that go beyond the capabilities of a programming language. It’s basically the “evil” side of programming and hence, its use is discouraged.

The problem

One of the major issues with reflection arises with accessing fields and methods for various classes. For example, say we have some method getFieldA() which retrieves the field named a from some class MyClass.

1
2
3
public Field getFieldA() {
    return MyClass.class.getDeclaredField("a");
}
1
2
3
public class MyClass {
    String a;
}

Now, let’s assume that MyClass is actually from some imported library (so it’s not in your local project). Let’s say that the example above uses version 1.0 for this library.

Now let’s say that the developer of this library releases a new version 2.0 which has the following declaration:

1
2
3
public class MyClass {
    String b;
}

The field named a has been renamed to b in version 2.0 of this library. Therefore, if we want to use a different version of this library, we’d have to ensure that our getFieldA method actually has a field called a, which is only the case in version 1.0 of this library.

Normally, this can simply be resolved by performing some check for the version of the library that is being used. Take the following pseudocode:

1
2
3
4
5
if (library.version == 1.0):
    use field "a"
else if (library.version == 2.0):
    use field "b"
end if

Now this is all well and good, but what happens when the creator of the library releases 3.0? Will the field name change? If you don’t have access to the source code for the library, this will mean you’d have to decompile the new library, manually check if there has been a change to the name of the field you want to access and hope that your code is compatible with that library.

Basically, the problem boils down to the following points:

  • You have to take into account different library versions and method/field names that they use
  • Those method/field names could change at any given update
  • You don’t necessarily know which version of the library your clients are using, therefore you have to cover all cases

Creating the safe reflection annotation

So, this problem has been brought to my attention a number of times when creating plugins for Minecraft servers. I’ve been using a sneaky bit of reflection to bend the capabilities of what I can do when writing my Bukkit/Spigot plugins and then they release a new version which changes all of the variable and method names for basically no reason, which messes up my whole project.

The idea is simple. What if I have an annotation which states what fields or methods I want to access in what class and for what version. Then, I could have an annotation processor to handle that annotation at compile time which will perform the check to ensure that the field or method exists.

As usual, I go for the “write code first, plan stuff later” methodology and begin by creating an annotation that will handle the information that I would require. Luckily, that list is pretty well defined by what I want it to do overall.

  • The class which contains the field/method
  • The name of the field or method
  • The version of the library that contains this class with this field/method name

Luckily, this is the easy part. I take a quick peek at Oracle’s documentation on declaring an annotation and begin constructing such an annotation. Luckily, it’s possible to declare certain parameters as optional, so I make the field name and method name optional with the intention that whatever developer is using it will choose to use one of the two parameters.

Now I wanted to make it repeatable. Repeatable annotations basically mean you can have multiple of the same annotation on one member. I want it to be repeatable so I could have safe reflection for different library versions, for example:

1
2
3
@SafeReflection(target = MyClass.class, field = "a", version = "1.0")
@SafeReflection(target = MyClass.class, field = "b", version = "2.0")
Field someField = //...

Since I have never created a repeatable annotation, I find out that you have to have some other annotation which is sort of like a collection of the first. So, my “main” annotation is @SafeReflection and I made my repeating annotation SafeReflections, which basically contains a SafeReflection[]. This is basically the main setup that I required. To allow the code to be more condense and support multiple versions with the same mapping, I ensure that the version parameter is a String[], which leads to this effect, for example:

1
@SafeReflection(target = MyClass.class, field = "a", version = {"1.0", "1.1"})

Creating the annotation processor

Creating the annotation processor should be a breeze. I follow two generic tutorials, one from Baeldung, which I highly recommend1, and another from Medium to get an idea of what to do. From this, I create an annotation processor that processes the SafeReflection annotation, performs the safe reflection check by finding the relevant .jar file from the version number, finding the specific class (which is provided by the target parameter) and then checks if the field or method exists. If so, everything is fine and the compilation continues as normal. Otherwise, compilation fails.

Or rather, that is how I expect it to go. Upon testing my annotation processor, I run into three problems that take me a lot longer to fix than I expect.

Type mirrors

In my SafeReflection annotation, I have a parameter called target which is of type Class<?>. No problem there. Unfortunately, actually retrieving that class is a lot harder than you’d expect. Basically, trying to access a class throws a MirroredTypeException and I have no clue why and instead, found this little trick to retrieving a class from an annotation, which goes like this:

1
2
3
4
5
try {
    safeReflection.target();
} catch(MirroredTypeException e) {
    e.getTypeMirror() // Do something with this
}

Luckily for me, all I require is the name of the class, so converting the type mirror to a String was sufficient for my purposes.

Dealing with LOCAL_VARIABLE

With annotations, you can restrict where they can be used, by using the meta-annotation (yeah, it’s literally called that. Meta-annotations are annotations for annotations) @Target. I personally think it would be suitable to have the @SafeReflection annotation directly next to a variable which executes the reflection call, for example:

1
2
3
4
public void someMethod() {
    @SafeReflection(target = MyClass.class, field = "b", version = /* ... */)
    Field field = MyClass.class.getDeclaredField("b");
}

To have this specific behaviour, I use the meta-annotation @Target(ElementType.LOCAL_VARIABLE) which should have this effect. However! When I run the annotation processor to compile a test project, I find out quickly that the main method for processing annotations (called process) was never being called! It takes me ages of searching the internet to find out that annoyingly, annotation processors just cannot process local variables annotations2.

To resolve this, I come to the compromise that I’ll just have all of the annotations declared at the top of the class where reflective calls are made, which would have this sort of result:

1
2
3
4
5
6
7
8
9
@SafeReflection(/**/)
@SafeReflection(/**/)
public class SomeClass {

    public void someMethod() {
        //Various reflective calls here
    }

}

Again, not ideal, but a major improvement over using a Java compiler plugin (compiler plugins have the power to go beyond annotation processing, but it’s quite complex and not so easy to just implement in a project). I guess one of the pros is that all of the reflection declarations for that class are all in one place?

Repeatable annotations

So, you’d expect repeatable annotations to be the easiest thing to handle. Surely, since it has the repeatable tag, it’ll just convert all of the SafeReflection annotations into a single SafeReflections annotation which contains the array SafeReflection[]. And indeed, it does do this. Only if you have more than one annotation.

This leads to me having to implement something which looks like this:

1
2
3
4
5
6
7
if (number of annotations == 1):
    process the annotation
else if (number of annotations > 1):
    for each annotation:
        process the annotation
    end for
end if

Like, WHY?! Couldn’t they have just implemented it such that if your annotation is repeatable, always make it in the form of the collection?! This would lead to the trivial code below, which doesn’t need this doopy if statement:

1
2
3
for each annotation:
    process the annotation
end for

Conclusions

Overall, it works. Of course, it’s not fool proof - for example, you could just not use a @SafeReflection declaration, or declare it and not use that in your code, but the overall goal that I want to achieve, that is checking for fields and methods that are accessed using reflection at compile time, is now doable!

The SafeReflection project that I made (which can be found on GitHub) is tailored for my other project, the CommandAPI, which uses a bit of reflection here and there, spanning over many versions of Minecraft. By using SafeReflection, I’ve been able to ensure that the code will not throw reflection-based errors at runtime.


  1. Content wise, it’s not what I wanted, but the explanation on how to set up the development environment in maven, as well as include the annotation processor in another project is what makes it such a fantastic resource. 

  2. According to this StackOverflow answer 

This post is licensed under CC BY 4.0 by the author.

Proprietary software, TOS and other thoughts

A guy with a katana and a bathrobe