Reflection in Java

Reflection is the ability of an application to inspect and modify the code in the system.

If an application can only inspect but not also modify the code then it only has the ability of introspection.

So for an application to be reflective it must also be able to modify its code at runtime.

Java is a language that supports reflection which allows us to do some nice things, such as examine classes at runtime, create instances of the class, look at its fields and methods, and modify or execute them without knowing anything about the classes in advance at compile time.

What this means is that one can look at the internals of any class that is in the classpath, list its properties (such as annotations, modifiers, methods, fields, constructors, etc…), execute the class’ methods or constructors, and modify the qualifiers of fields/methods..

The base for all of these actions is the aptly named Class class in the java.lang package.

We can see it has methods to:

  • List all constructors => #getAllConstructors
  • List all public methods => #getAllMethods
  • List all methods => #getAllDeclaredMethods
  • List all public fields => #getAllFields
  • List all fields => #getAllDeclaredFields
  • List all annotations => #getAllAnnotations
  • And many more

With these methods available to us, we can load a class in our Java application, get the information about its methods and constructors, and then use this information to create an instance of the class and execute the methods or change the class’ modifiers.

Reflection is a powerful tool in the programmers toolbox, but because of its dynamic resolving nature at runtime, it has the drawback of IDEs not being able to assist you in this area much since they mostly rely on static analysis, so you have to take care when dealing with reflective code.

I have encountered the scenario multiple times, where an IDE suggests that the method/class is not used and can be removed, but it was ultimately used via reflective calls, so removing this “unused” piece of code would be bad. In my cases this did not lead to multiple big issues and I was able to avoid problems because I kept looking around and/or testing the area after making the changes and before pushing the change to production, so the reflective use of the class/method was revealed.

My advice is that you should clearly document in code which areas are relying on reflection to get the job done and where/how the reflection is used to avoid problems that arise only at specific points during runtime, when the reflective logic fails to find its targets because they were removed/changed.

Reflection in the real world is used in very specific cases. Most of the time you can and should write code without using reflection for performance reasons, because reflection’ dynamic resolving brings some overhead.

So we, developers, must have a good reason to introduce reflection.

For instance, in Tomcat, reflection is used to load all the Tomcat classes, which is not a straightforward way, but it enables the Tomcat app itself to keep the classes “hidden” from the applications that we want to run in the Tomcat container. From their documentation on the base class that loads everything - the org.apache.catalina.startup.Bootstrap class:

Bootstrap loader for Catalina. This application constructs a class loader for use in loading the Catalina internal classes (by accumulating all of the JAR files found in the “server” directory under “catalina.home”), and starts the regular execution of the container. The purpose of this roundabout approach is to keep the Catalina internal classes (and any other classes they depend on, such as an XML parser) out of the system class path and therefore not visible to application level classes.


Examples

Example 1: Testing private methods

The most common example outside of frameworks and testing libraries is the example of modifying a private method into a public one and then executing it, which could be used to test private methods in a class although this is not recommended, since you should only be testing the public methods to ensure the correctness of your classes API.

The full example of using reflection to test a private method is in this github repository.


Example 2: “Real world” project

In our example we will create a project that dynamically loads plugins while running and starts them on demand.

The idea is that you can build a mechanism into your application to be able to extend it with some plugins while your application is running without restarting it. You can also use the plugin architecture to separate your program into a basic module that you offer and then develop extra functionality in the form of plugins which you can easily add or remove based on your customer requirements. This then also helps cut down on unnecessary functionality and space required by your application, but again, it might incur some performance overhead.

So let’s dive into the example:

The first thing we need to do is to define an interface, which our plugins will need to implement:

We defined a really simple interface with three methods that:

  • start the plugin => #startPlugin
  • stop the plugin => #stopPlugin
  • identify the plugin => #getPluginName

Our plugins will need to implement this interface since we will be reflectively looking for classes that are assignable from this interface (classes that are implementing it).

What is important here is that our plugin implementations have this interface in the same package (com.devflection.reflection) since the interface is identified with the combination of package name + class name - in our example “com.devflection.reflection.DevflectionPlugin”.

First we will implement a sample plugin, that we will very creatively call Plugin1 :) .

Plugin1 will be started and stopped using methods from the DevflectionPlugin interface. Those methods should return control to the main program as soon as possible, so in the startPlugin method we will create a new thread in which the plugin logic will periodically run and in the stopPlugin method we want to gracefully shutdown the plugin worker thread (let it finish the current execution cycle), so we will set a flag to tell the plugin it is time to shut down.

The worker thread in our example plugin prints a statement and goes to sleep, but you can easily imagine replacing this with some business logic that was actually useful here. This might be some batch analytics logic or logging of the current state of the system at particular moments in time or something else you might need. This is it for the implementation of our first plugin.

In our project we have two more plugin implementations, that are there to showcase a bit how the classes can be located in packages in our plugin implementations.

Plugin2 shows that our plugin implementation can be in a different package than the plugin interface.

And Plugin3 shows that the interface is not considered the same if the interface is put in a different package than it is in our plugin loader project although the interface definition is the same - ‘com.devflection.reflection’ vs. ‘com.devflection.plugin3’.

The reflective part of our DevflectionPluginLoader is in the lines 113-119.

Here we first load the class from the classname we got from the jar we are currently checking using the classloader that contains the jar file.

Then we check if the class implements our plugin interface, and if so, we reflectively create an instance.

The full instructions to build and run the example project are in the project readme file in the github repository.

When the application is running you can then play around with adding and removing plugins to the application. The app will scan for new jars every 30 seconds in the “plugins” directory of the project. When it finds a new plugin implementation it adds it to the list of current plugins and all the plugins can then be started or stopped. To then remove a jar from the “plugins” directory, you have to stop the plugin and remove it, which removes it from the list of current plugins. This makes it possible to remove the jar from the folder before the scan executes again.


This is it for our example reflection project. Thank you for reading through, I hope you found my (first ever) post about reflection in Java useful.


Improvements to do for practice:

Things that could be improved and/or extended for practice:

  • Extracting the plugin interface into a jar, that you reference as a library in all the projects, so you can more easily track versions
  • Improve the lifecycle management of the plugins (start, stop, remove) and add some actual business logic into the plugin implementations
  • Create a GUI to manage the plugins instead of the CLI
  • Create a backend that exposes these methods via a rest API and a frontend that consumes it and make a SPA (single page application) to manage the plugins