users@javaee-spec.java.net

[javaee-spec users] Reflection API defects around annotations and their impact on Java EE 7

From: Craig Ringer <craig_at_postnewspapers.com.au>
Date: Wed, 02 May 2012 21:23:25 +0800

Hi to the Java EE 7 spec leads and the -users list.

I'm writing in about the first of a few issues I'm interested in seeing
discussed and considered for Java EE 7. This first one may be a Java 8
issue as much as a Java EE 7 one, except that Java EE's heavy use of
annotations (especially for CDI) will make this issue a particular pain
point in Java EE. These issues combine to create a potential performance
problem as well as a potential source of bugs and unnecessarily
complicated code.

The two issues I'm concerned about at the moment, both of which require
slow and inefficient workarounds, are that:

- Method. getAnnotations() just calls Method.getDeclaredAnnotations()
rather than looking up the inheritance tree; and
- Class.getMethod(...) only returns methods that are an exact match,
ignoring polymorphism

How does this matter in the real world, and how does it affect Java EE?
Well, presuming there's a real-world effect at all, the effect on Java
EE would be pretty clear given the role of interceptors, CDI, etc in
Java EE and how reliant they are on interceptors. Is there a real-world
problem here, though?

Yes, there is. Consider this simple (if somewhat contrived) example: An
annotation "@Traced", with an enabled=[true|false] flag and a log level
parameter. An interceptor traps every invocation of a method annotated
@Traced or any method of a class annotated @Traced, times it, and logs
the params, instance hashcode, etc. The interceptor wants to allow a
@Traced(enabled=false) annotation on a method to take precedence over
@Traced(enabled=true) on a class at the same level. To do that, the
interceptor will call Method.getAnnotation(Traced.class) on the method
before Class.getAnnotation(Traced.class) on the class, using whichever
is non-null first. Obvious, simple, and logical, right?

Fine, so far as it goes, but now let's introduce inheritance.

@Traced(enabled=true)
public class SomeBase {

   @Traced(enabled=false)
   public void frequentlyCalledSmallMethod() {
   }

   public void someOtherMethod() {
   }

}

public class SomeSubclass extends SomeBase {
   // EMPTY CLASS
}


If we inject `SomeBase' and call its methods, everything works.
someOtherMethod() gets traced, frequentlyCalledSmallMethod() doesn't
because the Traced(enabled=false) is seen.

If we inject SomeSubclass, which logically should behave identically as
it is an empty subclass, we find that suddenly
frequentlyCalledSmallMethod() is getting traced, because
Method.getAnnotation(...) doesn't look up the inheritance tree, even
though Class.getAnnotation() does.

To work around this it's necessary to write one's own recursive search
up the tree using Method.getDeclaredAnnotation(),
Class.getDeclaredAnnotation(), and Class.getSuperclass(). At which point
we run into the second API defect.

We have no way to ask the reflection API "which method did this Method
override?". We can ask "Which method in this class has the exact same
parameters and name", but that won't work where permitted polymorphisms
appear, so we have to hand write a method override resolver too!


Both these issues make code that works on annotations, especially in
interceptors and in annotation processors, way harder than it needs to
be and way slower. A fix to the API introduced in Java SE 5 can't be
retroactively introduced, but new methods that behave more sanely
certainly can be, and in my view need to be. Even if they can't be added
in Java EE 7, thought needs to be given to the details of how
inheritance interacts with (eg) interceptors in Java EE, and more
thought to how future improvements in the reflection API can be used.

See these two posts by Marco of Encodo Systems for a write-up on the two
specific reflection issues covered here:

http://encodo.com/en/blogs.php?entry_id=10
http://encodo.com/en/blogs.php?entry_id=12

Oh, and for the record, I know the @Traced annotation is largely
"solving" a problem already well solved many different ways. I just
needed a simple and fairly self-contained example.

-- 
Craig Ringer
POST Newspapers
276 Onslow Rd, Shenton Park
Ph: 08 9381 3088     Fax: 08 9388 2258
ABN: 50 008 917 717
http://www.postnewspapers.com.au/