Annotations and Reflection - Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Lesson 23. Annotations and Reflection

In general, metadata is data about your data. In the context of DBMSes, metadata can be information describing the way you store data, such as the table and field names or primary keys. Program code metadata is data about your code. Any Java class has its metadata embedded, and you can write a program that “asks” another class, “What methods do you have?” or similar questions about class fields, constructors, and ancestors.

You can use annotations to include metadata about your code. There are a number of predefined annotations (for example, @Override and @SuppressWarning). The Java annotation model enables you to add custom metadata anywhere in your code. You can apply custom annotations to a class, a method, or a variable—just specify allowed targets when the annotation is being defined. Java annotations start with the @ sign and may optionally have one or more parameters. Some of the annotations are built into Java SE and are used by the javac compiler, but most of them are consumed by some kind of processing program or tool.

The subject of Java reflection doesn’t require the use of annotations; reflection is used widely in various areas of Java development. But because this subject has to be covered before you can proceed with annotation processing, I decided to cover both topics in the same lesson.

Javadoc Annotations

If you’ve ever looked at the source code of any Java class, you can easily identify Javadoc-style comments for classes, interfaces, variables, and methods. These comments may include specially formatted words called tags. These tags are special annotations. They also start with the @ sign and help the Javadoc tool to generate the online documentation with the standardized look and feel.

In Eclipse you can select any Java class and press F3 to open the source code of this class. Because the previous lesson was about working with JTable, open the source code of this class. In the top part of the code you find a description similar to the one that follows (I removed a large portion of the text for brevity):

/**

* The <code>JTable</code> is used to display and edit regular

* two-dimensional tables of cells.

* To enable sorting and filtering of rows, use a

* {@code RowSorter}.

* * As for all <code>JComponent</code> classes, you can use

* {@link InputMap} and {@link ActionMap} to associate an

* {@link Action} object with a {@link KeyStroke} and execute the

* action under specified conditions.

* <p>

* <strong>Warning:</strong> Swing is not thread safe. For more

* information see <a

* href="package-summary.html#threading">Swing's Threading

* Policy</a>.

* <p>

*

* @version 1.292 05/30/08

* @author Philip Milne

* @author Shannon Hickey (printing support)

* @see javax.swing.table.DefaultTableModel

* @see javax.swing.table.TableRowSorter

*/

The special words marked with an @ sign are Javadoc metadata describing links, version, author, and related classes to see in Java documentation. If you run the source code of this class through the Javadoc utility, the utility generates HTML output that can be opened by any web browser. It’s a good practice to include Javadoc comments in your classes.

Javadoc acts as a processing tool that extracts from the source code all comment blocks that start with /** and end with */. It then formats this text using HTML tags and embedded annotations to generate program documentation. The preceding text is an example of the use of specific tags that are predefined and understood by Javadoc. This was an example of metadata that are understood by just one utility: Javadoc.

But Java allows you to declare your own custom annotations and define your own processing rules that route the execution of your program and produce configuration files, additional code, deployment descriptors, and more. No matter what your goals, when you create annotations you also need to create or use an annotation processor to get the expected output.

Starting from Lesson 26 you learn about annotations defined by the creators of Java EE technologies. In this lesson you become familiar with Java SE annotations, which you will eventually use in your projects.

Java Annotations Basics

There are about a dozen predefined annotations already included with Java SE. You can find them, along with their supporting classes, in the packages java.lang, java.lang.annotation, and javax.annotation. You can get familiar with the content of these packages in the latest online documentation on the Java SE API, which at the time of this writing is located at http://goo.gl/sVZ8bI.

Some of these annotations are used by the compiler (@Override, @SuppressWarning, @Deprecated, @Target, @Retention, @Documented, and @Inherited); some are used by the Java SE run time or third-party run times and indicate methods that have to be invoked in a certain order (@PostConstruct, @PreDestroy), or mark code that was generated by third-party tools (@Generated). I’m not going to repeat a detailed description of how to use each of these annotations, but I am going to give you selected examples to help you get started with annotations.

@Override

In the “Try It” section of Chapter 3 you overrode the method public double calcTax() in the class NJTax. The method signature of calcTax() was the same in both NJTax and its superclass Tax. Now deliberately add an argument to calcTax() in NJTax, as if you had done so by accident. The code compiles with no errors. But you could have done this by mistake, and instead of overriding the method as planned, you’ve overloaded it. This doesn’t happen if you use the annotation @Override whenever you are planning to override a method:

@Override public double calcTax(String something)

Now the compiler complains with the following error:

The method calcTax(String) of type NJTax must override or implement a supertype method

The annotation @Override signals the compiler that overriding is expected, and that it has to fail if an override does not occur. This annotation indicates your intentions and helps other people who may be reading your code in the future.

@SuppressWarning

The compiler can generate warning messages that don’t stop your program from running but that do indicate potential problems. In some cases, though, you want to suppress some or all warnings so the output of the project build looks clean. For example,–Xlint:none disables all warnings, whereas -Xlint:fallthrough instructs the compiler to warn you if you forget to add the break statement to the switch statement (see Chapter 5). In Eclipse IDE, to set the compiler’s options you right-click the project and select Properties → Java Compiler → Errors/Warnings → Potential Programming Problems.

But what if you want to omit the break keyword in the switch statement on purpose? You still want to be warned in all other cases about a missing break, but not in this particular method. This is where the @SupressWarnings annotation becomes quite handy, and Listing 23-1 illustrates it. To see this example at work, turn on the compiler’s option that warns you about the switch case fall-throughs.

Listing 23-1: Custom rendering of the Price value

package com.practicaljava.lesson24;

public class SuppressWarningDemo {

@SuppressWarnings("fallthrough")

public static void main(String[] args) {

int salaryBand=3;

int bonus;

// Retrieve the salary band of the person from some

// data source here

switch(salaryBand){

case 1:

bonus=1000;

System.out.println("Giving bonus " + bonus);

break;

case 2:

bonus=2000;

System.out.println("Giving bonus " + bonus);

break;

case 3:

bonus=6000;

System.out.println("Giving bonus " + bonus);

case 4:

bonus=10000;

System.out.println("Giving bonus " + bonus);

break;

default:

// wrong salary band

System.out.println("Invalid salary band");

}

}

}

Note that the break keyword is missing in the case 3 section. In this code it’s done on purpose: All employees in salaryBand 3 are entitled to two bonuses—$6,000 in addition to $10,000. The compiler’s annotation @SuppressWarnings(“fallthrough”) suppresses compiler warnings only for this method. In all other classes or methods that may have switch statements, the warning is generated.

@Deprecated

If you are developing classes that are going to be used by someone else, mark as @Deprecated any classes or methods that may be removed in future versions of your code. Other developers will still be able to use this code, but they’ll be warned that switching to newer versions is highly recommended.

@Inherited

This annotation simply means that the annotation has to be inherited by descendants of the class in which it is used. The next section includes an example of its use.

@FunctionalInterface

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface. If you’ll ever be defining interfaces with a single abstract method, you may optionally mark such an interface with @FunctionalInterface just to hint that this interface can be implemented as a lambda expression.

@Documented

If you want an annotation to be included in the Javadoc utility, mark it as @Documented. The next section includes an example of this annotation.

Custom Annotations

Creating your own annotations is more interesting than using core Java or third-party annotations. First of all, you have to decide what you need an annotation for and what properties it should have. Then you need to specify the allowed targets for this annotation (for example, class, method, or variable). Finally, you have to define your retention policy: how long and where this annotation will live. Let’s go through an example to illustrate all these steps.

Suppose you need to create an annotation that allows the user to mark class methods with a SQL statement to be executed during the run time. These classes are loaded dynamically. The goal is to declare your own annotation to be used by other Java classes and to write the annotation processor that reads these Java classes, identifies and parses annotations and the values of their parameters, if any, and does whatever is required accordingly. Usually creators of object-relational mapping (ORM) frameworks of code generators need to implement such tasks.

Declaring annotations is very similar to declaring interfaces, but don’t forget to add the @ sign at the beginning. I’m naming my annotation MyJDBCExecutor:

public @interface MyJDBCExecutor{

}

If metadata is data about data, then meta-annotations are annotations about annotations. This is not as confusing as it sounds. To specify where you can use this newborn annotation, define the meta-annotation @Target. The enumeration ElementType defines possible target values: METHOD,TYPE, CONSTRUCTOR, FIELD, PARAMETER, PACKAGE, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE, and ANNOTATION_TYPE. If you don’t use @Target, the annotation can be used anywhere. For example, this is how you can allow use of the annotation only with methods and constructors:

import java.lang.annotation.*;

@Inherited

@Documented

@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR })

@Retention(RetentionPolicy.SOURCE)

public @interface MyJDBCExecutor {

}

Starting from Java 8 you can create custom annotations that can be used anywhere where you can declare a type, for example:

@MyAnnotation String employeePhone;

The retention policy in the preceding code snippet is set to SOURCE, which means that this annotation will be used for processing only during the compilation of your program. The other two allowed values for retention policy are RUNTIME and CLASS.

Annotations with the CLASS retention policy stay in the compiled class, but are not loaded during run time. The CLASS retention policy is used by default if a retention policy is not explicitly specified.

Annotations with the RUNTIME retention policy have to be processed by a custom processing tool (someone has to write it) when the compiled code is running.

Annotations may have parameters. Say you want to add a single parameter that will allow you to specify an SQL statement to be processed. Your annotation MyJDBCExecutor has to be declared as follows:

@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR })

@Retention(RetentionPolicy.SOURCE)

public @interface MyJDBCExecutor {

String value();

}

A sample Java class, HRBrowser, may use this annotation like this:

class HRBrowser{

@MyJDBCExecutor (value="Select * from Employee")

public List getEmployees(){

// add calls to some JDBC executing engine here

}

}

If the annotation has only one parameter named value, the “value=” part in the preceding code snippet is not required. But I’d like this annotation to have three parameters: SQL to execute, transactional support, and a notification flag to inform other users of the application about any database modifications. Add more parameters to your annotation:

@Target({ ElementType.METHOD})

@Retention(RetentionPolicy.SOURCE)

public @interface MyJDBCExecutor {

String sqlStatement();

boolean transactionRequired() default false;

boolean notifyOnUpdates() default false;

}

You’ve replaced the parameter value with a more meaningful sqlStatement, and added two more: transactionRequired and notifyOnUpdates. The latter has two default values. If a Java class doesn’t need to support transactions and notify other applications about updates, why force software developers to provide values for these parameters?

If you don’t specify default values then the Java compiler generates compilation errors if the values for transactionRequired and notifyOnUpdates are missing in classes that use @MyJDBCExecutor. The following code is an example of a class, HRBrowser, with the method getEmployees()that’s annotated with @MyJDBCExecutor having only a sqlStatement parameter; no other actions are needed here:

class HRBrowser{

@MyJDBCExecutor (sqlStatement="Select * from Employee")

public List<Employee> getEmployees(){

// The code to get the the data from DBMS goes here,

// result set goes in ArrayList myEmployeeList,

// which is returned to the caller of getEmployees()

// ...

return myEmployeeList;

}

}

The code sample in Listing 23-2 adds the method updateData() and uses all three annotation parameters.

Listing 23-2: Using the annotation MyJDBCExecutor

class HRBrowser{

@MyJDBCExecutor (sqlStatement="Select * from Employee")

public List<Employee> getEmployees(){

// Generate the code to get the the data from DBMS,

// place them in ArrayList and return them to the

// caller of my getEmployees

...

return myEmployeeList;

}

@MyJDBCExecutor (sqlStatement="Update Employee set bonus=1000",

transactionRequired=true,

notifyOnUpdates=true)

public void updateData(){

// JDBC code to perform transactional updates and

// notifications goes here

}

}

Annotations and Code Generation

I was involved in the development of an open-source code generator called Clear Data Builder (CDB) . The CDB allows the user to write a simple Java class that has an abstract method annotated with an SQL statement and several other parameters, and within seconds to generate complete code for the functional application that has JavaScript on the client side talking to Java at the server, which is accessing data stored in any relational DBMS via JDBC. In this project we used only the annotations with the SOURCE retention policy, and, before compiling, classes would generate additional code according to specified annotations.

If CDB would be processing the annotation @MyJDBCExecutor, it would engage additional tools and generate and compile all JDBC code for the methods getEmployees() and updateData() automatically.

For the annotations with the RUNTIME retention policy you should know how to write an annotation processor, however, as it has to “extract” the values from the annotations during run time, and, based on those values, engage the appropriate code. But there is one Java feature, reflection, that you must understand before you can write your own annotation-processing class.

Reflection

Reflection enables you to find out about the internals of a Java class (its methods, constructors, and fields) during the run time, and to invoke the discovered methods or access public member variables. A special class called Class can load the class in memory, and then you can explore the content of the class by using classes from the package java.lang.reflect. Consider the classes Person and Employee in the following code.

Listing 23-3: Class Employee extends Person

abstract public class Person {

abstract public void raiseSalary();

}

public class Employee extends Person{

public void raiseSalary() {

System.out.println("Raising salary for Employee...");

}

}

The ReflectionSample class in Listing 23-4 loads the class Employee, prints its method signatures, and finds its superclass and methods. The process of querying an object about its content during run time is called introspection.

Listing 23-4: Introspecting Employee

import java.lang.reflect.*;

public class ReflectionSample {

public static void main(String args[]) {

try {

Class c = Class.forName("Employee");

Method methods[] = c.getDeclaredMethods();

System.out.println("The Employee methods:");

for (int i = 0; i < methods.length; i++){

System.out.println("*** Method Signature:" +

methods[i].toString());

}

Class superClass = c.getSuperclass();

System.out.println("The name of the superclass is "

+ superClass.getName());

Method superMethods[] = superClass.getDeclaredMethods();

System.out.println("The superclass has:");

for (int i = 0; i < superMethods.length; i++){

System.out.println("*** Method Signature:" +

superMethods[i].toString());

System.out.println(" Return type: " +

superMethods[i].getReturnType().getName());

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

Here’s the output of the program ReflectionSample:

The Employee methods:

*** Method Signature:public void Employee.raiseSalary()

The name of the superclass is Person

The superclass has:

*** Method Signature:public abstract void Person.raiseSalary()

Return type: void

Some other useful methods of the class Class are getInterfaces(), getConstructors(), getFields(), and isAnnotationPresent(). The following code snippet shows how to get the names, types, and values of the public member variables of the loaded class:

Class c = Class.forName("Employee");

Field[] fields = c.getFields();

for (int i = 0; i < fields.length; i++) {

String name = fields[i].getName();

String type = fields[i].getType().getName();

System.out.println("Creating an instance of Employee");

Object obj = c.newInstance();

Object value = fields[i].get(obj);

System.out.println("Field Name: " + name + ", Type: "

+ type + " Value: " + value.toString());

}

The process of reflection uses introspection to find out during run time what the methods (or properties) are, but it also can call these methods (or modify these properties). The method invoke() lets you call methods that were discovered during run time:

Class c= Class.forName("Employee");

Method raiseSalary = c.getMethod( "raiseSalary", null);

raiseSalary.invoke(c.newInstance(),null);

Note that the method forName() loads the class and the newInstance() creates an instance of Employee. The first argument of the method invoke() represents an instance of the object Employee, and null means that this method doesn’t have arguments. With reflection, the arguments are supplied as an array of objects. You can find out what the method arguments are by calling the method Method.getParameterTypes(), or create and populate them on your own. Add the following method to the class Employee:

public void changeAddress(String newAddress) {

System.out.println("The new address is "+ newAddress);

}

Note the public qualifier: It’s needed for proper introspection. Otherwise the NoSuchMethodException is thrown by the following code snippet. The ReflectionSample class can invoke changeAddress() as follows:

Class c= Class.forName("Employee");

Class parameterTypes[]= new Class[] {String.class};

Method myMethod = c.getMethod( "changeAddress", parameterTypes);

Object arguments[] = new Object[1];

arguments[0] = "250 Broadway";

myMethod.invoke(c.newInstance(),arguments);

Reflection helps in building dynamic component-based applications that can load different classes based on certain business logic and invoke this logic during run time. Many third-party Java frameworks read configuration files and then instantiate and use required objects.

Run-Time Annotation Processing

The author of a custom run-time annotation usually gives it to other developers along with the processing tool. Developers add the annotation to their classes and compile them, and the processing tool consumes these classes during run time. To illustrate the concept I reuse the code example from Listing 23-2, but this time imagine that @MyJDBCExecutor becomes the annotation with the RUNTIME retention policy and that there is no need to generate additional source code for the compilation time. Suppose this annotation is being used in HRBrowser, and another class has to analyze the annotation parameters and route the execution accordingly.

Now I’ll write the annotation processor class called MyJDBCAnnotationProcessor, and the class HRBrowser in Listing 23-2 can serve as a command-line argument to that processor:

c:/>java MyJDBCAnnotationProcessor HRBrowser

The class MyJDBCAnnotationProcessor has to load the class HRBrowser, introspect its content, find the annotations and their values, and process them accordingly. I’ll show you how to write such a processor, or rather its annotation-discovery part.

Listing 23-5 shows MyJDBCAnnotationProcessor, which starts by loading another class, whose name was supplied in the command line. After that it introspects the loaded class and places all references to its method definitions into an array called methods. Finally, it loops through this array, and for each method that has annotations it finds and prints the values of the parameters sqlStatement, notifyOnUpdates, and transactionRequired.

Listing 23-5: MyJDBCAnnotationProcessor

import java.lang.reflect.*;

import com.practicaljava.lesson24.MyJDBCExecutor;

public class MyJDBCAnnotationProcessor {

public static void main(String[] args) {

// TODO add a check for the number of command line arguments

// has to be the name of the class to load.

String classWithAnnotation = args[0];

try {

//Load provided on the command line class

Class loadedClass = Class.forName(classWithAnnotation);

// Get references to class methods

Method[] methods = loadedClass.getMethods();

//Check every method of the class.If the annotation is present,

//print the values of its parameters

for (Method m: methods){

if (m.isAnnotationPresent(MyJDBCExecutor.class)){

MyJDBCExecutor jdbcAnnotation =

m.getAnnotation(MyJDBCExecutor.class);

System.out.println("Method: " + m.getName() +

". Parameters of MyJDBCExecutor are: " +

"sqlStatement="+ jdbcAnnotation.sqlStatement() +

", notifyOnUpdates="+ jdbcAnnotation.notifyOnUpdates() +

", transactionRequired="+

jdbcAnnotation.transactionRequired());

}

}

}catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

After running this processor with the class HRBrowser, the former correctly identifies the annotated methods and prints the values of their parameters:

Method: getEmployees. Parameters of MyJDBCExecutor are: sqlStatement=Select * from

Employee, notifyOnUpdates=false, transactionRequired=false

Method: updateData. Parameters of MyJDBCExecutor are: sqlStatement=Update Employee set

bonus=1000, notifyOnUpdates=true, transactionRequired=true

If a class may have several annotations, the annotation processor would need to start by getting all annotations of the loaded class using loadedClass.getAnnotations(). It would then process these annotations in a loop.

Summary

In real-world applications you wouldn’t simply be printing the values of the annotation parameters, but rather would be executing different branches of your code based on these values. This is the point of run-time annotation processing. You may ask, “OK, now I know the annotations and their values, so what do I do with them?” The big idea is that you’ve written a generic processor that can work with any classes that include your annotations. It’s a pretty powerful mechanism for all software developers who are creating tools for other people to use.

You’ll probably be using annotations and run-time processors written by other people rather than ones you write yourself. You’ll see lots of examples of using annotations starting from Chapter 26, while learning about Java EE development. But now that you know what’s going on under the hood in annotation processors, learning about Java EE annotation will be a lot easier.

The reflection mechanism allows you to find out the members of any class during the run time. This nice feature should be used sparingly because such discovery would require additional processing time.

Try It

Create a class-level run-time annotation called @DBParams that enables you to specify the name of the database, the user ID, and the password. Write a processor for this annotation.

Lesson Requirements

You should have Java installed.

NOTE You can download the code and resources for this “Try It” from the book’s web page at www.wrox.com/go/javaprog24hr2e. You can find them in the Lesson23.zip.

Step-by-Step

1. Create a new Eclipse project.

2. Declare there the annotation DBParams with the retention policy RUNTIME targeted to TYPE.

3. Define three parameters in this annotation: dbName, uid, and password.

4. Create the class MyDBWorker and annotate it with @DBParms populated with some initial values.

5. Write an annotation processor class called DBParamProcessor to find and print the annotation values in the class MyDBWorker.

6. Run and test DBParamProcessor.

TIP Please select the videos for Lesson 23 online at www.wrox.com/go/javaprog24hr2e. You will also be able to download the code and resources for this lesson from the website.