java注解的实现和工作原理

来源:互联网 发布:美萍预算软件 编辑:程序博客网 时间:2024/04/29 15:13

      spring的配置有两种主流方式:1 配置文件 2 注解 相信很多人都想知道注解的核心工作原理的 所以花了一天的时间整理了一下注解是如何工作,文章有点长,耐心看完还是很有收获的。

注解(Annotation)

1、Annotation的工作原理:

JDK5.0中提供了注解的功能,允许开发者定义和使用自己的注解类型。该功能由一个定义注解类型的语法和描述一个注解声明的语法,读取注解的API,一个使用注解修饰的class文件和一个注解处理工具组成。

Annotation并不直接影响代码的语义,但是他可以被看做是程序的工具或者类库。它会反过来对正在运行的程序语义有所影响。

Annotation可以从源文件、class文件或者在运行时通过反射机制多种方式被读取。

常见的作用

有以下几种:

1,生成文档。这是最常见的,也是java 最早提供的注解。

  常用的有@see@param @return 等

2,跟踪代码依赖性,实现替代配置文件功能。

  比较常见的是spring2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都 使用了这种配置来减少配置文件的数量。

3,在编译时进行格式检查。

  如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查 出。

 

*@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。

*方法的名称就是参数的名称,返回值类型就是参数的类型。

*可以通过default来声明参数的默认值。

 

父类,接口

Java.lang.annotation

包java.lang.annotation 中包含所有已定义或自定义注解所需用到的原注解和接口。如接口 java.lang.annotation.Annotation 是所有注解继承的接口,并且是自动继承,不需要定义时指定,类似于所有类都自动继承Object。

该包同时定义了四个元注解;

 

常见注解

@Override注解:

注释类型 Override

@Target(value=METHOD)

@Retention(value=SOURCE)

public@interface Override

 

@Override注解表示子类要重写父类的对应方法。

下面是一个使用@Override注解的例子:

class A {

    private String id;

    A(String id){

        this.id = id;

    }

    @Override

    public String toString() {

        return id;

    }

}

 

@Deprecated注解:

注释类型Deprecated

@Documented

@Retention(value=RUNTIME)

public @interface Deprecated

@Deprecated注解表示方法是不被建议使用的。

下面是一个使用@Deprecated注解的例子:

class A {

   private String id;

   A(String id){

       this.id = id;

    }

   @Deprecated

   public void execute(){

       System.out.println(id);

    }

   public static void main(String[] args) {

       A a = new A("a123");

       a.execute();

    }

}

 

@SuppressWarnings注解:

注释类型SuppressWarnings

@Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})

@Retention(value=SOURCE)

public @interface SuppressWarnings

指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。注意,在给定元素中取消显示的警告集是所有包含元素中取消显示的警告的超集。例如,如果注释一个类来取消显示某个警告,同时注释一个方法来取消显示另一个警告,那么将在此方法中同时取消显示这两个警告。

下面是一个使用@SuppressWarnings注解的例子:

@SuppressWarnings("unchecked")

public static void main(String[] args) {

   List list = new ArrayList();

   list.add("abc");

}

 

自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

 

1,自定义最简单的注解:

public@interface MyAnnotation {

 

}

使用自定义注解:

public classAnnotationTest2 {

 

    @MyAnnotation

    public void execute(){

        System.out.println("method");

    }

}

2,添加变量:

public@interface MyAnnotation {

    String value1();

}

使用自定义注解:

public classAnnotationTest2 {

 

    @MyAnnotation(value1="abc")

    public void execute(){

        System.out.println("method");

    }

}

添加默认值:

public@interface MyAnnotation {

    String value1() default "abc";

}

3、多变量使用枚举:

public@interface MyAnnotation {

    String value1() default "abc";

    MyEnum value2() default MyEnum.Sunny;

}

enum MyEnum{

    Sunny,Rainy

}

使用自定义注解:

public classAnnotationTest2 {

 

    @MyAnnotation(value1="a",value2=MyEnum.Sunny)

    public void execute(){

        System.out.println("method");

    }

}

4、数组变量:

public@interface MyAnnotation {

 

    String[] value1() default "abc";

}

使用自定义注解:

public classAnnotationTest2 {

 

   @MyAnnotation(value1={"a","b"})

    public void execute(){

        System.out.println("method");

    }

}

 

 

当注解中使用的属性名为value时,对其赋值时可以不指定属性的名称而直接写上属性值接口;除了value意外的变量名都需要使用name=value的方式赋值。

 

5、限定注解的使用:

限定注解使用@Target。

@Documented

@Retention(value=RUNTIME)

@Target(value=ANNOTATION_TYPE)

public@interface Target

指示注释类型所适用的程序元素的种类。如果注释类型声明中不存在 Target 元注释,则声明的类型可以用在任一程序元素上。如果存在这样的元注释,则编译器强制实施指定的使用限制。例如,此元注释指示该声明类型是其自身,即元注释类型。它只能用在注释类型声明上:

@Target(ElementType.ANNOTATION_TYPE)

    public @interface MetaAnnotationType {

        ...

    }

此元注释指示该声明类型只可作为复杂注释类型声明中的成员类型使用。它不能直接用于注释:

@Target({})

    public @interface MemberType {

        ...

    }

这是一个编译时错误,它表明一个 ElementType 常量在 Target 注释中出现了不只一次。例如,以下元注释是非法的:

@Target({ElementType.FIELD,ElementType.METHOD, ElementType.FIELD})

    public @interface Bogus {

        ...

    }

public enumElementType

extendsEnum<ElementType>

程序元素类型。此枚举类型的常量提供了 Java 程序中声明的元素的简单分类。

这些常量与 Target 元注释类型一起使用,以指定在什么情况下使用注释类型是合法的。

ANNOTATION_TYPE

注释类型声明

CONSTRUCTOR

构造方法声明

FIELD

字段声明(包括枚举常量)

LOCAL_VARIABLE

局部变量声明

METHOD

方法声明

PACKAGE

包声明

PARAMETER

参数声明

TYPE

类、接口(包括注释类型)或枚举声明


注解的使用限定的例子:

@Target(ElementType.METHOD)

public@interface MyAnnotation {

 

    String[] value1() default "abc";

}

 

 

元注解

元注解是指注解的注解。包括  @Retention @Target @Document @Inherited四种。

@Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括:               @Target(ElementType.TYPE)   //接口、类、枚举、注解

  @Target(ElementType.FIELD) //字段、枚举的常量

  @Target(ElementType.METHOD) //方法

  @Target(ElementType.PARAMETER) //方法参数

  @Target(ElementType.CONSTRUCTOR) //构造函数

  @Target(ElementType.LOCAL_VARIABLE)//局部变量

  @Target(ElementType.ANNOTATION_TYPE)//注解

      @Target(ElementType.PACKAGE) //包 packag注解必须在package-info.java 中声明

  

 

@Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy                    中,包括:         

 @Retention(RetentionPolicy.SOURCE)  

  //注解仅存在于源码中,在class字节码文件中不包含

    @Retention(RetentionPolicy.CLASS) 

   // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,

         @Retention(RetentionPolicy.RUNTIME)

   // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

@Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文                   档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与                            @see,@param 等。       @Inherited 允许子类继承父类中的注解

 

@Target

@Target用来声明注解可以被添加在哪些类型的元素上,如类型、方法和域等。

 例:

@Target({TYPE,METHOD,FIELD,CONSTRUCTOR})

public @interface TestA {

  //这里定义了一个空的注解。

  }

   这个类专门用来测试注解使用

@TestA   //使用了类注解

public class UserAnnotation {

     

      @TestA //使用了类成员注解

      private Integer age;

      

      @TestA //使用了构造方法注解

      public UserAnnotation(){

         

      }

      @TestA //使用了类方法注解

      public void a(){

          @TestA //使用了局部变量注解

          Map m = new HashMap(0);

      }

     

  @TestA //使用了方法参数注解

      public void b(@TestA Integer a){       

      }

  }

  

  

@Retention

  @Retention用来声明注解的保留策略,有CLASS、RUNTIME和SOURCE这三种,

  分别表示注解保存在类文件、JVM运行时刻和源代码中。

                只有当声明为RUNTIME的时候,才能够在运行时刻通过反射API来获取到注解的信息。

无属性注解

  @Retention 参数 RetentionPolicy。这个注解还没有特殊的属性值。 简单演示下如何使用:

 

  importjava.lang.annotation.ElementType;

  importjava.lang.annotation.Target;

  /*

  * 定义注解 Test

  * 首先使用ElementType.TYPE(需要在package-info.java中声明)

  * 运行级别定为 运行时,以便后面测试解析

   */

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface TestA {}

有属性注解

@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

代码:

/*

* 定义注解 Test

* 为方便测试:注解目标为类方法,属性及构造方法

* 注解中含有三个元素 id ,name和 gid;

* id 元素有默认值 0

*/

@Target({TYPE,METHOD,FIELD,CONSTRUCTOR})

@Retention(RetentionPolicy.RUNTIME)

public @interface TestA {

  String name();

  int id() default 0;

  Class<Long> gid();}

  

测试类

import java.util.HashMap;

import java.util.Map;

 

/**

* 这个类专门用来测试注解使用

*/

 

@TestA(name="type",gid=Long.class)//类成员注解

public class UserAnnotation {

 

@TestA(name="param",id=1,gid=Long.class)//类成员注解

  privateInteger age;

 

@TestA(name="construct",id=2,gid=Long.class)//构造方法注解

  publicUserAnnotation(){}

@TestA(name="publicmethod",id=3,gid=Long.class) //类方法注解

  public voida(){

  Map<String,String>m = new HashMap<String,String>(0);

  }

 

@TestA(name="protectedmethod",id=4,gid=Long.class) //类方法注解

  protectedvoid b(){

  Map<String,String>m = new HashMap<String,String>(0);

  }

 

@TestA(name="privatemethod",id=5,gid=Long.class) //类方法注解

  private voidc(){

  Map<String,String>m = new HashMap<String,String>(0);

  }

 

  public voidb(Integer a){ }

}

读取定义注解内容

import java.lang.annotation.Annotation;

import java.lang.reflect.Constructor;

import java.lang.reflect.Method;

 

public class ParseAnnotation {

 

/**

*A 简单打印出UserAnnotation 类中所使用到的类注解

* 该方法只打印了 Type 类型的注解

* @throws ClassNotFoundException

*/

  publicstatic void parseTypeAnnotation() throws ClassNotFoundException {

  Class clazz =Class.forName("com.tmser.annotation.UserAnnotation");

 

  Annotation[] annotations = clazz.getAnnotations();

  for (Annotation annotation : annotations) {

  TestA testA = (TestA)annotation;

System.out.println("id=\""+testA.id()+"\"; name=\""+testA.name()+"\"; gid = "+testA.gid());

  }

}

//结果为id="0";name="type";gid=classjava.lang.Long

/*

* B简单打印出UserAnnotation 类中所使用到的方法注解

* 该方法只打印了 Method 类型的注解

* @throws ClassNotFoundException

*/

  publicstatic void parseMethodAnnotation(){

  Method[] methods = UserAnnotation.class.getDeclaredMethods();

  for (Method method : methods) { 

  // 判断方法中是否有指定注解类型的注解

  booleanhasAnnotation = method.isAnnotationPresent(TestA.class);

  if (hasAnnotation) {

  //根据注解类型返回方法的指定类型注解

  TestA annotation = method.getAnnotation(TestA.class);

  System.out.println("method = " + method.getName()

  + " ; id = " + annotation.id() + " ; description ="

  + annotation.name() + "; gid= "+annotation.gid());

  }

  }

  }

method = c ; id = 5 ; description = privatemethod; gid= class java.lang.Long

method = a ; id = 3 ; description = public method;gid= class java.lang.Long

method = b ; id = 4 ; description = protectedmethod; gid= class java.lang.Long

 

/**

* C简单打印出UserAnnotation 类中所使用到的构造方法注解

* 该方法只打印了 Method 类型的注解

* @throws ClassNotFoundException

*/

public static void parseConstructAnnotation(){

  Constructor[]constructors = UserAnnotation.class.getConstructors();

  for(Constructor constructor : constructors) {

//判断构造方法中是否有指定注解类型的注解

  booleanhasAnnotation = constructor.isAnnotationPresent(TestA.class);

  if(hasAnnotation) {

//根据注解类型返回方法的指定类型注解

  TestA annotation =(TestA) constructor.getAnnotation(TestA.class);

  System.out.println("constructor = " +constructor.getName()

  + " ; id = " + annotation.id() + " ; description ="

  + annotation.name() + "; gid= "+annotation.gid());

  }

  }

  }

constructor=com.tmser.annotation.UserAnnotation;id=2;description=construct;gid=classjava.lang.Long

 

public static void main(String[] args)throws ClassNotFoundException {

  parseTypeAnnotation();

  parseMethodAnnotation();

  parseConstructAnnotation();//              field是一样的,省略之

  }

}

 

@Documented

在帮助文档中加入注解:

要想在制作JavaDoc文件的同时将注解信息加入到API文件中,可以使用java.lang.annotation.Documented。

在自定义注解中声明构建注解文档:

@Documented

public @interface MyAnnotation {

 

   String[] value1() default "abc";

}

 

使用自定义注解:

public class AnnotationTest2 {

 

   @MyAnnotation(value1={"a","b"})

   public void execute(){

       System.out.println("method");

    }

}

   

@Inherited

在注解中使用继承:

默认情况下注解并不会被继承到子类中,可以在自定义注解时加上java.lang.annotation.Inherited注解声明使用继承。

@Documented

@Retention(value=RUNTIME)

@Target(value=ANNOTATION_TYPE)

public @interface Inherited

指示注释类型被自动继承。如果在注释类型声明中存在 Inherited 元注释,并且用户在某一类声明中查询该注释类型,同时该类声明中没有此类型的注释,则将在该类的超类中自动查询该注释类型。此过程会重复进行,直到找到此类型的注释或到达了该类层次结构的顶层 (Object) 为止。如果没有超类具有该类型的注释,则查询将指示当前类没有这样的注释。

注意,如果使用注释类型注释类以外的任何事物,此元注释类型都是无效的。还要注意,此元注释仅促成从超类继承注释;对已实现接口的注释无效。

 

 

 

 

 

 

自定义注解案例

实例1:

下面是使用反射读取RUNTIME保留策略的Annotation信息的例子:


自定义注解:

@Retention(RetentionPolicy.RUNTIME)

public @interface MyAnnotation {

    String[] value1() default "abc";

}

 

 

使用自定义注解:

public class AnnotationTest2 { 

  @MyAnnotation(value1={"a","b"})

    @Deprecated

    public void execute(){

        System.out.println("method");

    }

}

 

 

读取注解中的信息:

public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {

    AnnotationTest2 annotationTest2 = new AnnotationTest2();

    //获取AnnotationTest2的Class实例

    Class<AnnotationTest2> c = AnnotationTest2.class;

    //获取需要处理的方法Method实例

    Method method = c.getMethod("execute", new Class[]{});

    //判断该方法是否包含MyAnnotation注解

    if(method.isAnnotationPresent(MyAnnotation.class)){

        //获取该方法的MyAnnotation注解实例

        MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);

        //执行该方法

        method.invoke(annotationTest2, new Object[]{});

        //获取myAnnotation

        String[] value1 = myAnnotation.value1();

        System.out.println(value1[0]);

    }

    //获取方法上的所有注解

    Annotation[] annotations = method.getAnnotations();

    for(Annotation annotation : annotations){

        System.out.println(annotation);

    }

}

 

 

 

 

实例2:

@Documented

@Target({ElementType.TYPE,ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

public @interface Yts {

    public enum YtsType{util,entity,service,model};

    public YtsType classType() default YtsType.util;

}

 

@Documented

 @Retention(RetentionPolicy.RUNTIME)

 @Target(ElementType.METHOD)

 @Inherited

 public @interface HelloWorld {

    public String name()default "";

 }

 

public class ParseAnnotation {

      public void parseMethod(Class clazz) throws IllegalArgumentException, IllegalAccessException,InvocationTargetException,SecurityException, NoSuchMethodException, InstantiationException{

   Object obj = clazz.getConstructor(new Class[]{}).newInstance(new Object[]{});

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

         HelloWorld say = method.getAnnotation(HelloWorld.class);

         String name = "";

         if(say != null){

            name = say.name();

            method.invoke(obj, name);

         }

        Yts yts = (Yts)method.getAnnotation(Yts.class);

        if(yts != null){

           if(YtsType.util.equals(yts.classType())){

           System.out.println("this is a util method");

         }else{

             System.out.println("this is a other method");

             }

         }

       }

     }

 

 

     @SuppressWarnings("unchecked")

     public void parseType(Class clazz) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{

         Yts yts = (Yts) clazz.getAnnotation(Yts.class);

         if(yts != null){

             if(YtsType.util.equals(yts.classType())){

                 System.out.println("this is a util class");

             }else{

                 System.out.println("this is a other class");

             }

         }

     }

    

 }

@Yts(classType =YtsType.util)

 public class SayHell {

 

     @HelloWorld(name = " 小明 ")

     @Yts

     public void sayHello(String name){

         if(name == null || name.equals("")){

             System.out.println("hello world!");

         }else{

             System.out.println(name + "say hello world!");

         }

     }

 }

 

public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, InstantiationException {

         ParseAnnotation parse = new ParseAnnotation();

         parse.parseMethod(SayHell.class);

         parse.parseType(SayHell.class);

     }

  

总结

 

1. 要用好注解,必须熟悉java 的反射机制,从上面的例子可以看出,注解的解析完全依赖于反射。

2. 不要滥用注解。平常我们编程过程很少接触和使用注解,只有做设计,且不想让设计有过多的配置时。

 


1 0