|
Developer: J2EE
Making the Most of Java's Metadata
by Jason Hunter
Learn how to use the metadata annotations provided in J2SE 5.0
The recent release of J2SE 5.0 (also known by its codename "Tiger") introduced
numerous Java language changes designed to make programming in Java more
expressive, developer-friendly, and safe. I covered many of Java's new
features in a September 2003 article entitled "Big Changes Coming for Java". One
significant change I didn't coverat the time it hadn't been fully
sketched outwas Java's Metadata facility. In a new four-part series of
articles, beginning with this one, I'll continue where I left off one year ago
and show you how to make the most of Java's Metadata.
In this first article I'll explain the purpose of metadata and demonstrate how
to use metadata annotations provided in the core J2SE libraries.
In the second article, I'll show how to write your own annotations (first
writing simple ones like @Copyright and then looking at more advanced
annotations like those built into the core language).
In the third article, I'll demonstrate how tools can use annotations at
build-time (generating new source or supporting files) and how programs can
use annotations at runtime (to change code behavior).
In the fourth and final article, I'll cover how authoring and deploying Web
Services will be much easier in the future thanks to standard metadata
annotations under development in JSR-181, for which Oracle is a member of the expert group. (Oracle is an active proponent of increased support for design-time metadata in development tools.)
Metadata
I admit when I first saw the proposal for JSR-175, "A Metadata Facility for the Java Programming Language," (released in September 2004; Oracle is also a member of this expert group), I anticipated it producing yet another
properties file that had to be placed in a JAR's META-INF directory or another
XML deployment descriptor that had to be bundled next to JARs. Happily,
that's not what metadata is about. In fact, it's just the opposite. Java's
new metadata facility provides a standard way to annotate Java code from
within Java code. It lets you put descriptive metadata right next to the
element being described.
When talking about metadata you see several similar terms float by, so here's a little glossary to help you understand their differences:
| Term | Definition |
| Metadata | Data about data. The goal of JSR-175 was to provide metadata facilities
in the Java language. |
| Annotation | A special kind of Java construct used to decorate a class, method, field,
parameter, variable, constructor, or package. It's the vehicle chosen by
JSR-175 to provide metadata. |
| Annotation Type | A named variety of annotation, with a particular implementation |
| Attribute | A particular metadata item that's being specified by an annotation. Sometimes used interchangeably with annotation |
Example: A Fuji apple has the attribute that it's red. Assuming there's a
FujiApple class you can specify its color using an annotation of the @Color
annotation type. By doing that you've provided metadata about the apple.
The need for metadata in Java has been around since release 1.0. Java just
never provided a standard mechanism to record metadata, and as a result we
programmers have found various tricks and hacks to add metadata using the
tools at our disposal. Some of the places you see metadata used in J2SE 1.4
and earlier:
- The transient keyword
- The Serializable marker interface
- The SingleThreadModel servlet interface
- Elements within the web.xml deployment descriptor
- The META-INF/MANIFEST.MF file
- The BeanInfo interface
- The @deprecated Javadoc comment
- All the XDoclet Javadoc tags.
When using these techniques you probably didn't think you were adding
metadata, but that's in fact what you were doing. The problem with the above
approaches is that they're all different ways of solving the same problem and
don't generalize well. Each has at least one drawback that's being addressed
by the new Metadata facility.
For some in this list, the limitations are obvious. Using keywords doesn't
scale; you can't use users defining their own keywords. Marker interfaces
don't provide any information except their existence (i.e. they don't take
parameters) and they only work on classes, not fields or methods or packages.
Some others in the list may look reasonable. Using XML support files seems
like a good idea, and in fact still is in many cases. But for many uses where
we've used XML files, like to dictate which of a class's methods should be
seen as web services, it would be more elegant to put the rules within the
Java code next to the methods directly. With metadata you can let the XML
descriptor files containing just the deployment-related decisions.
Probably the most elegant uses of metadata from the list are the @deprecated
Javadoc comment and the XDoclet tags created in its image. That's probably
why the JSR-175 syntax looks an awful lot like the @deprecated tag, as we'll
see in the next section.
Annotations
Annotations are easy to attach to code constructs. You write an "at" sign (@)
and then the annotation type name, placing the annotation (or annotations)
directly before the item being annotated. Here's a simple example:
import javax.jws.WebService;
import javax.jws.WebMethod;
@WebService
public class HelloWorldService {
@WebMethod
public String helloWorld() {
return "Hello World!";
}
}
When deployed in the right environment, the addition of the @WebService and
@WebMethod annotations instruct the web service environment to make this class
into a web service.
You can annotate methods, classes, fields, parameters, variables,
constructors, and even whole packages (using a special external
package-info.java file). Annotations can take any number of named arguments
within parentheses. Here's a more advanced example class decorated using
annotations to make a web service. It includes a theoretical JNDI environment
variable lookup:
@WebService(
name = "PingService",
targetNamespace="http://acme.com/ping"
)
@SOAPBinding(
style=SOAPBinding.Style.RPC,
use=SOAPBinding.Use.LITERAL
)
public class Ping {
public @env double level = 500.0; // JNDI lookup
public @WebMethod(operationName = "Foo") {
void foo() { }
}
}
This example shows annotations attached to a class, a variable, and a method
(there are actually two on the class). The @env annotation doesn't have any
arguments and so it doesn't need any parentheses. The other annotations take
one or more named parameters.
When you create a new annotation type you dictate which parameter names are
allowed and their types. The types accepted by an annotation are strictly
limited; they can only be primitives, String, Class, enum types, annotation
types, and arrays of the preceding types. Passes parameters must always be
non-null compile-time constants.
Understanding what effect the annotations shown in this example have will have
to wait for the fourth article in this series. Let's start with a look at the
simple annotation types provided with J2SE 5.0: @Override, @Deprecated, and
@SuppressWarnings.
Built-In Annotations
As we look at the three standard user-level annotations, one has to wonder:
with all the possible annotation types that could be provided, why did Tiger
provide a scant three? The reason is that providing a multitude of standard
annotations wasn't the goal.
The charter for JSR-175 strictly dictated it was to define a metadata facility.
It's left to us as programmers to write custom annotation types and to other JSRs to write a set of standard annotation types. For example, there's a new JSR-250, titled "Common Annotations for the
Java Platform," chartered to "develop annotations for common semantic concepts
in the J2SE and J2EE platforms that apply across a variety of individual
technologies." JSR-250 plans to deliver its standard set of annotations in
the javax.annotations package sometime during the spring of 2005. There's
also the aforementioned JSR-181, which would make
it easier to write web services in a J2EE container (what we're covering in
the fourth article in this series). In fact most new enterprise JSRs, from
Servlets 2.5 to EJB 3.0 to JDBC 4.0, are considering what niceties annotations
might provide.
@Override
The first J2SE standard annotation, @Override, lets you add a new optional
compiler check to your code. Its presence on a method indicates that the
method is intended to override a method in a superclass. If the compiler
detects the method isn't really overriding anything, it's a compile error.
With regular use, @Override helps you to avoid the subtle bugs you get when
your method signatures don't exactly match when your polymorphism becomes
what you might call "unimorphism."
As an example, the following code may look quite reasonable:
public class OverrideExample {
@Override
public boolean equals(OverrideExample obj) {
return false;
}
}
However, when you compile OverrideExample.java you receive an error indicating
a subtle problem.
% javac OverrideExample.java
javac OverrideExample.java
OverrideExample.java:3: method does not override a method from its superclass
@Override
^
1 error
By giving the compiler the hint you were expecting an override that lets the
compiler catch the subtle bug that the equals() method takes an Object type
parameter.
Is the @Override annotation useful in the real world? Only if you're an
ultra-disciplined programmer who's willing to mark every overriding method
with @Override. How many of us can claim that level of discipline? I don't
think I can. Perhaps IDEs will find a way to encourage or enforce @Override
use.
@Deprecated
The second standard annotation is @Deprecated and has nearly identical
behavior to the @deprecated Javadoc tag. You use it like this:
public class DeprecatedExample {
@Deprecated
public static void badMethod() {
}
}
public class DeprecatedUser {
public static void main(String[] args) {
DeprecatedExample.badMethod();
}
}
The @Deprecated annotation looks a lot like the @deprecated tag except it
appears outside a comment, right before the method or class declaration, and
has a capital "D". If you try to compile the above code javac produces a
warning:
% javac Deprecated*.java
Note: DeprecatedUser.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
1 error
If you follow the warning's suggestion and compile with -Xlint:deprecation,
you get details on the warning:
% javac -Xlint:deprecation
DeprecatedUser.java:3: warning: [deprecation] badMethod() in DeprecatedExample
has been deprecated
DeprecatedExample.badMethod();
Is the @Deprecated annotation more useful than @Override? I think
not. The annotation doesn't support any arguments, so unlike the Javadoc tag
you can't provide a string to explain the deprecation and recommend an
alternative method to use. The @Deprecated annotation actually provides less
value than the @deprecated tag. The only advantage with the annotation is
that you can programatically detect deprecated items at runtime. For that
reason, conventional wisdom says to use both the @deprecated tag and the
@Deprecated annotation, one for documentation and the other for runtime
reflection.
I believe it's unfortunate JSR-175 didn't choose to do more with @Deprecated.
At minimum the annotation should have reproduced the @deprecated tag's ability
to take a string explanation, something the compiler could output with its
deprecation warning. With additional arguments, @Deprecated also could have
accepted an "isError" boolean parameter to indicate whether use of the method
was merely discouraged or should be considered a compile error (complete with
a nice custom explanation explaining the reason for the error). Looking at C#
for example one finds the attribute [Obsolete] that does exactly this and it
proves quite useful.
@SuppressWarnings
The last J2SE-provided annotation is @SuppressWarnings. This annotation acts
as a directive to the compiler telling it to be quiet about certain warnings
within the annotated code element.
A bit of background: J2SE 5.0 adds several new features to the Java language
and with them a plethora of new warnings and a promise to add more in the
future. You can control the reporting of these warnings with -Xlint arguments
to "javac" as shown above in the @Deprecated section.
By default the Sun compiler outputs warnings as simple two-liners. By adding
an -Xlint:keyword flag like -Xlint:finally you can get a full
explanation for errors of the keyword's type. By adding a dash before the
keyword and writing -Xlint:-keyword you can suppress the warning. (The
full list of keywords supported by -Xlint can be found on the javac
documentation page.) Here's a
cheat sheet:
| Keyword | Purpose |
| deprecation | Warn when using a deprecated class or method |
| unchecked | Warn when performing an unchecked conversion, such as when using a collection without using generics to specify the type that the collection holds |
| fallthrough | Warn when a switch block falls-through to the next case without a break |
| path | Warn when there's a non-existent path in a classpath, sourcepath, etc. |
| serial | Warn when there's a missing a serialVersionUID definitions on serializable classes. |
| finally | Warn about any finally clause that cannot complete normally |
| all | Warn about all of the above |
The @SuppressWarnings annotation allows you to do selective suppression of
warnings within particular code sections (i.e. classes or methods). The idea
is that when you see a warning, you should investigate it, and if you
determine it's not a problem, you add an @SuppressWarnings annotation so you
won't see the warning anymore. Although it sounds like this could mask
potential errors, it actually improves code safety because it keeps you from
becoming insensitized to warnings every warning you see should be worth
looking into.
Here's an example using @SuppressWarnings to suppress a deprecation warning:
public class DeprecatedExample2 {
@Deprecated
public static void foo() {
}
}
public class DeprecatedUser2 {
@SuppressWarnings(value={"deprecation"})
public static void main(String[] args) {
DeprecatedExample2.foo();
}
}
The @SuppressWarnings annotation accepts a "value" variable that's an array of
strings indicating the warnings that should be suppressed. The set of legal
strings varies by the compiler, but on the JDK it's conveniently the same set
of keywords that can be passed to -Xlint. Compilers are required to ignore
any keywords they don't recognize; handy if you use a few different compilers.
Because the @SuppressWarnings annotation only accepts one parameter and used
the special name "value" for that parameter, you have the option to drop the
value= as a convenient shorthand:
public class DeprecatedUser2 {
@SuppressWarnings({"deprecation"})
public static void main(String[] args) {
DeprecatedExample2.foo();
}
}
You can pass any number of string values in the single array parameter to the
annotation and place the annotation at any level. For example, the following
example code dictates that deprecation warnings be suppressed for the entire
class, with unchecked and fallthrough warnings suppressed only within the
main() method code:
import java.util.*;
@SuppressWarnings({"deprecation"})
public class NonGenerics {
@SuppressWarnings({"unchecked","fallthrough"})
public static void main(String[] args) {
Runtime.runFinalizersOnExit();
List list = new ArrayList();
list.add("foo");
}
public static void foo() {
List list = new ArrayList();
list.add("foo");
}
}
Is @SuppressWarnings more useful than the previous two annotations?
Absolutely. However, the annotation isn't yet fully supported in the JDK
1.5.0 release and if you try it with 1.5.0 it acts like a no-op. Calling
-Xlint:-deprecation also doesn't have any effect. Sun hasn't stated when
support will be added but hinted it will happen in an upcoming dot release.
Going Further
If you tried to look at the Javadocs pages for these attributes you may have
had a hard time finding them. They're in the core java.lang package but
they're a bit hidden; they appear at the very bottom of the Javadoc class
listing after Exceptions and Errors.
Notice the strange annotations @Target and @Retention attached to the
SuppressWarnings annotation? These are called meta-annotations and describe
where the annotation is applicable. I'll explain that, along with how to
apply meta-annotations to your own annotations, the second article in this
series.
Jason Hunter is author of Java Servlet Programming and co-author of Java Enterprise Best Practices (both O'Reilly). He's an Apache Member and as Apache's representative to the Java Community Process Executive Committee he established a landmark agreement for open source Java. He's publisher of Servlets.com and XQuery.com, an original contributer to Apache Tomcat, the creator of the com.oreilly.servlet library, and a member of the expert groups responsible for Servlet, JSP, JAXP, and XQJ API development. He co-created the open source JDOM library to enable optimized Java and XML integration. In 2003, he received the Oracle Magazine Author of the Year award.
|