Reflection & Annotations — The Powerful Combination

Beknazar
5 min readOct 7, 2021

--

We tag our method with @Test and it becomes JUnit or TestNg unit test. We tag our class @Controller and it becomes a controller class in Spring. In this article, we will try to understand the background of these kinds of features in java.

Inversion of control

To get started with our discussion let’s talk about the inversion of control principle. It is commonly used by frameworks. The framework is a set of defined patterns to effectively complete a task. The framework has generic components with their roles. For example, let’s take frameworks for web applications, what kind of generic components or mechanisms they should have. Might be a controller where we can define endpoints and corresponding methods, a service where we can connect to the database and do some business logic. Now if we think almost every web project should have these mechanisms (controllers, services). But of course, the type of controllers and services and what they do will depend on the project.

That’s exactly what the Inversion Of Control principle is. It should give us a common structure and flow, but the logic we will write by ourselves.

Now, let’s take as our example the Spring framework. How will Spring know if your class is a controller class? Right, we tag it with annotation @Controller and after that, it will be part of the controllers and take specific features. The same thing with services in spring, we tag with @Service annotation and regular class will become a service with transactions and so on. Spring utilized inversion of control widely.

So how does it work? We know from the annotation article, annotation is used to give metadata to a class. The annotation itself is not enough, however, in combination with reflection, it will suit this job perfectly. The annotation will tag a regular class let’s say as controller and the reflections will scan classes and whatever class will have controller annotation will receive controller features.

So far I think you understand the main idea of using annotation and reflection together for the Inversion Of Control principle.

Let’s create something simple

Let’s create our own small framework and utilize Inversion Of Control with annotations and reflection. This is not a production version or complete framework. The main idea is to understand how does it work.

Github Repo: https://github.com/Suranchiyev/rest-client

The purpose of the framework is to create the rest clients to do requests and test responses (or actually just print them). It’s a Maven project with two dependencies(for the actual HTTP request part). Let’s see the structure of the project and pom.xml

First I have created test.java.restClient a package. It’s under test because I think it will be more useful for API testing.

Then I have test.java.test where I will have my test scripts

Ok, let’s start with our annotations

test.java.restClient.ResstClient.java

We will tag with this annotation our classes and we can provide the base URL this class will use to request/test APIs.

test.java.restClient.ClientRequest.java

With this annotation, we will tag our methods. We will provide a URL(which will be concatenated to the base URL) and all other details for this specific request in the method.

test.java.restClient.Response.java

With this annotation, we will tag our method argument and once we do request, we will put a response inside this argument, or better to say we will run this method with this response from the request as a method argument.

I decided to keep it super simple, but we could kind of create a specific response type and load the status code, response, and all other details of the response. Our response is just a String that represents the actual response body.

We have additional enum for request types

package restClient;

public enum RequestMethod {
GET, POST, PUT, DELETE
}

Ok, it’s time for the best part. If we think the annotations don’t give us much, we can tag the element and it provides extra information for it. But with reflection combination, we can do really cool stuff. Most of the(almost all) reverse of control frameworks will provide their running point because they need to scan the classes and provide them with features based on their annotations.

This is our reflection part and our framework will always get started from here

test.java.restClient.RequestRunner.java

When we run this class it looks at all classes under test.java.test package for the classes with RestClient annotation. Then it looks at the methods with ClientRequest annotation. Once found it gets all the needed information from annotation to do the request. In the last step, it calls this method with one argument which is a response.

The user can write now a bunch of RestClient classes with ClientRequest methods and do rest calls. They will get a response as a method argument and they can test the response and so on(we just printing it).

test.java.test.DummyRestApiTesting.java

and one more

test.java.test.DummyRestApiTesting.java

The output I got is here:

Summary

Inversion of control when the framework has ready flow and pattern but it lets user specify the business logic. The annotation and reflection can be used to build frameworks with an inversion of the control principle.

--

--