Take full advantage of Java metadata, part 3rd: Advanced Processing __java

Source: Internet
Author: User
Tags auth modifier modifiers reflection web services stringbuffer

Take full advantage of Java metadata, part 3rd: Advanced Processing
Author: Jason Hunter

Learn about techniques and mechanisms for handling annotations at run time, even at compile time, and for changing program behavior.

In the first article in this series (four articles), I introduced the new metadata tools for Java and the built-in annotation types @Override, @Deprecated, and @SuppressWarning. In the second article, I described how to write a custom annotation type and control the annotation behavior using meta annotations in java.lang.annotation. In this third article, I will demonstrate techniques and mechanisms for handling annotations at run time, even at compile time, and for changing program behavior.

Value of run-time annotation processing

The ability to interact with annotations at run time can provide important value. Imagine a next-generation test tool built to take advantage of annotations. Such tools can run methods that are marked as @Test-no masking of method names used to differentiate between test methods and support methods. By using Parameters on @Test annotations, each test can be grouped logically to control which tests it relies on and to accept various test case parameters. (Such a test tool is not just an assumption, you can actually find such a tool in beust.com/testng.) )

These possibilities continue to exist in the Java EE 5.0 environment, where annotation-driven "resource injection" appears to be a standard operating procedure. With resource injection, a container can "inject" a value into a variable that is specifically annotated by its managed object. For example, if the servlet requires a data source, the model in J2SE 1.4 extracts the resources from JNDI:

Public Javax.sql.DataSource Getcatalogds () {
try {
Javax.naming.IntialContext initctx = new InitialContext (); C2/>catalogds = (javax.sql.DataSource)
initctx.lookup ("Java:comp/env/jdbc/catalogds"); 
  }
catch (Javax.naming.NamingException ex) {
//Handle failure 
  }
} public

products[] GetProducts () {
Javax.sql.DataSource Catalogds = Getcatalogds ();
Connection con = catalogds.getconnection ();
  // ...
}
The above code is not only very complex, but in order for the resource to be available for JNDI lookup, the servlet must declare a <resource-ref> entry in its separate Web.xml deployment descriptor:
<resource-ref>
<description>catalog datasource</description>
<res-ref-name> jdbc/catalogds</res-ref-name> 
<res-type>javax.sql.DataSource</res-type>
< res-auth>container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope> 
</resource-ref>
In a J2SE 5.0 environment that provides resource injection capabilities, the servlet simply sets annotations for its requirements in code and enables the data source to reference "injected resources" before executing code:
@Resource Javax.sql.DataSource Catalogds;

Public products[] GetProducts () {
Connection con = catalogds.getconnection ();
  // ...
}
In particular, annotations themselves serve as deployment descriptors, eliminating the need to declare a separate entry in the Web.xml deployment descriptor. @Resource annotations can accept parameters that indicate resource names, types, validation, and scope equivalents. These items can be inferred easily, even without parameters.

JSR-181 (Web service metadata for Java) is now complete, and we'll see annotations that are widely used to guide the container to deploy Web services. Annotations are gradually becoming the raw material that defines the protocol between a container and a managed object. I will introduce the JSR-181 Web service in the fourth article in this series, the final article.

Run-time Reflection

Java uses reflection to expose annotations and enable programs to change their behavior at run time. Java introduced basic reflection in J2SE 1.2 and added annotatedelement and Annotation interfaces to support annotations in J2SE 5.0. These two interfaces allow you to locate annotations and call them after you get a handle on the annotation to retrieve its arguments.

The new Annotatedelement interface is in the Java.lang.reflect package and is implemented by classes, constructor, Field, method, and Package:

Public interface Annotatedelement {
annotation[] getannotations ();
Annotation[] Getdeclaredannotations ();

<t extends annotation> T getannotation (class<t>);

Boolean isannotationpresent (class<? extends annotation>);
}
The Getannotations () method returns all annotations attached to the given element. The Getdeclaredannotations () method is similar to the method, but only returns a specially-declared annotation at that location, not a @Inherited annotation.

The Getannotation () method accepts a class type and returns annotations of that type. The method uses a paradigm, so the returned value is implicitly converted accordingly. It returns this type regardless of what type of class is passed to the method. The Isannotationpresent () method allows you to see if an annotation exists without retrieving annotations. It also uses the paradigm to enforce that the class type it accepts must be the one that implements Annotation.

Each annotation type automatically implements the Java.lang.annotation.Annotation interface. This will implement this interface in the background when you declare a new annotation type using the @interface keyword. Using the method on Annotatedelement, you can get any Annotation type. For example, the following code extracts the Unfinisheddemo @Unfinished annotation from the previous article and requests its precedence:

Unfinished u =
UnfinishedDemo.class.getAnnotation (unfinished.class);
U.priority ();
Note that the paradigm makes the interface declaration a bit messy, but makes the feature code very elegant. In addition, it makes sense that the deleted method used to declare the annotation parameter eventually becomes the method that is called to retrieve its value.

The following example shows how to perform a complete "dump" of all the unfinished parts of the Unfinisheddemo class.

Import com.servlets.*;
Import java.lang.reflect.*;
Import java.util.*;

public class Unfinisheddump {public
static void Main (string[] args) {
class C = Unfinisheddemo.class;
System.out.println ("Package:");
Dump (C.getpackage ());
System.out.println ("Class:");
Dump (c);
System.out.println ("constructor:");
Dump (C.getconstructors ());
System.out.println ("Methods:");
Dump (C.getmethods ());
  }

public static void Dump (annotatedelement[] elts) {for
(annotatedelement e:elts) {dump (e);}
  }

Written specifically for unfinished annotation type public
static void dump (Annotatedelement e) {
if (e = = Nu ll | |
! E.isannotationpresent (Unfinished.class)) {return
;
    }
Unfinished u = e.getannotation (unfinished.class);
String desc = U.value ();
Unfinished.priority Prio = u.priority ();
string[] Owner = U.owner ();
System.out.println ("  " + desc +); Prio: "+ Prio +
"; Owner: "+ arrays.aslist (owner));
  }

Here's a step-by-step introduction to this code. The main () method requests information dumps about the Unfinisheddemo class, its packages, its constructors, and each of its methods. The dump () method has two overloaded variant forms. The first variant uses Java's new foreach loop to accept an array and call dump () for each item in the array. The second variant accepts an item and performs a bulk operation.

The primary role of the dump () method is to first check for the existence of unfinished annotations. If it does not exist, nothing is displayed, so it will return. If it does, it gets the annotation using getannotation (), gets its value, priority, and owner list, and then prints the values to the console. It uses the List to wrap the array as an easy way to print an array.

The output looks like this:

Package:
Package scope; Prio:medium owner: []
class:
class scope; prio:low; owner: []
constructor:
constructor Prio:medium; owner: []
Methods: Method
; prio:medium; Owner:[jason]
A basic @Test tool

I pointed out earlier the possibility of @Test annotation-driven test tools. The following is a basic example in java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html.

We will maintain the simplicity of the annotation itself. It does not accept parameters, persists until run time, and can only be applied to methods:

Import java.lang.annotation.*;

@Retention (retentionpolicy.runtime)
@Target (elementtype.method) public
@interface Test {}
We will specify a convention that if an exception is not thrown at execution time, any method marked as @Test will pass, and if the exception is propagated to the caller, the method will fail. Therefore, the following simple example contains a successful method, a failed method, and a method that is not tested:
public class Foo {
@Test public static void M1 () {} public static void
m2 () {}
@Test public static void M3 () {
throw new runtimeexception ("Boom");
  }

You can implement the test tools as simple code as shown below:
Import java.lang.reflect.*;
Import static java.lang.System.out;

public class Runtests {public
static void Main (string[] args) throws Exception {
int passed = 0, failed = 0;
  for (Method M:class.forname (Args[0)). GetMethods ()) {
if (m.isannotationpresent (Test.class)) {
try {
m.invoke (null);
passed++;
} catch (Throwable ex) {
out.printf (
"Test%s failed:%s%n", M, Ex.getcause ());
failed++
    }
}} out.printf ("passed:%d, failed:%d%n", passed, Failed);
  }

The main () method loads the class specified as Args[0 and iterates through each of its methods. For each method, it checks to see if the @Test annotation exists, and calls the method if it exists. A successful return increments the passed count. Any exceptions trigger capture, error reporting, and failed increments. When the Testable method is complete, the method prints a final summary. A more advanced version examines Annotation to determine whether and how to invoke the method.

Careful users will notice the out.printf () call. The printf () method is a new method introduced in J2SE 5.0, which is very similar to the old printf () method that we use most often in C language. %d in the format string is replaced by a decimal value, and%n resolves to a platform-specific newline character. Because the static import statement was used at the beginning of the code, we were able to replace "System.out" with "out", which is another J2SE 5.0 attribute.

Compile-time

annotation processing at compile time involves more operations than run-time processing, but has a much more powerful function. To provide compile-time hooks, Java 5 introduces a new annotation processing tool called "apt". It is a wrapper for Javac, which contains a com.sun.mirror.* mirror API for programmatic access to code processed through the tool. With the APT tool, you can issue comments, warnings, and even errors during compilation. You can also generate a new text file, binary file, or source file. That's what the tool means.

Suppose you need a invariant subclass, a superclass similar to it, but any attempt to change its value will cause the exception's class. The class is implemented in the Java collection library, and it is a common convention in other programs. Unfortunately, it is difficult to write in pure Java because it is difficult to keep subclasses synchronized with superclass and not to omit new setter methods.

Use annotations to resolve this issue. First, we write a basic annotation type @Immutable:

Import java.lang.annotation.*;

@Documented
@Target (elementtype.type) public
@interface immutable {
String value ();
}

Then we can add the annotation to each immutable subclass: Public

class Project {
//Content here
}

@Immut Able public
class Immutableproject {}
Even if the immutableproject is left blank, the APT tool can generate a whole new Immutableproject.java during the compilation process. You can control the tool by providing a annotationprocessorfactory (return custom Annotationprocessor instance) for the APT tool. Each annotationprocessor can use the Mirror class to check these classes through tools and output annotations, warnings, errors, support files, or Xinyuan files (@Immutable need a new source file). After the APT tool is complete, it will invoke Javac.

The following is a basic annotationprocessorfactory implementation that supports only @Immutable and returns Immutableannotationprocessor:

Import java.util.*;
Import java.io.*;
Import com.sun.mirror.apt.*;
Import com.sun.mirror.declaration.*;
Import com.sun.mirror.util.*;

public class Immutableannotationprocessorfactory
implements Annotationprocessorfactory {
public Annotationprocessor
getprocessorfor (set<annotationtypedeclaration> Atds,
Annotationprocessorenvironment env) {
if (!atds.isempty ()) {return
new Immutableannotationprocessor (env);
else {return
annotationprocessors.no_op;
    }

} Public collection<string> Supportedannotationtypes () {return
collections.singletonlist ("immutable");
  } Public

collection<string> Supportedoptions () {return
collections.emptylist ();
  }
}
The factory is very simple. The important point is in the Immutableannotationprocessor class as follows:
Import java.util.*;
Import java.io.*;
Import com.sun.mirror.apt.*;
Import com.sun.mirror.declaration.*;

Import com.sun.mirror.util.*; public class Immutableannotationprocessor implements Annotationprocessor {private final

Annotationprocessorenvironment env;
  Immutableannotationprocessor (annotationprocessorenvironment env) {this.env = env; public void process () {Declarationvisitor visitor = Declarationvisitors.getsourceorderdeclarationscanner (New CLASSV
Isitor (), declarationvisitors.no_op);
    For (Typedeclaration type:env.getSpecifiedTypeDeclarations ()) {type.accept (visitor);  } private class Classvisitor extends Simpledeclarationvisitor {public void visitclassdeclaration (classdeclaration c)
{collection<annotationmirror> annotations = c.getannotationmirrors ();
Typedeclaration immutable = env.gettypedeclaration ("immutable");
for (Annotationmirror mirror:annotations) {if (Mirror.getannotationtype (). GetDeclaration (). Equals (immutable)) { ClassDeclaration SUPERCLAss = C.getsuperclass (). GetDeclaration (); Check that we found a super class other than Object if (Superclass.getsimplename (). Equals ("Object") {Env.getmessager (
). Printerror ("@Immutable annotations can only is placed on subclasses");
          Return
} String errormessage = null;
map<annotationtypeelementdeclaration,annotationvalue> values = Mirror.getelementvalues (); For (Map.entry<annotationtypeelementdeclaration, annotationvalue> entry:values.entrySet ()) {Annotationvalue
Value = Entry.getvalue ();
          ErrorMessage = Value.tostring ();
String newline = system.getproperty ("Line.separator");
String packagestring = C.getpackage (). Getqualifiedname ();
String Newclass = C.getsimplename ();
try {stringbuffer sourcestring = new StringBuffer (); Sourcestring.append ("package" + packagestring + ";" + newline);
Sourcestring.append ("public class" + Newclass + "extends" + superclass.getsimplename () + "{" + newline); Collection<methoddeclaration> methods = supErclass.getmethods (); for (MethodDeclaration m:methods) {if (M.getsimplename (). StartsWith ("set")) {collection<modifier> modifiers = m.
GetModifiers ();
                for (Modifier mod:modifiers) {sourcestring.append (mod + "");
} sourcestring.append (M.getreturntype () + "");
Sourcestring.append (M.getsimplename () + "(");
collection<parameterdeclaration> params = M.getparameters ();
int count = 0;
For (parameterdeclaration p:p arams);
Sourcestring.append (P.gettype () + "" + p.getsimplename ());
count++;
                  if (Count!= params.size ()) {Sourcestring.append (",");
} sourcestring.append (") {" + newline);
Sourcestring.append ("throw New RuntimeException" ("+ errormessage +"); "+ newline);
              Sourcestring.append ("}" + newline);

} sourcestring.append ("}" + newline);
System.out.println ("-------generated SOURCE FILE--------");
System.out.println (Sourcestring.tostring ()); System.out.println ("--------------------------------------");
PrintWriter writer = Env.getfiler (). Createsourcefile (packagestring + ".")
+ Newclass);
          Writer.append (sourcestring);
          }catch (IOException e) {} env.getmessager (). Printerror ("Failed to create" + Newclass + ":" +e.getmessage ());
 }
        }
      }
    }
  }
}
The processor accesses the declaration of a class that is marked as @Immutable and, when it finds one, looks at the method of the superclass that begins with "set" and then copies it to a new source file. permissions, return values, and parameters are replicated, and the body is hard-coded to throw runtimeexception. If a class that is marked as @Immutable is missing a non-object superclass, the processor logic generates a compilation error. When the processor completes and generates the replacement source file, the control is passed to the Javac.

APT tools were installed next to Javac and shared similar command-line options and added sm-factory (pointing apt to SM Annotationprocessorfactory instances) and Sm-factorypath (indicating where to find the factory's class files )。

Apt-factorypath.-factory immutableannotationprocessorfactory *.java
While writing this article, there are no <apt> Ant tasks, but you can use <exec> to invoke apt:
<exec executable= "apt" >
<env key= "PATH" path= "${java.home}/bin"/> <arg "-
D line=}" >
<arg line= "s ${temp}"/>
<arg line= "-cp ${classpath}"/>
<arg line= "-factorypath ${ Build} "/>
<arg line="-factory org.qnot.ImmutableAnnotationProcessorFactory "/> <arg line=
" ${ Temp}/org/qnot/project.java "/>
<arg line=" ${temp}/org/qnot/immutableproject.java "/>
<arg Line= "${temp}/org/qnot/immutable.java"/>
<arg line= "-nocompile"/>
</exec>
I'll explain the last question below. The basic @Immutable implementation has one weakness: assuming that each setter method starts with "set." The actual class has the delete () and remove () methods, as well as other methods that do not need to be overwritten. Have you thought of any mechanism to solve this problem? If you have added annotations to each setter method, congratulations. Writing such annotations and adjusting the processor to identify it may be the first vivid experiment you can do with compile-time annotation processing.

Subsequent articles

In the next article in this series, the last article, I'll explain how the metadata annotations introduced by JSR-181 simplify the authoring and deployment of WEB services.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.