ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Declarative Programming in Java Declarative Programming in Java

by Narayanan Jayaratchagan
04/21/2004

What makes EJB components special is the declarative programming model through which we can specify the services such as security, persistence, transaction etc., that the container should provide. An EJB only implements the business logic; the services are associated through a deployment descriptor, which essentially acts as metadata for the EJB. At runtime, the container uses the metadata specified in the deployment descriptor to provide the services. The deployment descriptor is an XML file, not part of the Java classes that make up the EJBs. Is there a standard way to annotate the Java classes that make up the EJBs so that a developer can look at the class definition, together with annotations, and know everything about that class? It would be even better if the remote, home interfaces and the deployment descriptor could be automatically generated by a tool using the annotations. Better yet, can we provide the same kind of declarative services for a simple Java object? If so, how? This article examines how JSR-175: A Metadata Facility for the Java Programming Language will help us in finding answers to these questions and more.

Approaches to Programming

There are two approaches to programming called imperative programming and declarative programming. Imperative programming gives a list of instructions to execute in a particular order -- Java program that counts the number of words in a text file is an example of the imperative approach. Declarative programming describes a set of conditions, and lets the system figure out how to fulfill them. The SQL statement SELECT COUNT(*) FROM XYZ is an example for the declarative approach. In other words, "specifying how" describes imperative programming and "specifying what is to be done, not how" describes declarative programming.

Annotations

The Tiger release of Java (JDK 1.5) adds a new language construct called annotation (proposed by JSR-175). Annotation is a generic mechanism for associating metadata (declarative information) with program elements such as classes, methods, fields, parameters, local variables, and packages. The compiler can store the metadata in the class files. Later, the VM or other programs can look for the metadata to determine how to interact with the program elements or change their behavior.

Declaring an Annotation

Declaring an annotation is very simple -- it takes the form of an interface declaration with an @ preceding it and optionally marked with meta-annotations, as shown below:

package njunit.annotation;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface UnitTest {
	String value();
}

The Retention meta-annotation declares that the @UnitTest annotation should be stored in the class file and retained by the VM so it may be read reflectively. The Target meta-annotation declares that the @UnitTest annotation can be used to annotate methods in a Java class. @interface declares the @UnitTest annotation with one member called value, which returns a String.

Using an Annotation

Here is an example that shows how to use the @UnitTest annotation declared in the previous section:

import njunit.annotation.*;

public class Example {

    @UnitTest(value="Test 1. This test will pass.")
    public void pass() {
        assert 10 > 5;
    }

    @UnitTest("Test 2. This test will fail.")
    public void fail() {
        assert 10 < 5;
    }

}

An annotation is applied to the code element by placing an annotation statement (@AnnotationType(...)) before the program element. Annotation values take the form "name=value"; for example, @UnitTest(value="some text"). Single-member annotations with a member named value are treated specially and can use the shorthand @UnitTest("some text"). In the example, the @UnitTest annotation is associated with the pass and fail methods.

Related Reading

Enterprise JavaBeans
By Richard Monson-Haefel, Bill Burke, Sacha Labourey

Accessing Annotations at Runtime

Once annotations have been associated with program elements, we can use reflection to query their existence and get the values. The main reflection methods to query annotations are in a new interface: java.lang.reflect.AnnotatedElement.

Methods available in the AnnotatedElement interface are:

  • boolean isAnnotationPresent(Class<? extends Annotation> annotationType)
    Returns true if an annotation for the specified type is present on this element, else false. This method is designed primarily for convenient access to marker annotations.

  • <T extends Annotation> T getAnnotation(Class<T> annotationType)
    Returns this element's annotation for the specified type if such an annotation is present, else null.

  • Annotation[] getAnnotations()
    Returns all annotations present on this element. (Returns an array of length zero if this element has no annotations.)

  • Annotation[] getDeclaredAnnotations()
    Returns all annotations that are directly present on this element. Unlike the other methods in this interface, this method ignores inherited annotations. (Returns an array of length zero if no annotations are directly present on this element.)

You may notice that the isAnnotationPresent and getAnnotation methods are defined using generics, another new feature available in JDK 1.5.

Here is the list of classes that implement the AnnotatedElement interface:

  1. java.lang.reflect.AccessibleObject
  2. java.lang.Class
  3. java.lang.reflect.Constructor
  4. java.lang.reflect.Field
  5. java.lang.reflect.Method
  6. java.lang.Package

Next, I'll show you an example that illustrates how to access annotations at runtime.

package njunit;

import java.lang.reflect.*;
import njunit.annotation.*;

public class TestRunner {
    static void executeUnitTests(String className) {
        try {
            Object testObject =
                Class.forName(className).newInstance();
            Method [] methods =
                testObject.getClass().getDeclaredMethods();
            for(Method amethod : methods) {
            UnitTest utAnnotation =
                amethod.getAnnotation(UnitTest.class);
            if(utAnnotation!=null) {
                System.out.print(utAnnotation.value() +
                                " : " );
                String result =
                    invoke(amethod, testObject);
                System.out.println(result);
                }
            }
        }catch(Exception x) {
            x.printStackTrace();
        }
    }
    
    static String invoke(Method m, Object o) {
        String result = "passed";
        try{
            m.invoke(o,null);
        } catch(Exception x) {
            result = "failed";
        }
        return result;
    }
    
    public static void main(String [] args) {
        executeUnitTests(args[0]);
    }
}

The TestRunner uses the @UnitTest annotation to determine whether a method is a unit test or not, invoke the method if it is marked with the @UnitTest annotation, and report the success or failure.

Here is how the TestRunner executes the unit test. Given a Java class, the TestRunner first obtains the list of all declared methods using reflection. Then it queries each method using the enhanced for construct and the getAnnotation method available in JDK 1.5 to find out whether it is marked as a @UnitTest. If it is marked, then it invokes the method and reports the success or failure. A test is considered failed if there is any exception when executing the test, and is considered passed otherwise.

In our Example class, the pass method will succeed when invoked, but the fail method will throw an AssertionError, which is propagated to the TestRunner.invoke method as InvocationTargetException.

When run with the command java -ea njunit.TestRunner Example, the output looks like the following:

Test 1. This test will pass. : passed
Test 2. This test will fail. : failed

Pages: 1, 2, 3

Next Pagearrow