Bringing military-grade cybersecurity solutions to the enterprise and critical systems.
Telephone: 310.356.7869

Annotations, inheritance, EJBs and JBoss Seam

Category: Java

9:00 AM, Fri, Jan 25 2008

Annotations are one of the most important new language features introduced with Java 1.5. Annotations allow meta-information to be connected to classes and members of classes. What kind of meta-information is limited only by the developer's imagination. Seam, EJB and Hibernate make heavy use of annotations. Common examples are @Stateful, to indicate that a class is a stateful session bean:

@Stateful public class UserManagerAction {

Annotations are also placed on methods, like @Begin to begin a long-running conversation:

@Begin(join=true) public void selectUser() {
Java annotation
Adelita

When the application server starts, the Seam framework searches through JAR files in the application. If Seam finds a EJB JAR file that contains a file called seam.properties in the root of the JAR, Seam will decide that that JAR file may contain Seam components. At that point, Seam will scan every class file in the JAR looking for classes annotated with the @Name("") annotation. Meanwhile the application server will scan looking for classes with EJB annotations, such as @Stateful and @Local. Obviously, annotations are important. Before annotations, all this configuration had to be done in separate XML files, a task which was tedious and error-prone.

The essence of object-oriented design is the class. All objects are members of a class. One of the early features of classes is the idea of inheritance. One class may extend another class, allowing the sub-class to modify or add to the features of the super-class. Extending classes is a fundamental part of object-oriented programming.

Now comes the question. How do annotations and inheritance interact? If a super-class has a certain annotation, does the sub-class also have it? The answer is complicated. In this blog entry, we'll go over all the scenarios. First, annotations can occur on classes, or they can occur on class members (methods and variables). An annotation on a class is called a type annotation.

Type annoations are defined like this:

@Target(ElementType.TYPE)
public @interface Adelita { }

That annotation can be put onto a class declaration, like this:

@Adelita public class MyClass {

That's all that is necessary to do a simple annotation. Note that the annotation definition should have @Retention(RetentionPolicy.RUNTIME) added, so that classes using the annotated class can find the annotation. Without the retention policy, the annotation would be visible only during compilation, which is not what we need for this type of use. So the full annotation class looks like this:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Adelita { }

Now what happens if we put the @Adelita annotation on a super-class, and then sub-class it, like this:

@Adelita public class AbstractClass {
}

public class MyClass extends AbstractClass {
}

In this case, MyClass does not have the @Adelita annotation on it. If we want sub-classes to have that annotation, we must add one more annotation to the Adelita definition:

@Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
@Inherited
    public @interface Adelita { }

With the @Inherited definition added, sub-classes of a super-class which has the Adelita type annotation will also have the Adelita type annotation.

Next, let us consider annotations on class members (variables and methods). We change the definition of the Adelita annotation from a type annotation to a method annotation, so it now looks like this:

@Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
@Inherited
    public @interface Adelita { }

We create a super-class file called SuperAnnotated with two annotated methods, public void foo() and public void bar():

public class SuperAnnotated {

    @Adelita
    public void foo() {
    }

    @Adelita
	public void bar() {
    }

}

And we create a sub-class called Annotated which overrides public void bar() but not public void foo():

public class Annotated extends SuperAnnotated {
    public void bar() {
    }
}

We also create a test class called AnnotationReader to use reflection to check for the presence or absence of annotations.

When we run the test class, we find that when the method is overridden, the annotation disappears:

 java AnnotationReader
Testing annotations where the method IS NOT overridden by the sub-class
Method 'foo' of Annotated has the Adelita annotation
Testing annotations where the method IS overridden by the sub-class
Method 'bar' of Annotated does NOT have the Adelita annotation

Note that the Adelita annotation still has the @Inherited annotation on it. The Adelita annotation is a method annotation, not a type annotation, so the @Inherited is ignored. Try compiling it with and without the @Inherited annotation. The results will be the same. In fact, the compiler should give a warning when @Inherited is present on any annotation other than a Type annotation, because @Inherited is ignored.

Download the source for this entry and try it out, and try other variations.

How this affects EJBs and Seam: Let's say you want to create a super-class for some of your session beans. This is a logical thing to do; many session beans need similar code. You might annotate the super-class to specify the scope:

@Scope(ScopeType.CONVERSATION)
public abstract class Home {

(Note that this is the start of the declaration of the org.jboss.seam.framework.Home abstract class.)

If you do that, will sub-classes also have the @Scope annotation? Yes. Take a look at org.jboss.seam.annotations.Scope:

@Target(TYPE)
@Retention(RUNTIME)
@Documented
@Inherited
public @interface Scope {
   ScopeType value();
}

It has the @Inherited annotation, and it is type annotation. If the super-class is annotated with @Scope(ScopeType.CONVERSATION), sub-classes will inherit it.

Now let's say that you want your session beans to have a uniform method for beginning a conversation. You add this method to the super-class:

@Begin public void startConversation() { }

This will work so long as the sub-class does not override the public void startConversation() method, but if the sub-class overrides public void startConversation(), the annotation will disappear!. If the sub-class overrides that method, the sub-class must also have the @Begin annotation on the same method.

This blog entry shows the subtleties of annotations and inheritance, and how important it is to understand what is going on. You could create the sub-class, and have your page call the startConversation action, and wonder why your conversation does not promote to long-running. That could be a tricky problem to figure out. But now you see how annotation and inheritance works, so you can avoid such bugs.