Chapter 24

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.

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.

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, let’s open the source code of this class. In the top part of the code you’ll 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 will generate 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 will 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 in Lesson 27 you’ll learn about annotations defined by the creators of Java EE technologies. But in this lesson you’ll get familiar with Java SE annotations, which you will eventually use in your projects.

Java Annotations Basics

The Java annotation model enables you to add custom annotations anywhere in your code, not just in the comments section. 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.

There are about a dozen of 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 check the Annotations section of the latest online documentation on the Java SE API, which at the time of this writing is located at http://download.oracle.com/javase/7/docs/api/overview-summary.html.

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 give you a detailed description of how to use each of these annotations, but I will give you selected examples that will help you get started with annotations.

@Override

In the Try It section of Lesson 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 will compile with no errors. But you could have done this by mistake, and instead of overriding the method as planned, you’ve overloaded it. This won’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 will complain 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.

@SuppressWarning

The compiler can generate warning messages that won’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, while -Xlint:fallthrough instructs the compiler to warn you about if you forget to add the break statement to the switch statement (see Lesson 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 get warned in all other cases about missing break, but not in this particular method. This is where the @SupressWarnings annotation becomes quite handy and Listing 24-1 illustrates it. To see this example at work, turn on the compiler’s option that warns you about the switch case fall-throughs.

download.eps

Listing 24-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 — six thousand dollars in addition to ten thousand dollars. 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 will be 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. You’ll see an example of its use in the next section.

@Documented

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

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 will allow the user to mark class methods with a SQL statement to be executed during the run time. These classes will be loaded dynamically. The goal is to declare your own annotation to be used by other Java classes, and to write the annotation processor that will read these Java classes, identify and parse annotations and the values of their parameters, if any, and do whatever is required accordingly.

Declaring annotations is very similar to declaring interfaces, but don’t forget to add the @ sign at the beginning. I’ll name 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, 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 {
 
}

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 when the compiled code is running.

Annotations may have parameters. Say we want to add a single parameter that will allow us to specify an SQL statement to be processed. Our MyJDBCExecutor has to be declared as follows (omitting the import statement):

@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. Let’s add more parameters to our annotation:

package com.practicaljava.lesson24;
 
import java.lang.annotation.*;
 
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface MyJDBCExecutor {
         String sqlStatement();
         boolean transactionRequired() default false;
         boolean notifyOnUpdates() default false; 
 
}

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

If we don’t specify default values the Java compiler will generate compilation errors if the values for transactionRequired and notifyOnUpdates are missing in classes that use @MyJDBCExecutor. Here’s an example of a class, HRBrowser, that’s annotated only with a select statement — 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, which is returned to the 
      // caller of the method getEmployees()
      ...   
      return myEmployeeList;
  }
}

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

download.eps

Listing 24-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
 }
}

The annotations with the SOURCE retention policy can be processed by the Annotation Processing Tool (APT) located in the bin directory of your Java installation. As a result of such processing, the new source code may be generated for further compilation. (The use of APT is an advanced subject and is out of the scope of this tutorial. Refer to the following web page for more information: http://download.oracle.com/javase/1.5.0/docs/guide/apt/GettingStarted.html.)

I was involved in the development of an open-source code generator called Clear Data Builder (CDB) (available at http://sourceforge.net/projects/cleartoolkit/). 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 Adobe Flex code 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

Strictly speaking the subject of Java reflection doesn’t belong to the lesson on 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 include it here.

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 class Employee, shown in Listing 24-3.

download.eps

Listing 24-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 24-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.

download.eps

Listing 24-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() will let 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);

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, as in the following example. 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 will be 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. They will add the annotation to their classes and compile them, and the processing tool will consume these classes during run time. Reuse the code example from Listing 24-2, but this time imagine that @MyJDBCExecutor becomes the annotation with RUNTIME retention policy and that there is no need to generate additional source code for the compilation time. Suppose this annotation will be used in HRBrowser, and another class will have 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 24-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 24-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.

download.eps

Listing 24-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

Any class can use more than one annotation. In this case 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 Lesson 27, while you’re 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.

Try It

Create a class-level run-time annotation called @DBParams that will enable 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.ai

You can download the code and resources for this Try It from the book’s web page at www.wrox.com. You can find them in the Lesson24 folder in the download.

Step-by-Step

1. Create the Eclipse project Lesson24.

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.

cd.ai

Please select Lesson 24 on the DVD with the print book, or watch online at www.wrox.com/go/fainjava to view the video that accompanies this lesson.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.192.241