How Do Annotations Work in Java?
Grab some coffee and get ready to dive into the world of annotations.
Join the DZone community and get the full member experience.
Join For FreeAnnotations have been a very important part of Java, and it’s been there from the time of J2SE 5.0. All of us have seen annotations like @Override
and @Deprecated
in our application code at some place or another. In this article, I will discuss what exactly annotations are, why they were introduced, how they work, how to write custom annotations (with example code), what could be valid scenarios for annotations, and lastly annotations and ADF. It’s going to be a long post, so grab some coffee and get ready to dive into the world of annotations.
What Are Annotations?
One word to explain annotation is metadata. Metadata is data about data. So annotations are metadata for code. For example, look at the following piece of code.
@Override
public String toString() {
return "This is String Representation of current object.";
}
toString()
method and used @Override
annotation in above code. Even if I don’t put @Override
, code works properly without any issue. So what’s the advantage and what does this annotation stand for? @Override
tells the compiler that this method is an overridden method (metadata about the method), and if any such method does not exist in a parent class, then throw a compiler error (method does not override a method from its super class). Now if I would have made a typography mistake and used the method name as toStrring() {double r}
, and if I wouldn’t have used @Override
, my code would have compiled and executed successfully but the outcome would be different from what I would have accepted. So now, we understand what annotations are but still, it’s good to read formal definitions
Annotation is a special kind of Java construct used to decorate a class, method, field, parameter, variable, constructor, or package. It’s the vehicle chosen by JSR-175 to provide metadata.
Why Were Annotations Introduced?
Prior to annotation (and even after), XML was extensively used for metadata, and somehow, a particular set of Application Developers and Architects thought XML maintenance was getting troublesome. They wanted something that could be coupled closely with code instead of XML, which is very loosely coupled (in some cases, almost separate) from code. If you google “XML vs. annotations”, you will find a lot of interesting debates. An interesting point is that XML configurations were introduced to separate configurations from code. The last two statements might create a bit of doubt in your mind that these two are creating a cycle, but both have their pros and cons. Let’s try to understand with an example.
Suppose, you want to set some application-wide constants/parameters. In this scenario, XML would be a better choice because this is not related to any specific piece of code. If you want to expose some method as a service, an annotation would be a better choice as it needs to be tightly coupled with that method and developer of the method must be aware of this.
Another important factor is that an annotation defines a standard way of defining metadata in code. Prior to annotations, people also used their own ways to define metadata. Some examples are using marker interfaces, comments, transient keywords, etc. Each developer needs his own way to decide metadata, but annotation standardized things.
These days most frameworks use a combination of both XML and Annotations to leverage positive aspects of both.
How Annotations Work and How to Write Custom Annotations
Before I start this explanation, I will suggest you download this sample code for annotations (AnnotationsSample.zip) and keep that open in any IDE of your choice, as it will help you to understand following explanation better.
Writing annotations is very simple. You can compare annotation definition to an interface definition. Let’s have a look at two examples — one is the standard @Override
annotation and the second is a custom annotation @Todo
:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
Something seems fishy about @Override
; it’s not doing anything — it simply checks to see if a method is defined in the parent class. Well, don’t be surprised; I am not kidding you. Override annotation’s definition has that much code only. This is the most important part to understand, and I am reiterating myself: Annotations are only metadata and do not contain any business logic. Tough to digest but true. If annotations do not contain the logic, then someone else must be doing something and that someone is the consumer of this annotation metadata. Annotations only provide information about the attribute (class/method/package/field) on which it is defined. The consumer is a piece of code that reads this information and then performs the necessary logic.
When we are talking about standard annotations like @Override
, the JVM is the consumer and it works at the bytecode level. Now that’s something application developers can’t control and can’t use for custom annotations. So we need to write to consumers for our annotations by ourselves.
Let’s understand the key terms used for writing annotations one by one. In the above examples, you will see annotations are used on annotations.
J2SE 5.0 provides four annotations in the java.lang.annotation package that are used only when writing annotations:
@Documented
– Whether to put the annotation in Javadocs
@Retention
– When the annotation is needed
@Target?
– Places the annotation can go
@Inherited
– Whether subclasses get the annotation.
@Documented
– A simple market annotation that tells whether to add an annotation in the Java doc or not.
@Retention
– Defines how long the annotation should be kept.
RetentionPolicy.SOURCE
– Discard during the compile. These annotations don’t make any sense after the compile has completed, so they aren’t written to the bytecode. Examples: @Override
, @SuppressWarnings
RetentionPolicy.CLASS
– Discard during class load. Useful when doing bytecode-level post-processing. Somewhat surprisingly, this is the default.
RetentionPolicy.RUNTIME
– Do not discard. The annotation should be available for reflection at runtime. This is what we generally use for our custom annotations.
@Target
– Where annotation can be placed. If you don’t specify this, annotation can be placed anywhere. The following are valid values. One important point here is that it’s inclusive only, which means if you want annotation on 7 attributes and just want to exclude only one attribute, you need to include all 7 while defining the target.
ElementType.TYPE (class, interface, enum)
ElementType.FIELD (instance variable)
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE (on another annotation)
ElementType.PACKAGE (remember package-info.java)
@Inherited
– Controls whether the annotation should affect the subclass.
Now, what goes inside an annotation definition? Annotations only support primitives, string, and enumerations. All attributes of annotations are defined as methods, and default values can also be provided
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Todo {
public enum Priority {LOW, MEDIUM, HIGH}
public enum Status {STARTED, NOT_STARTED}
String author() default "Yash";
Priority priority() default Priority.LOW;
Status status() default Status.NOT_STARTED;
}
The following is an example of how the above annotation can be used:
@Todo(priority = Todo.Priority.MEDIUM, author = "Yashwant", status = Todo.Status.STARTED)
public void incompleteMethod1() {
//Some business logic is written
//But it’s not complete yet
}
If we have only one attribute inside an annotation, it should be named “value” and can be used without attribute name while using it.
@interface Author{
String value();
}
@Author("Yashwant")
public void someMethod() {
}
So far so good. We have defined our custom annotation and applied it to some business logic methods. Now, it’s time to write a consumer. For that, we will need to use Reflection. If you are familiar with Reflection code, you know reflection provides Class, Method and Field objects. All of these have a getAnnotation()
method, which returns the annotation object. We need to cast this object as our custom annotation (after checking with instanceOf()
) and then, we can call methods defined in our custom annotation. Let’s look at the sample code, which uses the above annotation:
Class businessLogicClass = BusinessLogic.class;
for(Method method : businessLogicClass.getMethods()) {
Todo todoAnnotation = (Todo)method.getAnnotation(Todo.class);
if(todoAnnotation != null) {
System.out.println(" Method Name : " + method.getName());
System.out.println(" Author : " + todoAnnotation.author());
System.out.println(" Priority : " + todoAnnotation.priority());
System.out.println(" Status : " + todoAnnotation.status());
}
}
Use Cases for Annotations
Annotations are very powerful, and frameworks like Spring and Hibernate use annotations very extensively for logging and validations. Annotations can be used in places where marker interfaces are used. Marker interfaces are for the complete class but you can define annotations that could be used on individual methods, for example, whether a certain method is exposed as service method or not.
In the servlet specification 3.0, a lot of annotations were introduced, especially related to servlet security. Let's check out a few:
HandlesTypes
– This annotation is used to declare an array of application classes which are passed to a ServletContainerInitializer
.
HttpConstraint
– This annotation represents the security constraints that are applied to all requests with HTTP protocol method types that are not otherwise represented by a corresponding HttpMethodConstraint
in a ServletSecurity
annotation.
HttpMethodConstraint
– Specific security constraints can be applied to different types of request, differentiated by the HTTP protocol method type by using this annotation inside the ServletSecurity
annotation.
MultipartConfig
– This annotation is used to indicate that the Servlet on which it is declared expects requests to made using the multipart/form-data MIME type.
ServletSecurity
– Declare this annotation on a Servlet implementation class to enforce security constraints on HTTP protocol requests.
WebFilter
– The annotation used to declare a Servlet Filter.
WebInitParam
– The annotation used to declare an initialization parameter on a Servlet or Filter, within a WebFilter
or WebServlet
annotation.
WebListener
— The annotation used to declare a listener for various types of event, in a given web application context.
WebServlet
– This annotation is used to declare the configuration of a Servlet.
ADF (Application Development Framework) and Annotations
Now, we are in the last part of our discussion: the Application Development Framework, also known as ADF. The ADF is developed by Oracle and used to build Oracle Fusion Applications. We have seen pros and cons, and we know how to write custom annotations, but where can we use custom annotations in ADF? Does ADF provide any native annotations?
These are certainly interesting questions: But are there certain limitations that prevent the usage of annotations on a large scale in ADF? Frameworks, which were mentioned earlier, like Spring and Hibernate, use AOP (aspect-oriented programming). In AOP, the framework provides a mechanism to inject code for preProcessing and postProcessing for any event. For example, you have a hook to place code before and after a method execution, so you can write your consumer code in those places. ADF does not use AOP. If we have any valid use case for annotations, we might need to go via the inheritance way.
Hope you enjoyed this article. Please drop your thoughts in the comments!
Originally published August 2015
Further Reading
Published at DZone with permission of Yashwant Golecha, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments