Unveiling Java annotations

[Versión en castellano]This link opens in a popup window

Annotations are, from my experience interviewing people, one of the perhaps most used but least understood features of Java Programming Language. On the one hand, they are the most used, because, nowadays, almost everybody is using Spring, Hibernate and all those frameworks and libraries that make magic under the hood. On the other hand, what I’ve found is that people are not able to explain properly what an annotation is, or how to make that magic on their own.

annotationsannotations

They are, in fact, related to metaprogrammingThis link opens in a popup window. In a not very orthodox way, we could define an annotation as the ability to tag declarations (fields, methods, parameters and classes) with information which will be used by an external processor in order to control the interaction with that declaration. Perhaps this definition is obscure, and you could wonder what kind of control I’m talking about. I could suggest several use cases to you, such as:

  • Extract information from the class, providing a custom mechanism to serialize object (transform it into a plain representation). An example could be GsonThis link opens in a popup window and its custom annotations.
  • Validate an input parameter (checking its nullity, emptiness or even if it matches a regexp or a complex algorithm), or even altering its value before the method is invoked. Think about the @NotNull or @NotEmpty annotations.
  • Have a plug-n-play engine. This engine could use annotations to detect – statically or dynamically – which classes are going to be plugged. I’m sure you are used to this use case if you are using SpringThis link opens in a popup window
  • Using annotations to modify class logic. This modification could be done by using a ProxyThis link opens in a popup window or even by altering its bytecode aided by ASMThis link opens in a popup window. A typical example could be a cache mechanism, as the one provided by SpringThis link opens in a popup window or many others.

Obviously the latter one is the more complex, and I don’t recommend you to write any custom bytecode manipulation except if you need to do it – as it was our case -, sinc­e it’s quite tricky and not very easy to test. Nevertheless, the other types of annotations (or even a proxy-based one) are not very complex, they are very valuable on their own, and they will certainly provide you with a deeper understanding of Java Programming Language (and related ones).

In order to give you a deeper understanding of annotation usage, I’m going to show you an example.

The problem

When we want to populate a database from a series of files, we have to solve several questions. Being, perhaps, the top:

  • Are we going to manage different information types?
  • Does this population involve data modification?

For this problem we are going to answer yes to both questions. Therefore, we have to solve issues related to the storage of different object types and being able to detect if a record has been modified. Solving the first question would involve either a BLOB storage (on a relational database) or another approach as a NoSQL database.

The latter is a different problem, detecting if an object is different in a file than in the database could be resolved in different ways. I think that the simplest approach is to generate a HASH (MD5, SHA or another) of the object and to do a comparison between the source-object hash and the target one.

This hash generation isn’t complex at all, but we should consider that an object is not only comprised of properties, but also state – or temporary information - and computed fields. Obviously the state fields, or even the computed ones, are not going to be used to compute the hash, but, perhaps, these fields should be stored within the database.

For the different solutions we are going to use an interface which will be responsible of hash generation, according to the different solutions we are going to implement.

Imagen 1Imagen 1

No custom annotations solution

In this solution we are going to use a Gson builtin solution, using the @Expose annotation, this approach is very simple and works swiftly.

Imagen 2Imagen 2

Imagen 3Imagen 3

This solution has two main issues:

  • On the one hand you have to tag all fields with you want to be serialized, you could easily forget one important.
  • On the other hand there is a semantic problem. The @Expose annotation is so common, that we cannot infer its use within this problem scope. We should document very carefully, and that isn’t even enough.

Simple custom annotations solution

In this solution we are going to use a custom annotation @HashHidden within Gson, this approach is almost as simple as the previous solution, and it solves the detected issues smoothly.

Imagen 4Imagen 4

Imagen 5Imagen 5

Not only does Java introduce a new keyword, @interface, but it’s also demanding us to use two standard Java annotations in order to allow proper @HashHidden working: @Retention and @Target. Let me explain them a bit more.

  • The @Retention tells us where the annotation information is going to be available. Options are:
    • RUNTIME: I’d say that this is the most common type, it’s stored in compiled bytecode and this information could be accessed from your code, using reflection and introspection.
    • CLASS: Annotation information is stored in compiled bytecode, but it’s not accessible from your code. The use case is complex, and I’ve only used them when manipulating the bytecode (ASM)
    • SOURCE: Annotation information is only available when compiling. I must admit that I’ve never used this type of annotation.
  • On the other hand, the @Target annotation defines in which definitions the annotation could be used, that means whether I could use it to annotate a class, a method, a field or a method parameter.

Complex custom annotations solution – under the hoods -

If you wonder how this custom annotation could be used, how the magic is done and you are eager to learn some easy Java internals, this solution is the one for you. Besides, in a more complex environment with a different problem type, previous solutions probably won’t be suitable.

In this approach we will have to use some reflectionThis link opens in a popup window to retrieve the fields, its values and its annotations.

Imagen 6Imagen 6

As you can notice it’s an oversimplified example, in which values are simply transformed to strings. But I think that it’s enough for showing how annotations could be use in a more complex way.

Something more

An annotation isn’t simply an empty declaration, you can define inner fields which have some properties as these ones:

  • Fields could be one of a "primitive" type, enum type, or even a nested annotation.
  • Fields could be of an array type.
  • Fields could have default values.
  • There is a special field named value. Using this you could omit field name when annotating a declaration.
  • You can use field value to perform some magic, for example a expression in a language like MVEL.

Somehow, defining these properties is similar to create a JSON in a language like JavaScript.

How we use them

At Divisa iT we have used annotations for different utilities within our framework, such as:

  • PUSH notifications: we use annotations to perform object serialization
  • Cache injection: we use annotations and byte code modification to inject cache logic.
  • Database-specific code selection: we use a repository pattern, with multi database support. Annotations are used to select proper implementation according to target database.
  • Exception processing: Translating exceptions to human readable format, using internationalization.
  • Clustering, endpoint and business logic services: autodetecting after compile time different service provider so they could be injected by our plug and play engines.

Credits

Icons used are courtesy of Flaticon