java中注解详解

什么是注解?

注解就是元数据,用于描述java代码的类、接口、方法、属性等等,然后应用程序可以根据注解做一些动作。比如Spring中的注解@Service,表明这是一个service类,然后Spring框架就会为其创建实例,并根据注解的参数来设置key值保存到ApplicationContext中,这些行为都是Spring框架来做的,注解只是提供元数据。详细可参见另一篇《动手写简单实现注解SpringMVC框架》。可以看到注解的作用就是给它打一个标识,表示这是一个Service类你们要处理它,另外提供一些参数,告诉你怎么处理它。注解的作用就跟xml配置文件一样。

为什么要引入注解?

使用Annotation之前(还有之后),xml被广泛应用于描述元数据。不知何时开始一些应用开发人员与架构师发现xml的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像xml那样松耦合的代码描述。如果你在Google中搜索“xml vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是xml配置其实就是为了分离代码和配置而引入的。上述两个观点可能会让你很疑惑,两者观点似乎构成一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。

假如你想为应用设置很多常量后参数,这种情况下,xml是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用annotation会更好一点,因为这种情况下需要注解和方法紧密耦合起来。

目前许多框架将xml和annotation两种方式结合使用,平衡两者之间的利弊。

扫描二维码关注公众号,回复: 253978 查看本文章

注解的使用

java现在内置三个标准注解

@Override,表示当前方法定义将覆盖超类中的方法

@Deprecated 表示不赞成使用的代码

@SuppressWarnings 关闭不当编译器警告信息

java另外还提供四种注解,专门负责新注解的创建:

@Target :表示该注解可以用于什么地方,可能的ElementType参数有:

                     CONSTRUCTOR:构造器的声明

                     FIELD:域声明(包括enum实例)

                     LOCAL_VARIABLE:局部变量声明

                     METHOD:方法声明

                     PACKAGE:包声明

                     PARAMETER:参数声明

                     TYPE:类、接口(包括注解类型)或enum声明

@Retention:表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:

                     SOURCE:注解将被编译器丢弃

                     CLASS:注解在class文件中可用,但会被VM丢弃

                     RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。

@Document:将注解包含在Javadoc中

@Inherited:允许子类继承父类中的注解

定义一个注解的方式:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
  
}

 在注解中一般会有一些元素表示某些值,注解的元素看起来就像接口的方法,唯一的区别在于可以为其定义默认值。使用时元素不能有不确定值,即要么有默认值,要么使用时提供元素的值,且不能用null作为默认值。注解在只有一个元素且元素名为value的情况下,使用注解可以省略“value=”,直接写值即可。

下面定义一个元素

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno{
      public String description() default "no description";
}

 然后使用注解

 public class UseAnno{
      @UseCase
     public void userAnnoMethod() {
         
     }

 然后处理注解,注解的处理就是通过反射机制获取注解信息,然后根据注解元素的值进行特定的处理

public static void main(String[] args){
   Method method = UseAnno.class.getMethod("useAnnoMethod");
   MyAnno anno = method.getAnnotation(MyAnno.class);
   System.out.println(method.description()); //打印出“no description”
}

注解的原理

java中的注解是一种继承自接口`java.lang.annotation.Annotation`的特殊接口。

下面看一下具体示例。

定义一个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
	int count() default 1;
}

 经过编译之后的字节码是这样的

Classfile /D:/platform/com.cq.platform.base/com.cq.platform.base.test/target/classes/com/cq/platform/base/test/annotation/MyAnno.class
  Last modified 2017-12-21; size 456 bytes
  MD5 checksum 3731139c896044ddb4f7dfc3d7092402
  Compiled from "MyAnno.java"
public interface com.cq.platform.base.test.annotation.MyAnno extends java.lang.annotation.Annotation
  SourceFile: "MyAnno.java"
  RuntimeVisibleAnnotations:
    0: #14(#15=[e#16.#17])
    1: #18(#15=e#19.#20)
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION

Constant pool:
   #1 = Class              #2             //  com/cq/platform/base/test/annotation/MyAnno
   #2 = Utf8               com/cq/platform/base/test/annotation/MyAnno
   #3 = Class              #4             //  java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Class              #6             //  java/lang/annotation/Annotation
   #6 = Utf8               java/lang/annotation/Annotation
   #7 = Utf8               count
   #8 = Utf8               ()I
   #9 = Utf8               AnnotationDefault
  #10 = Integer            1
  #11 = Utf8               SourceFile
  #12 = Utf8               MyAnno.java
  #13 = Utf8               RuntimeVisibleAnnotations
  #14 = Utf8               Ljava/lang/annotation/Target;
  #15 = Utf8               value
  #16 = Utf8               Ljava/lang/annotation/ElementType;
  #17 = Utf8               TYPE
  #18 = Utf8               Ljava/lang/annotation/Retention;
  #19 = Utf8               Ljava/lang/annotation/RetentionPolicy;
  #20 = Utf8               RUNTIME
{
  public abstract int count();
    flags: ACC_PUBLIC, ACC_ABSTRACT

    AnnotationDefault:
      default_value: I#10}

 从反编译后的信息可以看出,注解就是一个继承自“java.lang.annotation.Annotation”的接口

那么接口怎么能够设置属性呢?简单来说就是java通过动态代理的方式为你生成一个实现了“接口”‘MyAnno’的实例(对于当前的实体来说,例如类、方法、属性域等,这个代理对象是单例的),然后对该代理实例的属性赋值,这样就可以在程序运行时(如果注解设置为运行时可见的话)通过放射获取得配置信息。

具体怎么实现呢?

写一个使用该注解的类

@MyAnno(count=2)
public class TestMain {
	public static void main(String[] args) {
		MyAnno anno = TestMain.class.getAnnotation(MyAnno.class);
		int b = anno.count();
	}
}

 反编译代码

Classfile /D:/platform/com.cq.platform.base/com.cq.platform.base.test/target/classes/com/cq/platform/base/test/annotation/TestMain.class
  Last modified 2017-12-21; size 900 bytes
  MD5 checksum 82c13adc58b44ced5b948006f28c0f15
  Compiled from "TestMain.java"
public class com.cq.platform.base.test.annotation.TestMain
  SourceFile: "TestMain.java"
  RuntimeVisibleAnnotations:
    0: #43(#26=I#49)
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER

Constant pool:
   #1 = Class              #2             //  com/cq/platform/base/test/annotation/TestMain
   #2 = Utf8               com/cq/platform/base/test/annotation/TestMain
   #3 = Class              #4             //  java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          //  java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          //  "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/cq/platform/base/test/annotation/TestMain;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Class              #17            //  com/cq/platform/base/test/annotation/MyAnno
  #17 = Utf8               com/cq/platform/base/test/annotation/MyAnno
  #18 = Methodref          #19.#21        //  java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
  #19 = Class              #20            //  java/lang/Class
  #20 = Utf8               java/lang/Class
  #21 = NameAndType        #22:#23        //  getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
  #22 = Utf8               getAnnotation
  #23 = Utf8               (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
  #24 = InterfaceMethodref #16.#25        //  com/cq/platform/base/test/annotation/MyAnno.count:()I
  #25 = NameAndType        #26:#27        //  count:()I
  #26 = Utf8               count
  #27 = Utf8               ()I
  #28 = Fieldref           #29.#31        //  java/lang/System.out:Ljava/io/PrintStream;
  #29 = Class              #30            //  java/lang/System
  #30 = Utf8               java/lang/System
  #31 = NameAndType        #32:#33        //  out:Ljava/io/PrintStream;
  #32 = Utf8               out
  #33 = Utf8               Ljava/io/PrintStream;
  #34 = Methodref          #35.#37        //  java/io/PrintStream.println:(I)V
  #35 = Class              #36            //  java/io/PrintStream
  #36 = Utf8               java/io/PrintStream
  #37 = NameAndType        #38:#39        //  println:(I)V
  #38 = Utf8               println
  #39 = Utf8               (I)V
  #40 = Utf8               args
  #41 = Utf8               [Ljava/lang/String;
  #42 = Utf8               anno
  #43 = Utf8               Lcom/cq/platform/base/test/annotation/MyAnno;
  #44 = Utf8               b
  #45 = Utf8               I
  #46 = Utf8               SourceFile
  #47 = Utf8               TestMain.java
  #48 = Utf8               RuntimeVisibleAnnotations
  #49 = Integer            2
{
  public com.cq.platform.base.test.annotation.TestMain();
    flags: ACC_PUBLIC

    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return        
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   Lcom/cq/platform/base/test/annotation/TestMain;

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC

    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #1                  // class com/cq/platform/base/test/annotation/TestMain
         2: ldc           #16                 // class com/cq/platform/base/test/annotation/MyAnno
         4: invokevirtual #18                 // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
         7: checkcast     #16                 // class com/cq/platform/base/test/annotation/MyAnno
        10: astore_1      
        11: aload_1       
        12: invokeinterface #24,  1           // InterfaceMethod com/cq/platform/base/test/annotation/MyAnno.count:()I
        17: istore_2      
        18: getstatic     #28                 // Field java/lang/System.out:Ljava/io/PrintStream;
        21: iload_2       
        22: invokevirtual #34                 // Method java/io/PrintStream.println:(I)V
        25: return        
      LineNumberTable:
        line 5: 0
        line 6: 11
        line 7: 18
        line 8: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      26     0  args   [Ljava/lang/String;
              11      15     1  anno   Lcom/cq/platform/base/test/annotation/MyAnno;
              18       8     2     b   I
}

  RuntimeVisibleAnnotations: 0:#43(#26=I#49)

可以看到注解“MyAnno”的属性设置是在编译时就确定了的。

然后可以在项目的com/sun/proxy包下看到代理类““$Proxy1.class”。其中‘com.sun.proxy’是jdk动态代理生成对象是的默认包名。可以看到这个代理类继承自“java.lang.reflect.Proxy”类,又实现了“接口”MyAnno。

文章来自:http://www.importnew.com/10294.html

https://www.cnblogs.com/huajiezh/p/5263849.html

https://www.zhihu.com/question/24401191曹旭东的回答

猜你喜欢

转载自xiaoxiaoher.iteye.com/blog/2405490