Annotations were introduced in Java 5, and we all got excited. Such a great instrument to make code shorter! No more Hibernate/Spring XML configuration files! Just annotations, right there in the code where we need them. No more marker interfaces, just a runtime-retained reflection-discoverable annotation! I was excited too. Moreover, I've made a few open source libraries which use annotations heavily. Take jcabi-aspects, for example. However, I'm not excited any more. Moreover, I believe that annotations are a big mistake in Java design.
Long story short, there is one big problem with annotations—they encourage us to implement object functionality outside of an object, which is against the very principle of encapsulation. The object is not solid any more, since its behavior is not defined entirely by its own methods—some of its functionality stays elsewhere. Why is it bad? Let's see in a few examples.
Say we annotate a property with
Then we have an injector that knows what to inject:
Now we're making an instance of class
Books via the container:
Books has no idea how and who will inject an instance of class
DB into it. This will happen behind the scenes and outside of its control. The injection will do it. It may look convenient, but this attitude causes a lot of damage to the entire code base. The control is lost (not inverted, but lost!). The object is not in charge any more. It can't be responsible for what's happening to it.
Instead, here is how this should be done:
This article explains why Dependency Injection containers are a wrong idea in the first place: Dependency Injection Containers are Code Polluters. Annotations basically provoke us to make the containers and use them. We move functionality outside of our objects and put it into containers, or somewhere else. That's because we don't want to duplicate the same code over and over again, right? That's correct, duplication is bad, but tearing an object apart is even worse. Way worse. The same is true about ORM (JPA/Hibernate), where annotations are being actively used. Check this post, it explains what is wrong about ORM: ORM Is an Offensive Anti-Pattern. Annotations by themselves are not the key motivator, but they help us and encourage us by tearing objects apart and keeping parts in different places. They are containers, sessions, managers, controllers, etc.
Then, you create a marshaller and ask it to convert an instance of class
Book into XML:
Who is creating the XML? Not the
book. Someone else, outside of the class
Book. This is very wrong. Instead, this is how this should have been done. First, the class that has no idea about XML:
Then, the decorator that prints it to the XML:
Now, in order to print the book in XML we do the following:
The XML printing functionality is inside
XmlBook. If you don't like the decorator idea, you can move the
toXML() method to the
DefaultBook class. It's not important. What is important is that the functionality always stays where it belongs—inside the object. Only the object knows how to print itself to the XML. Nobody else!
Here is an example (from my own library):
After compilation, we run a so called AOP weaver that technically turns our code into something like this:
I simplified the actual algorithm of retrying a method call on failure, but I'm sure you get the idea. AspectJ, the AOP engine, uses
@RetryOnFailure annotation as a signal, informing us that the class has to be wrapped into another one. This is happening behind the scenes. We don't see that supplementary class, which implements the retrying algorithm. But the bytecode produced by the AspectJ weaver contains a modified version of class
That is exactly what is wrong with this approach—we don't see and don't control the instantiation of that supplementary object. Object composition, which is the most important process in object design, is hidden somewhere behind the scenes. You may say that we don't need to see it since it's supplementary. I disagree. We must see how our objects are composed. We may not care about how they work, but we must see the entire composition process.
A much better design would look like this (instead of annotations):
And then, the implementation of
And now, the implementation of
Is the code longer? Yes. Is it cleaner? A lot more. I regret that I didn't understand it two years ago, when I started to work with jcabi-aspects.
The bottom line is that annotations are bad. Don't use them. What should be used instead? Object composition.
What could be worse than annotations? Configurations. For example, XML configurations. Spring XML configuration mechanisms is a perfect example of terrible design. I've said it many times before. Let me repeat it again—Spring Framework is one of the worst software products in the Java world. If you can stay away from it, you will do yourself a big favor.
There should not be any "configurations" in OOP. We can't configure our objects if they are real objects. We can only instantiate them. And the best method of instantiation is operator
new. This operator is the key instrument for an OOP developer. Taking it away from us and giving us "configuration mechanisms" is an unforgivable crime.