`
frank-liu
  • 浏览: 1683981 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java annotation基础

    博客分类:
  • java
 
阅读更多

简介

    之前有一篇简短的文章讨论过annotation的相关概念以及它的应用。annotation是java里一个比较有意思的特性。它本身相当于对代码中一种元数据的标记。在程序编译的时候,它和没有添加annotation的代码看起来没有多少差别。那么,annotation到底是用在哪里的呢?我们一般是怎么来处理它的呢?这里针对这几个方面进一步的讨论。

 

几个常见的annotation

    在讨论具体的annotation概念之前,我们在很多程序代码里已经接触过一些相关的annotation以及它的应用。一个典型的就是@Override。比如说当我们有如下的代码:

public class Employee {
    public void setSalary(double salary) {
        System.out.println("Employee.setSalary():" + salary);
    }
}

     如果我们要定义一个类Manager,继承类Employee:

public class Manager extends Employee {
    @Override
    public void setSalary(double salary) {
        System.out.println("Manager.setSalary():" + salary);
    }
}

     在子类里实现的方法setSalary前面多了一个@Override的标记。那么,这个东西有什么用呢?如果我们在代码里把这部分去掉,编译代码的时候没有任何问题。这么粗看起来好像标记的修饰没有什么用。但是我们再尝试一下将Manager里方法的签名稍微修改一下,比如将double类型参数改成int。然后再编译代码,将会发现出现如下的错误:

 

Manager.java:2: error: method does not override or implement a method from a supertype
    @Override
    ^

     从代码逻辑上来看,既然Manager类继承了Employee类,它必须要实现一个和父类签名相同的方法,如果没有的话,编译的时候会报错。所以这里@Override标记相当于告诉编译器对声明的继承方法进行签名检查。这样可以在编译的时候发现问题。

 

annotation基本概念

    annotation相当于是一个元数据层面的东西。它在编译的过程中会产生这些信息提供给后面的编译以及分析工具来使用。所以从这个角度来说,如果后续没有对相关annotation的分析和使用,有和没有它们编译之后的行为是没有任何变化的。

    那么该如何定义一个annotation呢?以前面使用过的@Override为例,它的定义如下:

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

     可见,我们定义一个annotation,它的基本形式如下:

[modifiers] @interface <annotation-type-name> {
    // Annotation type body
}

    前面是一个访问权限修饰,比如public, protected, private。后面就是我们定义的annotation名。比如Override。

    所以,按照这个基本定义,我们可以定义一个类似的annotation Version:

public @interface Version {
     int major();
     int minor();
}

     在这个annotation里定义了两个元素,major和minor。这里的定义major, minor看起来好像一个典型的方法,但是它本身只是表示里面的数值。在使用这个annotation的时候,我们使用如下形式的代码:

@Version(major=1, minior=0)

    可见,这里的两个属性只是元素值而不是某个特定的方法。通过这种方式来指定里面的元数据内容。

    基于上面的定义,我们可以在一些如下的地方使用Version这个标记。一段示例代码如下:

@Version(major=1, minor=0)
public class VersionTest {
    @Version(major=1, minor=0)
     private int xyz = 100;
     
     @Version(major=1, minor=0)
     public VersionTest() {}

     @Version(major=1, minor=0)
     public VersionTest(int xyz) {
         this.xyz = xyz;
     }

     @Version(major=1, minor=0)
     public void printData() {}

     @Version(major=1, minor=0)
     public void setXyz(int xyz) {
         @Version(major=1, minor=0)
         int newValue = xyz;
         this.xyz = xyz;
     }
}

     从这部分代码来看,我们可以将定义的annotation放到类、成员变量、构造函数、方法以及方法局部变量等地方。那么,对于annotation可以用到哪里的修饰限制,我们在后面会详细描述。

 

annotation类型定义限制

    因为annotation属于元数据定义类型,它的定义和使用和我们普通使用的数据类型不一样。所以,它有很多特殊的地方。这些也是我们在定义和使用它们的时候需要避免的。

1. annotation类型不能继承其他annotation类型

这个类型特殊的地方在于,我们不能把它当成一个普通的java数据类型,所以如果我们采用如下形式的代码:

public @interface ExtendedVersion extends BasicVersion {
    int extend();
}

     这部分代码在编译的时候是不能通过的。实际上,所有的annotation类型都是隐式的继承自java.lang.annotation.Annotation接口。这个接口的定义如下:

 

package java.lang.annotation;

public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

    这个接口里定义的方法前3个是属于Object对象的。而后面的这个方法由java提供的proxy对象在运行时动态的生成。所以我们实际定义的annotation里没有显式的实现这个接口的定义。

 

2. annotation类型里定义的方法不能有任何参数

    如果我们定义如下的代码:

public @interface WrongVersion {
    String concatenate(int major, int minor);
}

   这部分代码是不能编译的。为什么呢?因为annotation里定义的元素只是使得我们将一个数据值关联到某个具体的标记对象,而不是定义特殊的逻辑运算。我们可以将annotation里定义的所谓方法当成一个个的成员变量定义。只是它的定义形式有点不一样。

 

3. annotation里定义的方法不能有throws声明

   因此,按照如下方式写的代码是不能编译通过的:

 

public @interface WrongVersion {
    int major() throws Exception;
    int minor();
}

 

4. annotation里定义的方法返回类型必须是如下几种:

1) 基本类型,像byte, short, int, long, float, double, boolean, char.

2) java.lang.String

3) java.lang.Class

4)枚举类型

5) annotation类型

6) 上述类型的数组

 

5. annotation类型不能声明方法

6. annotation类型不能是泛型。

 

meta annotation

    在前面的代码示例里,还有几个比较特别的地方。就是我们看到定义@Override annotation的时候,里面居然还有@Target, @Retention等几个修饰的annotation。它们这种用来定义annotation的annotation就是元annotation。主要的几个我们都来过一下。

 

Target

Target主要用来描述这个annotation可以用到哪些地方。它本身的定义如下:

 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

    这里指定的ElementType数组用来描述它应用的地方。ElementType是一个枚举类型,它的详细定义如下:

 

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

    在java 9里还提供了对MODULE的支持。

    假设我们定义如下的annotation:

 

@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface Version {
    int major();
    int minor();
}

     在前面的定义里,它可以被应用到类型的定义,构造函数和方法上。所以我们可以在如下的代码里使用:

@Version(major = 1, minor = 0)
public class VersionTest {
	@Version(major = 1, minor = 0)
	public VersionTest() {
	}

	@Version(major = 1, minor = 1)
	public void doSomething() {
	}
}

 

Retention

    Retention主要用来描述这个annotation可以在哪个层面被访问。它本身的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

   描述它的访问范围被定义在枚举类型里RetentionPolicy里:

 

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

   从里面自带的注释就可以看到这三种不同应用范围的差别。具体采用哪种需要根据具体应用的需要。而且不同应用范围的选取也决定了后续处理方式的不一样。我们会在后面的部分详细描述。

 

Inherited

Inherited表示定义annotation使用的继承关系。它也是只能用来修饰annotation。它的定义如下:

 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

 

    如果一个annotation的类型有Inherited修饰,那么这个annotation对象修饰的对象如果有子类的话,这个子类也会继承这个annotation实例。我们可以看一个具体的示例。

    假如我们有如下的annotaion定义:

 

@Inherited
public @interface Ann3 {
    int id();
}

    假如我们声明两个类A和B:

 

@Ann3(id=707)
public class A {

}

 

public class B extends A {

}

    在这部分里,class B继承了@Ann3 annotation。

 

Documented

    Documented这个annotation相对比较简单,它主要是用来支持javadoc生成文档的。当我们用它修饰某个annotation的时候,这个annotation的实例在应用到某个地方的时候,它会被javadoc生成对应的文档内容。 

 

Repeatable

    还有一个比较常用的就是Repeatable。通过它修饰的annotation可以多次修饰目的对象。它的定义如下:

 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

 

当然,它的用法比较特殊一点。创建一个可重复的annotation需要两个步骤:

1. 定义一个annotation类型,并且用Repeatable修饰。同时在Repeatable里设置value为另外一个annotation。这个额外的annotation里包含有需要声明为可重复的annotation.

2. 声明这个包含的annotation里一个元素是可重复annotation的数组。

   一个详细的示例如下。比如我们定义一个ChangeLog的annotation:

 

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ChangeLogs.class)
public @interface ChangeLog {
	String date();
	String comments();
}

    这里声明了ChangeLog是可重复的。但是Repeatable里定义的value是ChangeLogs.class。这是包含annotation。

 

@Retention(RetentionPolicy.RUNTIME)
public @interface ChangeLogs {
	ChangeLog[] value();
}

    ChangeLogs里面的value方法返回的类型是一个ChangeLog的数组。这样,在程序里,我们可以来使用annotation ChangeLog:

 

@ChangeLog(date="08/28/2017", comments="Declared the class")
@ChangeLog(date="09/21/2017", comments="Added the process() method")
public class Test {
    public static void process() {

    }
}

     这样,我们就可以在程序里多次使用同一个annotation。

 

annotation processing

    前面讨论完了annotation的定义之后,还有一个需要关注的问题就是如果我们程序中应用上了这些annotation之后。我们该怎么来处理它们呢?像之前我们提到过的@Override, @Test等。既然它们编译的时候产生的字节码和没有这些的没什么区别。那么为什么在程序编译或者运行的时候它们这些定义的特性又会起作用呢?

     在之前的讨论里我们提到过,要定义annotation的有效范围,需要通过定义它的@Retention属性。这里定义了RetentionPolicy这个枚举类型。主要包括SOURCE, CLASS, RUNTIME这几种。其中SOURCE指的是将annotation编译的时候丢弃。但是在一些情况下它可以用来生成新的代码。而RUNTIME指的是在运行的时候

annotation将由编译器将它们加入到class文件里。在运行的时候可以通过反射的方式来访问。所以,我们再针对这两种情况的应用进一步讨论一下。

 

runtime level

    在runtime level的情况下,我们就是需要通过在运行时用反射的方式来访问class信息,然后做各种处理。我们来看一个示例。假设我们定义有两个annotation,一个Test, 一个TestInfo:

 

package com.yunzero;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface Test {
	public boolean enabled() default true;
}

 

 

package com.yunzero;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target({ TYPE })
public @interface TestInfo {
	public enum Priority {
		LOW, MEDIUM, HIGH
	}
	
	Priority priority() default Priority.MEDIUM;
	
	String[] tags() default "";
	
	String createdBy() default "frank";
	
	String lastModified() default "07/01/2018";
}

    这两个annotation的定义比较直接。它们都是用在runtime上。其中Test annotation被用在method上。也就是说它只能被用来修饰定义的方法。而TestInfo annotation可以用在type上。那么它可以被用在class定义上。除了这些应用范围的定义以外,我们还定义了一些元素属性。这些不过是用来描述这些annotation对象的信息。

   接着,我们再定义一个应用了这两个annotation的类:

 

package com.yunzero;

import com.yunzero.TestInfo.Priority;

@TestInfo(
	priority = Priority.HIGH,
	createdBy = "frank liu",
	tags = {"sales", "test"}
)
public class TestExample {
	@Test
	void testA() {
		if (true) {
			throw new RuntimeException("This test always failed");
		}
	}
	
	@Test(enabled = false)
	void testB() {
		if (false) {
			throw new RuntimeException("This test always passed");
		}
	}
	
	@Test(enabled = true)
	void testC() {
		if (10 > 1) {
			
		}
	}
}

    在这个类TestExample里,在它的类定义里,我们添加了TestInfo annotation。里面定义了priority, createdBy, tags信息。而里面的三个方法testA, testB, testC都被Test annotation修饰。从这里定义的样式来看,我们这里像是模拟了一个单元测试框架的用法。

    既然是runtime级别的分析处理,后面就是需要通过反射对运行时的对象进行分析了。对它们分析的代码过程如下:

 

package com.yunzero;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class App {
	
	public static void main(String[] args) {
		System.out.println("Testing...");

		int passed = 0, failed = 0, count = 0, ignore = 0;

		Class<TestExample> obj = TestExample.class;

		if (obj.isAnnotationPresent(TestInfo.class)) {
			TestInfo testerInfo = obj.getAnnotation(TestInfo.class);

			System.out.printf("%nPriority :%s", testerInfo.priority());
			System.out.printf("%nCreatedBy :%s", testerInfo.createdBy());
			System.out.printf("%nTags :");

			int tagLength = testerInfo.tags().length;
			for (String tag : testerInfo.tags()) {
				if (tagLength > 1) {
					System.out.print(tag + ". ");
				} else {
					System.out.print(tag);
				}
				tagLength--;
			}

			System.out.printf("%nLastModified :%s%n%n", testerInfo.lastModified());

			for (Method method : obj.getDeclaredMethods()) {

				// if method is annotated with @Test
				if (method.isAnnotationPresent(Test.class)) {

					Annotation annotation = method.getAnnotation(Test.class);
					Test test = (Test) annotation;

					// if enabled = true (default)
					if (test.enabled()) {

						try {
							method.invoke(obj.newInstance());
							System.out.printf("%s - Test '%s' - passed %n", ++count, method.getName());
							passed++;
						} catch (Throwable ex) {
							System.out.printf("%s - Test '%s' - failed: %s %n", ++count, method.getName(),
									ex.getCause());
							failed++;
						}

					} else {
						System.out.printf("%s - Test '%s' - ignored%n", ++count, method.getName());
						ignore++;
					}

				}

			}
			System.out.printf("%nResult : Total : %d, Passed: %d, Failed %d, Ignore %d%n", count, passed, failed,
					ignore);
		}
	}
}

    从这里可以看到,既然TestExample是Test和TestInfo两个annotation应用上的实体,那么,对它们的分析就必须从这个实体上来。从这个角度来看,annotation果然就像是一个修饰的东西,它本身不能作为一个单独的实体来使用。

    而这里分析使用情况的代码核心在于获取实体对象的class对象。也就是前面的Class<TestExample> obj = TestExample.class;这部分。其他的代码无非就是分析它是否有某些annotation以及通过反射的方式来调用某些方法并进行统计。

    运行上述的代码将的到如下的输出:

 

Testing...

Priority :HIGH
CreatedBy :frank liu
Tags :sales. test
LastModified :07/01/2018

1 - Test 'testA' - failed: java.lang.RuntimeException: This test always failed 
2 - Test 'testB' - ignored
3 - Test 'testC' - passed 

Result : Total : 3, Passed: 1, Failed 1, Ignore 1

   所以,总的来说,基于运行时的annotation分析主要就是通过反射来分析类里面的各种成员,然后针对性的进行处理。通过这个思路,我们也可以看到一些常用的测试框架,向JUnit也是采取类似的方式来进行处理的。

 

source level

    annotation的应用里,还有一个比较少见的用法,就是基于source level的处理。它的处理主要是通过它可以生成一些新的代码。在spring data等一些框架里就有用到。那么,这又是怎么实现的呢?

    这是因为,在java里,我们可以通过在编译的过程中指定一些annotation的processor。这样在编译的过程中编译器每编译一轮之后发现有新的文件生成就会将新的文件引入而开始新一轮的编译。也就是这个特性的应用使得我们在新代码的生成中的到应用。当然,这里也有一个限制,我们只能新生成代码而不能修改原有的代码。

     现在,我们来看一个示例。比如说我们看到定义的很多类里都需要定义一个toString的方法。但是给它们每个类专门去写这么一个toString的方法显得太繁琐。于是我们希望能够通过添加annotation的方式加到这些类上面去。然后再通过对annotation source级别的分析来生成它们对应的toString方法。那么,我们该怎么做呢?

    首先,我们需要定义标记toString方法的annotation:

package sourceAnnotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {
    boolean includeName() default true;    
}

     这部分定义里,我们专门标识了RetentionPolicy.SOURCE。表示它们是可以被应用到源代码级别的。它可以修饰到类和方法上。

    然后,我们再将这个annotation应用到几个类上面:

 

package rect;

import sourceAnnotations.ToString;

@ToString(includeName=false)
public class Point {
    private int x;
    private int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    @ToString(includeName=false) public int getX() { return x; }
    @ToString(includeName=false) public int getY() { return y; }
}

 

 

package rect;

import sourceAnnotations.ToString;

@ToString
public class Rectangle {
    private Point topLeft;
    private int width;
    private int height;
    public Rectangle(Point topLeft, int width, int height) {
        this.topLeft = topLeft;
        this.width = width;
        this.height = height;
    }
    @ToString(includeName=false) public Point getTopLeft() { return topLeft; }
    @ToString public int getWidth() { return width; }
    @ToString public int getHeight() { return height; }
}

     现在,如果我们采用如下的代码来运行程序的话,它是不能通过编译的:

 

package rect;

import sourceAnnotations.ToStrings;

public class SourceLevelAnnotationDemo
{
   public static void main(String[] args)
   {
      Rectangle rect = new Rectangle(new Point(10, 10), 20, 30);
      System.out.println(ToStrings.toString(rect));
   }
}

    在这部分代码里,引入了ToStrings类。但是在原有的包里是没有这个类的。因为这个类和对应的方法是需要通过我们的代码来生成。所以,现在唯一缺的就是我们生成上述ToStrings类的代码了。

    在java的javax.annotation.processing.*包里有一个AbstractProcessor类,如果我们需要生成新的代码,就需要通过继承它。继承它需要实现如下的方法:

 

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment currentRound) 

 

 我们详细实现的代码如下:

 

package sourceAnnotations;

import java.beans.*;
import java.io.*;
import java.util.*;

import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.tools.*;
import javax.tools.Diagnostic.Kind;

@SupportedAnnotationTypes("sourceAnnotations.ToString")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ToStringAnnotationProcessor extends AbstractProcessor 
{
   @Override
   public boolean process(Set<? extends TypeElement> annotations,
      RoundEnvironment currentRound) 
   {
      if (annotations.size() == 0) return true;
      try 
      {
         JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile("sourceAnnotations.ToStrings");
         try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) {
            out.println("// Automatically generated by sourceAnnotations.ToStringAnnotationProcessor");
            out.println("package sourceAnnotations;");
            out.println("public class ToStrings {");

            for (Element e : currentRound.getElementsAnnotatedWith(ToString.class)) {
               if (e instanceof TypeElement) 
               {
                  TypeElement te = (TypeElement) e;
                  writeToStringMethod(out, te);
               }
            }   
             out.println("    public static String toString(Object obj) {");
             out.println("        return java.util.Objects.toString(obj);");
             out.println("    }");
             out.println("}");
         }
      } 
      catch (IOException ex) 
      {
         processingEnv.getMessager().printMessage(Kind.ERROR, ex.getMessage());
      }        
      return true;
   }
    
   private void writeToStringMethod(PrintWriter out, TypeElement te) 
   {
      String className = te.getQualifiedName().toString();
      out.println("    public static String toString(" + className + " obj) {");
      ToString ann = te.getAnnotation(ToString.class);
      out.println("        StringBuilder result = new StringBuilder();");
      if (ann.includeName()) out.println("        result.append(\"" + className + "\");");        
      out.println("        result.append(\"[\");");
      boolean first = true;
      for (Element c : te.getEnclosedElements()) 
      {
         String methodName = c.getSimpleName().toString();
         ann = c.getAnnotation(ToString.class);
         if (ann != null) 
         {             
            if (first) first = false; else out.println("        result.append(\",\");");
            if (ann.includeName()) 
            {
               String fieldName = Introspector.decapitalize(methodName.replaceAll("^(get|is)", ""));
                  // Turn getWidth into width, isDone into done, getURL into URL 
               out.println("        result.append(\"" + fieldName + "=" + "\");"); 
            }
            out.println("        result.append(toString(obj." + methodName + "()));");
         }
      }
      out.println("        result.append(\"]\");");
      out.println("        return result.toString();");
      out.println("    }");
   }    
}

    在上述代码里,首先通过AbstractProcessor里的成员变量processingEnv来创建一个java源文件。然后再通过RoundEnvironment得到被标记的元素。如果是TypeElement,也就是我们前面定义的ToString修饰的类,那么调用writeToStringMethod.

    这些代码其实很大一部分是用来生成源文件内容的,显得比较繁琐一点而已。现在既然是生成源代码的代码弄好了。我们该怎么来编译和运行程序呢?

    肯定,我们首先应该编译这个生成ToStrings类的代码:

    

javac sourceAnnotations/ToStringAnnotationProcessor.java

    在编译完这部分代码之后,我们再编译使用到ToStrings类的代码:

 

javac -processor sourceAnnotations.ToStringAnnotationProcessor	rect/*.java

    这里,我们需要在命令行里指定processor,表示用它来处理annotation相关代码的生成。在执行完这部分代码之后,我们会发现sourceAnnotations目录下生成了一个ToStrings.java的文件,它的内容如下:

 

// Automatically generated by sourceAnnotations.ToStringAnnotationProcessor
package sourceAnnotations;
public class ToStrings {
    public static String toString(rect.Point obj) {
        StringBuilder result = new StringBuilder();
        result.append("[");
        result.append(toString(obj.getX()));
        result.append(",");
        result.append(toString(obj.getY()));
        result.append("]");
        return result.toString();
    }
    public static String toString(rect.Rectangle obj) {
        StringBuilder result = new StringBuilder();
        result.append("rect.Rectangle");
        result.append("[");
        result.append(toString(obj.getTopLeft()));
        result.append(",");
        result.append("width=");
        result.append(toString(obj.getWidth()));
        result.append(",");
        result.append("height=");
        result.append(toString(obj.getHeight()));
        result.append("]");
        return result.toString();
    }
    public static String toString(Object obj) {
        return java.util.Objects.toString(obj);
    }
}

     而且还有这个文件对应被编译后的class文件。这时候,我们再执行运行程序的命令:

 

java rect.SourceLevelAnnotationDemo

     将看到如下的输出:

 

rect.Rectangle[[10,10],width=20,height=30]

    这样,一个生成处理annotation代码的完整示例就完成了。

 

总结

     annotation在我们程序的应用中比较广泛,但是又显得很不起眼。它主要是在程序里添加一些元数据信息方便我们后续的程序去处理它们。这种方式从使用者的角度来说带来了很大的便利性。很多需要生成的繁琐的代码可以通过程序来生成处理。对于annotation的定义和处理主要是runtime和source两个级别的。它们有的用于测试框架的实现,有的用于一些新代码生成和使用。这些技术特点和细节还是值得学习和深究的。

 

 

参考材料

http://www.baeldung.com/java-annotation-processing-builder

http://www.mkyong.com/java/java-custom-annotations-example/

https://www.amazon.com/Core-Java-II-Advanced-Features-10th/dp/0134177290/ref=sr_1_5?s=books&ie=UTF8&qid=1529300087&sr=1-5&keywords=core+java

分享到:
评论

相关推荐

    Java Annotation手册

    《Java Annotation手册》 在Java编程语言中,注解(Annotation)是一种元数据,它提供了在编译时或运行时处理代码的一种方式。通过注解,开发者可以向编译器或JVM提供有关代码的附加信息,而这些信息通常不直接影响...

    JavaAnnotation手册[借鉴].pdf

    在《Java Annotation入门》的基础上,这篇手册深入探讨了Annotation的定义、使用和功能。 一、Annotation的本质 Annotation提供了一种附加信息到程序元素(如类、方法、变量等)的方式,这些信息被称为元数据。元...

    java1.5 annotation注释源代码

    Java 1.5 引入了一种新的元编程机制——注解(Annotation),极大地增强了代码的可读性和可维护性。注解是一种在代码中添加元数据的方式,它允许程序员在源代码上添加一些信息,这些信息可以被编译器或运行时环境...

    javaAnnotation.pdf

    Java Annotation 是一种在Java编程语言中引入的元数据机制,自J2SE 5.0开始可用,由JSR-175规范定义。注释的主要目的是为代码提供附加信息,这些信息可以用来生成文档、追踪依赖关系或执行编译时检查。元数据允许...

    Java注解学习1-Java Annotation认知(包括框架图、详细介绍、示例说明)

    1. Annotation接口:这是所有自定义注解的基础,它定义了一个基本的方法集,包括equals()、hashCode()、toString()和annotationType()。每个自定义注解都会继承这个接口,实现特定的行为。 2. ElementType枚举:它...

    通过Java annotation以及反射机制实现不同类型通用的数据库访问接口

    在Java编程中,注解(Annotation)和反射(Reflection)是两个非常强大的工具,它们能够帮助我们实现更灵活、更具动态性的代码。本篇将详细探讨如何利用这两者来实现一个通用的数据库访问接口,以适应不同类型的...

    Java基础之Annotation解读.docx

    Java注解(Annotation)是Java 5引入的一种元数据机制,用于向编译器、工具或运行时系统提供关于代码的附加信息。注解不是程序的一部分,不会直接影响代码的执行,但可以通过反射API来访问和处理这些信息,从而影响...

    Java基础之注解Annotation初入篇_刘永雷

    学习java自定义注解的小例子,处理运行时注解方法,可以结合博客学习,博客地址: http://blog.csdn.net/liuyonglei1314/article/details/59494503

    Java基础案例教程(第2版)_PPT.zip

    《Java基础案例教程(第2版)》是一本旨在教授初学者Java编程基础知识的教材,其PPT形式的压缩包提供了清晰、直观的教学材料。在深入探讨这些知识点之前,我们首先了解一下Java语言的基础概念。 Java是一种面向对象...

    Java基础尚硅谷宋红康学习笔记

    【Java基础】 Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems(现已被Oracle公司收购)于1995年发布。Java以其“一次编写,到处运行”的特性,成为跨平台应用开发的首选语言。Java的基础部分主要包括...

    java annotation入门

    Java 注解(Annotation)是 Java 语言中的一个重要特性,它为程序员提供了元数据,即关于数据的数据。在本文中,我们将深入理解 Java 注解的基本概念、用途、工作方式以及如何使用它们。 首先,让我们了解为什么...

    JAVA annotation入门基础

    Java 注解(Annotation)是 Java 语言中的一个重要特性,它允许程序员在源代码中嵌入元数据(metadata)。元数据是关于数据的数据,不直接影响程序的运行,但可以被编译器或运行时环境用来生成代码、执行验证、实现...

    java语言基础.zip

    Java语言基础是编程入门的重要部分,它涵盖了编程的基本概念、语法和编程思想。这份"java语言基础.zip"压缩包很可能是某内部培训机构的教学资料,旨在帮助初学者掌握Java编程的核心知识。下面,我们将深入探讨Java...

    Java基础Java基础Java基础Java基础.zip

    Java的基础知识是学习Java编程的起点,涵盖了许多关键概念和语法。 1. **Java环境设置**:在开始学习Java之前,你需要安装Java Development Kit (JDK)。JDK包含了Java编译器、Java运行时环境和其他开发工具。设置...

    Java编程基础PPT

    Java编程基础PPT是针对初学者的一份详细教程,涵盖了Java语言的核心概念和技术。这份PPT旨在帮助学习者建立坚实的Java编程基础,从而能够编写出高效、稳定的代码。以下是PPT可能涵盖的主要知识点: 1. **Java简介**...

    java 基础知识总结(经典)

    这篇“Java基础知识总结(经典)”涵盖了Java开发中的核心概念和重要知识点,旨在为初学者和有经验的开发者提供一个全面的回顾。以下是主要的学习点: 1. **Java环境配置**:在开始编程之前,必须安装Java ...

    java入门基础 李兴华教程

    这篇教程“java入门基础 李兴华教程”涵盖了多个关键概念,旨在帮助初学者掌握Java的核心知识。以下是对压缩包内各章节主要内容的详细解读: 1. **第10章:泛型** 泛型是Java SE 5.0引入的新特性,主要用于增强...

    计算机后端-Java-Java核心基础-第23章 枚举类与注解 11. Annotation的使用示例.avi

    计算机后端-Java-Java核心基础-第23章 枚举类与注解 11. Annotation的使用示例.avi

Global site tag (gtag.js) - Google Analytics