CSDN博客

img jgo

To Annotate or Not?

发表于2004/10/22 9:13:00  924人阅读

分类: Java基础

Developer: J2EE

To Annotate or Not?
by Mike Keith

A close look at annotations and specific use cases of employing annotations and XML as a metadata language

Java 2 Standard Edition (J2SE) 5.0 is now a reality, annotations have become popular, and the Java metadata feature it introduced is being touted with much fanfare as being somewhere between a new and better way to develop applications and a full-scale programming revolution. Whatever it is, it certainly deserves exploration as well as some tire kicking to see exactly how it stacks up against the current metadata technology (XML) we all know and love, or love to hate.

This article makes some comparisons between the two paradigms of storing and reading metadata and considers the advantages and disadvantages of annotations. Code examples help explain some of the issues concretely.

The article assumes that readers are familiar with annotations and program metadata. Those who are not should go to the Java Specification Request (JSR) page and read about JSR-175. It turns out that for getting a better grasp of annotations, the public review draft of the specification is much more useful than the final release. The finalized version has integrated the annotation feature into the language, by modifying all of the relevant VM and language specification sections to accommodate it. The result is an aggregate of annotation morsels in a host of different document contexts that is rather difficult to consume sequentially.

The Real Estate Principle

The oft quoted idiom that the three things that matter most when buying real estate are "location, location and location" is apropos. The primary difference between annotations and XML is where the metadata is located. The majority of their benefits and drawbacks result from metadata placement, and the consequences reach farther than is at first obvious.

XML metadata is traditionally stored in flat files. Although it is possible to exploit file structure, such as directory hierarchies, to offer more context, this is seldom done. More often the XML is randomly lumped together as a wad of metadata with the subject of description explicitly provided in each case.

Consider a middleware layer that allows objects to be remoted (without implementation of any special "Remote" interface) and also allows certain methods to be selectively marked as remote. Furthermore, it allows specification of method parameters as being passed by reference when a call is local. To specify this in XML, the following XML fragment might apply:

<remote>
    <package name="com.acme"> 
        <class name="InventoryControl">
            <method name="addNewItem">
                <method-params>
                    <method-param type="Integer"/>
                    <method-param type="String">
                        < pass-by-reference/>
                    </method-param>
                    <method-param type="Integer"/>
                </method-params>
            </method>
        </class>
    </package>
</remote>

This is quite a verbose example, and most people would agree that it might not be the most optimal XML structure for the purpose of specifying a single method. You can instead try to do things such as fully qualifying the class names or making more use of attributes:

        <remote>
            <method name="addNewItem" 
                    class="com.acme.InventoryControl">
                <method-params>
                    <method-param type="Integer"/>
                    <method-param type="String" 
                                  passByReference=true/>
                    <method-param type="Integer"/>
                </method-params>
            </method>
        </remote>

This example will then come under fire in the more realistic situation in which more than a single piece of metadata is required for each class. In such a case, the class attribute would have to be repeated in each XML element, and repeating elements is a symptom of a poor XML design. The problem again relates to the need for the context—that is, the package, class, method, and parameters—to be present to uniquely identify the method that is to be invoked. Although the different methods of specifying XML are not within the scope of this discussion, it should suffice to say that regardless of the structure adopted to contextually distinguish the metadata, the extra context information is required.

Annotations, on the other hand, are attached to the program artifacts they describe. This both gives relevance to the metadata when viewed and provides the multiple layers of context that must be explicitly conveyed in XML. The analogous annotation for the above example might simply be

public class InventoryControl {
    @Remote
    public void addNewItem(Integer itemId, 
   @PassByRef String description, 
   Integer count) { ... }
}

But is this really analogous? Consider when the middleware layer must determine what the startup method is. How is it going to determine that the method in this class is remotable? Is it reasonable for the processor to have to forage through every method of every application class just to find the one method that contains the annotation. It seems as though the very fact that the Java context is being exploited causes the difficulty in finding the metadata, because there is no explicit pointer to lead the processor to it. Well, just as the XML case looked a little unrealistic for only a single piece of metadata, this example is also a little underspecified. The normal scenario is that metadata is required for multiple artifacts and at multiple levels, so just as the XML context likely needs to specify a class element so that all of its class metadata can be grouped together, you would also expect to have something annotating the class.

@Remotable
public class AcmeStartup {
    @Remote
    public void addNewItem(Integer itemId, 
   @Optimize String description, 
   Integer count) { ... }
}

There should not be any argument about which approach is less verbose, easier to specify, and more effectively conveyed.

Other advantages of coupling the metadata with the source code are purely practical. For example, if the application needs to change the addNewItem method to accept an additional backOrder parameter, then the application or the entity that changed the addNewItem method, must also remember to change the XML file, which may be stored in an entirely different location from the class. This requirement to remember to change the XML file is simply because the method parameters were part of the contextual XML information required to specify the method. The maintenance incentive for using annotations, then, is fairly obvious.

Similarly, XML that is not connected to the code is also not an integrated part of the same version-controlled element as the code. Changing one element and creating a new version of it does not intrinsically imply creating a new version of the other, although it is possible to configure some version control systems to do such a thing. Although there are cases in which changing one does not require changing the other, even a dependency in one direction (that is, the metadata on the code) points to the more appropriate coupling of the two.

Facing the Consequences

So what is the cost of brevity? Specifying the metadata beside the source instead of in a decoupled XML file has certain repercussions.

Take the example of a tool for adding metadata to existing classes. At the first step of the process, we immediately hit the first and most obvious problem. What if only the class files are present but the source code is not available? Annotations may only be read at runtime, not added. Annotating the classes is not an option, and the tool is forced to use XML or some other mechanism external to the class.

The next step is to actually add the annotations. This becomes a fairly intensive process for the tool, because the source needs to be parsed and the new annotations added at the correct position. Whereas using XML was a simple matter of having to specify the context and then write out the XML according to the given schema, we now have to find the source code for the element, parse it, add the correct syntactical annotation pieces, and then rewrite it all back out. Just explaining it is exhausting.

Once the annotations have been added, the classes need to be recompiled. For this to be possible, the definitions for the annotations inserted into the source code need to be on the class path. Although this may not be an issue for immediate recompilation, it may be problematic later on. Annotations are, like an XML file, quite happy to exist in environments in which they get completely ignored. If the processing layer for which the annotations were added in the first place uses runtime reflection as a means of interpreting the metadata (which is the typical scenario), the annotations will clearly need to be preserved in the class files until runtime. Fortunately, the VM is more forgiving at class load time of elements that have annotations for which the definitions are not on the class path. It would be nice if the compile-time dependencies were similarly relaxed and any annotations for which interfaces were not found were simply ignored, having a net semantic of a SOURCE RetentionPolicy (that is, the annotations would not be retained in the class files). The XML artifact equivalent to the annotation interface definitions is the schema, which is required only when the XML file is read.

Metadata Programming

If you liken annotations to a coup, it would have to be one in which no capable or trained government is ready to take power. For metadata programming, annotations are currently on the immature side of the spectrum. Some of the obvious features are missing, and others are inadequate for day-to-day use. Some of these are discussed below:

Inheritance. There is none. It would be nice, but support for annotation type inheritance is just not there yet. For example, it would be nice to be able to define an annotation type hierarchy as follows:

public @interface Event {
    String debugString() default "";
}

public @interface SyncEvent extends Event {
    boolean allowPreempting();
}

public @interface AsyncEvent extends Event {
    boolean joinAfterCompletion();
}

If it were possible for annotation interfaces to extend other interfaces, either the SyncEvent or AsyncEvent could be used to annotate an event method and an optional debug string could be specified to be printed to the event log if debugging were enabled. Then, just as in code, if another common member were required, you could easily add a common member by adding it to the Event superinterface. Instead, you must choose one of a few unsightly alternatives. The obvious one is to duplicate the member in both of the events. This produces a potential maintenance headache and goes against the commonly accepted software anti-practice of cloning code. Another option would be to create a common annotation that has common member attributes, as in the following:

public @interface EventOptions {
    String debugString() default "";
}

public @interface SyncEvent {
    EventOptions options() default @EventOptions;
    boolean allowPreempting();
}

public @interface AsyncEvent {
    EventOptions options() default @EventOptions;
    boolean joinAfterCompletion();
}

This approach is also less than optimal, because it now requires the source to have to nest the debug string and any of the other common options inside an EventOptions annotation, as in the following:

@AsyncEvent(
options=@EventOptions(debugString="myEventMethod called"),
joinAfterCompletion=false)
public void myEventMethod(EventDescriptor eventDesc) { ... }

You may come up with your own favorite workaround, but the only elegant solution is to support inheritance of annotation types.

Note that even though annotation types may not be structured in inheritance hierarchies, annotations can still be applied to regular class inheritance trees. The reflective API for annotations offers the ability to access annotations on inherited program elements, much as the rest of the reflective API can operate on classes that are part of an inheritance structure.

Default Values. Thankfully, J2SE 5.0 offers some amount of support for providing defaults, but it is presently rather cumbersome and inadequate. Because null is not allowed as a valid default value, a special "uninitialized" value needs to be reserved for each type. If this is not possible, it will be hard to know, when reading the annotation, whether the default value was actually specified or whether the value was obtained because it was the default (that is, it was not specified). There is often a semantic difference between these types of values. It gets worse with nested annotations, because the default must be an actual annotation. The unpleasant workaround is to create a bogus annotation member that is used simply to distinguish whether the annotation was specified or was provided by default.

public @interface EventOptions {
    String debugString() default "";
    boolean specified() default true;
}

public @interface SyncEvent {
    EventOptions options() default @EventOptions(specified=false);
    boolean allowPreempting();
}

In this example, the specified member is never actually used when EventOptions annotates an element. It is used by the SyncEvent only to default the EventOptions annotation not to be specified and then later by the annotation processing code to determine whether the annotation value was obtained because it was specified or defaulted:

SyncEvent event = element.getAnnotation(SyncEvent.class);
if (event.options().specified()) {
    printDebug(event.options().debugString());
} else {
    // Nothing was specified
}

The resulting example above is arguably no worse than the XML case, in which a default value may be specified only for a simple type. A more useful feature that does not currently exist is when the default is actually dependent on some program element. Default values should be parameterizable and refer to other elements within the same program scope. For example, what if you wanted to make the default refer to a static variable that could be modified by an admin tool? Or if an annotation value were able to be defaulted to the name of the element it is annotating? The ability for default values to be parameterizable would be really helpful for the processor, but in practice this would be rather difficult to implement, because the annotation type does not have any real scope at definition time. Reserved words, such as this, would probably be needed to make default values parameterizable — an achievable goal.

An allowance for single-value annotation types means that if the name of a member is the magic word "value," the name can be skipped (defaulted to value). Although this is somewhat helpful, it seems to be a rather arbitrary and unnecessary choice. If a single-member annotation is being used, it is not obvious why it can't be treated as a "value" member in any case, without actually calling it "value." Because the name is guaranteed to be useful only within a single-member annotation, it doesn't seem to serve any real purpose, and the ability to assign more-descriptive names to my single-member annotations would be useful.

Validation. The allowable set of member value types is fairly restricted and syntactical, and simple value type validation is basically free, just as XML parsers check the type definitions for the base XML schema types. Furthermore, a @Target annotation provides a convenient complimentary check by the VM to ensure that annotation types do not annotate inappropriate program elements or elements that were not meant to be annotated by that annotation. It is more difficult, however, to restrict the annotations that may coannotate an element without doing a great deal of code checking and/or annotation rework. It may not make sense for a method to be both an AsyncEvent and a SyncEvent, but what is to stop someone from annotating it that way? The processor would have to look for both annotations and error-check for the unusual case of both existing on a method. Annotations could be reworked to use enumerated types and thereby limit what could be specified. Inheritance could go some of the way toward solving this problem, because you could at least introduce a parent element that included a single Event member, thus constraining the element to only a single event. What would really help, though, is an additional built-in annotation that would allow an annotation definition to specify a set of annotations that are unacceptable peers annotating the same programming element.

Namespace. A concern in the annotations world is that multiple layers of annotations will start colliding within the same global annotations namespace. For example, some of the common annotation type names, such as @Transaction, are likely to be used by multiple layers to signify slightly different semantics unique to each layer. This is a valid concern, because one of the strengths of annotations is the potential for terseness (potential because they are not necessarily terse; they must be designed to be so) and making names longer to lessen the possibility of collisions could reduce this.

One of the strategies being employed to avert this type of situation is the formation of groups such as JSR 250, which will attempt to define a set of common annotations that can be assigned specific semantics. A standardized set of annotations will hopefully prevent everybody from defining their own individual ones that mean essentially the same thing, but if they actually do have different meanings, the name will hopefully distinguish itself appropriately (and as briefly as possible). Coordination between the standards groups such as JSR 181, JSR 244, JSR 220, and others that are defining annotations would also help.

In the end, the annotations namespace problem is analogous to the class namespace problem, and we may be forced to fully qualify the annotations as a last resort. It looks hideous and hopefully will be needed only in emergency collisions, but it is always there in case it is needed:

@ com.acme.AsyncEvent(joinAfterCompletion=false)
public void myEventMethod(EventDescriptor eventDesc) { ... }

Location Revisited

The very notion of what an annotation is (something that adds extra information about an object) means there must be an object to which to add the information. One of the difficulties is that some metadata is more global in scope than any particular program element, meaning that there is not really a suitable object for annotating or situating the metadata. The only solution so far in this area is the package-info.java file, which allows annotations on a given package. Package-level annotations are located in this file, but there is no hint about how to specify global metadata. Perhaps XML is expected to be used at the application level, which is OK, except that if that is the only reason XML needed to be used, it would be a shame to have to bring in all of the XML baggage for that purpose alone.

Conclusion

In summary, there are still lots of problems with annotations, and some people are still going to want to continue using XML. In fact, annotations are unlikely to ever replace XML as a metadata language. There are specific use cases when one is certainly superior to the other, however.

Next Steps

Read the public review draft of JSR 175 specification

Oracle's Debu Panda on Simplifying EJB Development with EJB 3.0

Download OC4J 10g

Tutorial: EJB 2.1 Techniques

When the metadata lends itself to being more dynamically configurable or is configured at deployment, XML is the right language to use. Similarly, if the source is not available and extra metadata needs to be incrementally added by different people at different stages in the process (as with Enterprise JavaBeans [EJB] roles and the like), XML is the clear favorite.

However, when the goal is really about adding layers of metadata on top of the source and that applies uniquely to specific code elements, metadata in Java should fill the bill perfectly. The ability to supply the metadata at the same time and locality as the code increases ease of development. Not only is development made easier but the processing is also much simpler, because the object model is defined by the annotation type definitions themselves. No extra document object model (DOM) or other model is required. Documentation is also integrated, because the built-in @Documentation annotation will include the annotation types in the generated javadoc.

Annotations are here to stay. They have made their debut on the Java stage and are now being integrated into the various Java specifications and standards. They are being heavily adopted and relied on for standardized metadata within the J2EE standards, such as EJB, as well as making their way into open source components, such as TestNG.

Most of the standard Java 2 Enterprise Edition (J2EE) application server platforms, such as Oracle Containers for J2EE (OC4J), will be offering some amount of annotation support in coming releases. In many ways, this will reduce development and deployment time, leading to ever increasing developer productivity on the Java platform.

Disclaimer

It turns out that some of the issues I raise in this article are discussed in the Q&A section of the JSR 175 specification, but I just don't agree with the rationale or, maybe more appropriately, the use case motivation involved in the decisions that were reached by the JSR 175 expert group. It may just be that my own vision of annotations or usage pattern expectations is slightly different or simply that I do not have the same mind-set as the annotation specification group.

All the work I did with annotations was with a beta release of J2SE 5.0. Some differences may exist in the production release.


Mike Keith is an architect for Oracle Containers for J2EE (OC4J) and TopLink products and currently represents Oracle in the EJB 3.0 expert group.
阅读全文
0 0

相关文章推荐

img
取 消
img