Navigation
Recherche
|
How to describe Java code with annotations
mardi 2 juillet 2024, 15:05 , par InfoWorld
There are times when you need to associate metadata, or data that describes data, with classes, methods, or other elements in your Java code. For example, your team might need to identify unfinished classes in a large application. For each unfinished class, the metadata would include the name of the developer responsible for finishing the class and the class’s expected completion date.
Before Java 5, comments were Java’s only flexible mechanism for associating metadata with application elements. But because the compiler ignores them, comments are not available at runtime. And even if they were available, you would have to parse the text to obtain crucial data items. Without standardizing how the data items were specified, they might be impossible to parse. Java 5 changed everything by introducing annotations, a standard mechanism for associating metadata with various application elements. This tutorial introduces you to Java annotations. What you’ll learn in this Java tutorial The four elements of a Java annotation Using @interface to declare annotation types Meta-annotation types and the problem of flexibility How to process annotations Java’s pre-defined annotation types download Get the code Download the source code for examples in this Java tutorial. Created by Jeff Friesen. Elements of a Java annotation An annotation in Java consists of four elements: An @interface mechanism for declaring annotation types. Meta-annotation types, which you can use to identify the application elements an annotation type applies to, identify the lifetime of an annotation (an instance of an annotation type), and more. Support for annotation processing via an extension to the Java Reflection API and a generalized tool for processing annotations. Java’s standard (pre-defined) annotation types. You’ll learn how to use each of these elements in your Java annotations. Using @interface to declare annotation types You can declare an annotation type by specifying the @ symbol immediately followed by the interface reserved word and an identifier. For example, Listing 1 declares a simple annotation type that you might use to annotate thread-safe code. Listing 1. ThreadSafe.java public @interface ThreadSafe { } After declaring this annotation type, you would prefix the methods that you considered thread-safe with instances of this type by adding the @ symbol followed by the type name to the method headers. Listing 2 shows a simple example where the main() method is annotated @ThreadSafe. Listing 2. AnnDemo.java (version 1) public class AnnDemo { @ThreadSafe public static void main(String[] args) { } } ThreadSafe instances supply no metadata other than the annotation type name. However, you can supply metadata by adding elements to this type, where an element is a method header placed in the annotation type’s body. As well as not having code bodies, elements are subject to the following restrictions: The method header cannot declare parameters. The method header cannot provide a throws clause. The method header’s return type must be a primitive type (e.g., int), java.lang.String, java.lang.Class, an enum, an annotation type, or an array of one of these types. No other type can be specified for the return type. As another example, Listing 3 presents a ToDo annotation type with three elements identifying a particular coding job, specifying the date when the job is to be finished, and naming the coder responsible for completing the job. Listing 3. ToDo.java (version 1) public @interface ToDo { int id(); String finishDate(); String coder() default 'n/a'; } Note that each element declares no parameter(s) or throws clause, has a legal return type (int or String), and terminates with a semicolon. Also, the final element reveals that a default return value can be specified; this value is returned when an annotation doesn’t assign a value to the element. Listing 4 uses ToDo to annotate an unfinished class method. Listing 4. AnnDemo.java (version 2) public class AnnDemo { public static void main(String[] args) { String[] cities = { 'New York', 'Melbourne', 'Beijing', 'Moscow', 'Paris', 'London' }; sort(cities); } @ToDo(id = 1000, finishDate = '10/10/2019', coder = 'John Doe') static void sort(Object[] objects) { } } Listing 4 assigns a metadata item to each element; for example, 1000 is assigned to id. Unlike coder, the id and finishDate elements must be specified; otherwise, the compiler will report an error. When coder isn’t assigned a value, it assumes its default 'n/a' value. Java provides a special String value() element that can be used to return a comma-separated list of metadata items. Listing 5 demonstrates this element in a refactored version of ToDo. Listing 5. ToDo.java (version 2) public @interface ToDo { String value(); } When value() is an annotation type’s only element, you don’t have to specify value and the = assignment operator when assigning a string to this element. Listing 6 demonstrates both approaches. Listing 6. AnnDemo.java (version 3) public class AnnDemo { public static void main(String[] args) { String[] cities = { 'New York', 'Melbourne', 'Beijing', 'Moscow', 'Paris', 'London' }; sort(cities); } @ToDo(value = '1000,10/10/2019,John Doe') static void sort(Object[] objects) { } @ToDo('1000,10/10/2019,John Doe') static boolean search(Object[] objects, Object key) { return false; } } Meta-annotation types and the problem of flexibility You can annotate types (e.g., classes), methods, local variables, and more. However, this flexibility can be problematic. For example, you might want to restrict ToDo to methods only, but nothing prevents it from being used to annotate other application elements, as demonstrated in Listing 7. Listing 7. AnnDemo.java (version 4) @ToDo('1000,10/10/2019,John Doe') public class AnnDemo { public static void main(String[] args) { @ToDo(value = '1000,10/10/2019,John Doe') String[] cities = { 'New York', 'Melbourne', 'Beijing', 'Moscow', 'Paris', 'London' }; sort(cities); } @ToDo(value = '1000,10/10/2019,John Doe') static void sort(Object[] objects) { } @ToDo('1000,10/10/2019,John Doe') static boolean search(Object[] objects, Object key) { return false; } } In Listing 7, ToDo is also used to annotate the AnnDemo class and cities local variable. The presence of these erroneous annotations might confuse someone reviewing your code, or even your own annotation processing tools. For the times when you need to narrow an annotation type’s flexibility, Java offers the Target annotation type in its java.lang.annotation package. Target is a meta-annotation type—an annotation type for annotating annotation types. This is different from a non-meta-annotation type, whose annotations annotate application elements such as classes and methods. The Target annotation type identifies the kinds of application elements to which an annotation type is applicable. These elements are identified by Target’s ElementValue[] value() element. java.lang.annotation.ElementType is an enum whose constants describe application elements. For example, CONSTRUCTOR applies to constructors and PARAMETER applies to parameters. Listing 8 refactors Listing 5’s ToDo annotation type to restrict it to methods only. Listing 8. ToDo.java (version 3) import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target({ElementType.METHOD}) public @interface ToDo { String value(); } Given the refactored ToDo annotation type, an attempt to compile Listing 7 now results in the following error message: AnnDemo.java:1: error: annotation type not applicable to this kind of declaration @ToDo('1000,10/10/2019,John Doe') ^ AnnDemo.java:6: error: annotation type not applicable to this kind of declaration @ToDo(value='1000,10/10/2019,John Doe') ^ 2 errors Additional meta-annotation types Java 5 introduced three important meta-annotation types, which are found in the java.lang.annotation package: Retention indicates how long annotations with the annotated type are to be retained. This type’s associated java.lang.annotation.RetentionPolicy enum declares the following constants: CLASS: The compiler records annotations in a class file and the virtual machine doesn’t retain them. This is the default policy RUNTIME: The compiler records annotations in a class file and the virtual machine retains them. SOURCE: The compiler discards annotations. Documented indicates that instances of Documented-annotated annotations are to be documented by javadoc and similar tools. Inherited indicates that an annotation type is automatically inherited. Another meta-annotation type, introduced in Java 8, is java.lang.annotation.Repeatable. Repeatable is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. In other words, you can apply multiple annotations from the same repeatable annotation type to an application element, as demonstrated here: @ToDo(value = '1000,10/10/2019,John Doe') @ToDo(value = '1001,10/10/2019,Kate Doe') static void sort(Object[] objects) { } This example assumes that ToDo has been annotated with the Repeatable annotation type. How to process annotations Annotations are meant to be processed; otherwise, there’s no point in having them. Java 5 extended the Java Reflection API to help you create your own annotation processing tools. For example, Class declares an Annotation[] getAnnotations() method that returns an array of java.lang.Annotation instances. These instances describe annotations present on the element described by the Class object. Listing 9 presents a simple application that loads a class file, interrogates its methods for ToDo annotations, and outputs the components of each found annotation. Listing 9. AnnProcDemo.java import java.lang.reflect.Method; public class AnnProcDemo { public static void main(String[] args) throws Exception { if (args.length!= 1) { System.err.println('usage: java AnnProcDemo classfile'); return; } Method[] methods = Class.forName(args[0]).getMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isAnnotationPresent(ToDo.class)) { ToDo todo = methods[i].getAnnotation(ToDo.class); String[] components = todo.value().split(','); System.out.printf('ID = %s%n', components[0]); System.out.printf('Finish date = %s%n', components[1]); System.out.printf('Coder = %s%n%n', components[2]); } } } } After verifying that exactly one command-line argument (identifying a class file) has been specified, main() loads the class file via Class.forName(), invokes getMethods() to return an array of java.lang.reflect.Method objects identifying all public methods in the class file, and processes these methods. Method processing begins by invoking Method’s boolean isAnnotationPresent(Class
https://www.infoworld.com/article/2257851/how-to-describe-java-code-with-annotations.html
Voir aussi |
56 sources (32 en français)
Date Actuelle
dim. 22 déc. - 14:12 CET
|