注解(Annotation)

  • Post author:
  • Post category:其他




注解的概述

从JDK1.5开始,Java增加了对元数据的支持,也就是Annotation(注解),它是一种元数据形式。属于Java中的一种数据类型,其地位和类、接口、数组、枚举这些都是一样的。

简单理解注解其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation可以在不改变原有逻辑的情况下,在源文件中加入一些补充信息,代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。



注解和注释的区别

  • 注释:简单来说注释是对代码的解释和说明类似于使用说明,其目的是提高代码的可读性。它只存在于我们的java源代码中,对于编译和运行没有任何的作用,是不会被编译到我们的class文件当中的

  • 注解:注解主要是通过标注包、类、字段、方法、局部变量、方法参数等元素,告诉JVM这些元素的附加信息。它可以通过配置,在运行中让JVM去读取它,并完成对应的操作,一般我们会通过反射来获取某个元素上标注的注解。

  • 最大的区别在于注解可以被编译器打包进class文件而注释不行,总结一句话就是注释是给我们人看的,而注解则是给编译器和JVM看的。

例如我们书写如下代码:

   public class Test{
   public string toString(int x){
        return "我还没有秃"}

这个示例中我们想重写Test类的toString()方法,那么toString()方法被重写了吗?

答案肯定是否定的,因为Object定义的toString()方法是一个无参方法,这种写法属于是方法的重载。编译器会认为这是一个全新的方法,故而也不会报错。

这时我们如果在toString方法前,加上一个@Override注解后,编译就会发生错误。

  public class Test{
  @Override
   public string toString(int x){
        return "我还没有秃"}

这是为什么呢?

因为加上@Override注解之后,编译器会强制检测该方法是否重写了父类方法。如果没有,则编译错误。那么从这个例子中我们就能看出,@Override这个Annotation注解,不仅仅是给人读的,同样还会影响到编译器。可以看到注解Annotation会在编译期,影响到编译器的编译动作。所以注解Annotation不是注释。



注解的分类



注解语法支持两种注解:

  • JDK自带的Annotation注解
  • 开发者自定义的Annotation注解



– 常见的Annotation注解

  • 常见的Annotation示例:

    • 示例一:生成文档相关的注解
注解 作用
@author 标明开发该类模版的作者,多个作者之间使用,分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception 对方法可能抛出的异常进行说明,如果方法没有用thorws显示抛出的异常就不能写

其中:

  • @param、@return 和@exception 这三个标记都是只用于方法的。
  • @param的格式要求:@param形参名 形参类型 形参说明
  • @return的格式要求:@return 返回值类型 返回值说明
  • @exception的格式要求:@exception 异常类型 异常说明
  • @param和@exception可以并列多个



代码演示:

/**
* @author 琦玉
* @version 1.0
*/
public class Test{

   /**
    * 程序的主方法,程序的入口
    * @param args String[] 命令行参数
    * @see Student
    */
   public static void main(String[] args) {
       new Student();
   }

   /**
    *
    * @param age 年龄
    * @return String
    * @exception RuntimeException 当年龄大于100的时候
    * @exception IndexOutOfBoundsException 当年龄小于0的时候
    */
   public String play(int age){
       if (age>100){
           throw new RuntimeException();
       }
       if (age<0){
           throw new IndexOutOfBoundsException();
       }
       return “变秃”;
   }
}

}
    • 示例二:在编译时进行格式检查(JDK内置的三个基本注解)

      • @Override:限定重写父类方法,对于子类中被@Override修饰的方法,如果存在对应的被重写方法,则编译通过;如果不存在,则编译报错。

        • @Override只能用于方法,不能作用于其他程序元素。
      • @Deprecated:用于表示某个程序元素(类、方法)已过时。如果使用被@Deprecated修饰的类或方法等,编译器会发出警告。

        • 需注意:过时的类或方法并不代表错误,只是有了更好的替代方法,而不再推荐使用。
      • @SuppressWarnings:抑制编译器警告,指示被@SuppressWarnings修饰的程序元素(以及该程序元素中的所有子元素,例如类以及该类中的方法……)取消显示的编译器警告。

        • @SuppressWarnings抑制的警告类型:
关键字 被抑制的警告类型
all 抑制所有警告
deprecation 抑制使用了过时的类或方法的警告
fallthrough 抑制switch块中没有break语句警告
finally 抑制finally块不能正常完成的警告
rawtypes 抑制没有使用泛型的警告
serial 抑制可序列化类没有使用序列化ID的警告
unchecked 抑制未检查操作的警告
unused 抑制变量或方法申明定义后未使用的警告



代码演示:

public class AnnotationTest {
    public static void main(String[] args) {
        @SuppressWarnings("unused") int a = 10;
    }

    @Deprecated
    public void print() {
        System.out.println(" 过时的方法");
    }

    @Override
    public String toString() {
        return " 重写的 toString 方法() " ;
    }
}
    • 示例三:跟踪代码依赖性,实现替代配置文件功能
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
  • 示例四:spring框架中关于“事务”的管理
@Transactional(propagation = Propagation.REQUIRES_NEW, 
        isolation=Isolation.READ_COMMITTED, readOnly = false, timeout = 3) 
        public void buyBook(String username,String isbn){
//1.查询书的单价 
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存 
 bookShopDao.updateBookStock(isbn);
//3.更新用户的余额 
bookShopDao.updateUserAccount(username,price);
  }
  
<!-- 配置事务属性 -->
<tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
   <tx:attributes>
   <!-- 配置每个方法使用的事务属性 -->
    <tx:method name="buyBook" propagation="REQUIRES_NEW" 
    isolation="READ_COMMITTED" read-only="false" timeout="3" />
     </tx:attributes>
</tx:advice>



自定义的Annotation注解

在开发中,开发者有时候需要自定义注解完成某些配置。自定义注解使用的基本流程如下:

  • 第一步,定义注解——相当于定义标记
  • 第二步,配置注解——把标记打在需要用到的程序代码中
  • 第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作


注解定义的关键字:@interface
  • 自定义注解自动继承了java.lang.annotation.Annotation接口
  • Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值 定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类 型、Class类型、枚举类型、注解类型、以及以上所有类型的一维数组。
  • 注解元素的访问修饰符只能是public,不写也默认为public。
  • ()不是定义方法参数列表的地方,也不能在括号中定义任何参数,就是一个特殊的语法。
  • 可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字
  • 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”



自定义注解

@MyAnnotation(value = "琦玉")
public class MyAnnotationTest {
    public static void main(String[] args) {
        Class cla = MyAnnotationTest.class;
        Annotation an = cla.getAnnotation(MyAnnotation.class);
        MyAnnotation m = (MyAnnotation) an;
        String info = m.value();
        System.out.println(info);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {
    String value() default "111111" ;
}



元注解

元注解是专门修饰注解的注解,是为了更好的设计自定义注解的细节而专门设计的。

JDK1.5中提供的四个4个标准的meta-annotation类型,分别是:



一、@Target

@Target注解,是专门用来限定某个自定义注解能被应用到Java代码的那些位置。它使用一个枚举类型ElementType定义了限定范围的选择。

@Target的使用:

枚举值 注解能够被应用的地方
ElementType.ANNOTTION_TYPE 注解类型的声明
ElementType.CONSTRUCTOR 构造方法的声明
ElementType.FIELD 属性的声明
ElementType.LOCAL.VARIABLE 局部变量的声明
ElementType.METHOD 方法的声明
ElementType.PACKAGE 包的声明
ElementType.PARAMETER 方法参数的声明
ElementType.TYPE 类、接口及枚举的声明


二、@Retention:

只能用于修饰一个Annotation定义,用于指定该Annotation的生命周期,@Rentention包含一个RetentionPolicy类型的成员变量,使用@Rentention时必须为该value成员变量指定值:

  • RetentionPolicy.SOURCE:该注解被限定在Java源文件中,该注解不会参与编译也不会在运行期起任何作用。只能在源文件中可见,和注释没什么区别。
  • RetentionPolicy.CLASS:该注解被编译到Class文件中,编译器可以在编译时根据注解做一些处理动作。但在运行时,JVM会忽略它,在运行期也不能读取到。这是默认值
  • RetentionPolicy.RUNTIME:该注解可以在运行期的加载阶段被加载到Class对象中。在程序运行阶段,可以通过反射得到该注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。实际开发中的自定义注解,几乎都使用RetentionPolicy.RUNTIME

在这里插入图片描述

@Retention(RetentionPolicy.RUNTIME) //RetentionPolicy.CLASS/RetentionPolicy.SOURCE
public @interface MyAnnotation{   //MyAnnotation的生命力被保持到运行阶段。运行时可以通过反射的方式获得注解信息
    public String name();  
    int age();        
    int[] array();
}


三、@Documented:

@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到javadoc文档中。默认情况下,javadoc是不包括注解的。

  • 需注意:定义为Documented的注解必须设置Retention值为RUNTIME


四、@Inherited:

@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。

  • 比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解,实际应用中,较少使用



注解的使用

定义注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
	public String name();
	int age() default 18;
	int[] scores();
}
  • TestAnnotation的@Target定义为Element.TYPE,书写位置为类型定义的上方。
  • TestAnnotation有注解类型元素,而且有些元素没有默认值。这要求在使用时必须在标记名后打上(),并且在()内以“元素名=元素值”的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上,重新赋值),中间以“,”号分割。

定义学生类,使用TestAnnotation 注解:

@TestAnnotation(name="tom",age=24,scores={80,65,92})
public class Student {
	 age元素被重新赋值为24
} 
@TestAnnotation(name="tom", scores={80,65,92})
public class Student {
	age元素使用默认值18
}



注解Annotation的特殊语法

  • 如果注解本身没有定义注解类型元素,那么在使用注解时可以省略()
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {}
@TestAnnotation
public class Test {
	
}
  • 如果注解本身在其内部只定义了一个注解类型元素,并且命名为value,那么在使用注解时,可以直接书写为@注解名(注解值)。它和标准写法:@注解名(value=注解值)等效!
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
      String value();
}
@TestAnnotation(“琦玉”)
public class Test {
}
  • 如果注解中某个注解类型元素是一个数组类型,在使用时只需要填入一个值,那么,在使用注解时,可以直接书写为@注解名(类型名=类型值)。它和标准写法@注解名(类型名={类型值})等效。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
      String[] addresses();
}
@TestAnnotation(addresses=“NYC”)
public class Test {
}
  • 如果一个注解的@Target定义为Element.PARAMETER,那么这个注解是配置在方法的形参前面的。
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
      String param();
}
public class Test {
    public void speak(
      @TestAnnotation(param="name") String userName){
	}}



自定义注解Annotation的运行时解析

只有当一个注解使用了@Retention(RetentionPolicy.RUNTIME)修饰,才表示该注解能够保持到运行期,才能在运行期获到它。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
	public String name();
	int age() default 18;
	int[] scores();
}
@TestAnnotation(name="琦玉",age=24,scores={80,65,92})
public class Student {}
Class studentClass = Student.class;
//判断Student类中,是否定义了@TestAnnotation注解
if(studentClass.isAnnotationPresent(TestAnnotation.class)) {
    //得到Student类中定义的注解
   TestAnnotation anno = (TestAnnotation)
	    studentClass.getAnnotation(TestAnnotation.class);
    //得到注解元素的值
    System.out.println(anno.name()+"   "+
           anno.age()+"   "+anno.scores());
 }



注解和XML的比较

  • XML:全称叫做:可扩展标记语言。它是W3C组织于1998年2月提出的一种SGML(标准通用标记语言)的一个子集。XML是一种独立的文本格式,语法能够很丰富的表达各种数据结构。所以又被大量用来作为不同平台之间数据传递的标准格式,它的使用越来越广泛,包括WebService、AJAX等都是基于它的,所以配置文件只是XML的一种用途而已。
  • 注解:注解是Java在JDK1.5中提供的一种新类型。它是在Java代码中的特殊标记,这些标记可以在编译、类加载或运行期被读取,从而执行相应的特殊操作。所以,注解Annotation是Java语言独有的,只能Java用,并且其设计目的就是为了做配置的。
  • XML用途比Java注解要广泛得多,它不限语言,只要需要表示数据的地方都可以使用,这是Java注解绝对不可比拟的。所以,java注解要替代XML是不可能的。


XML的优缺点:
  • 优点:
    • 可扩展性强。XML和它的定义文件(DTD)是文本文件,添加或修改都很方便。
    • 满足开闭原则。XML的配置文件是独立于Java代码之外的,所以如果要修改配置是不需要改动Java代码的。
  • 缺点:
    • 解析麻烦,需要第三方类库配合
    • 配置文件过多后,会导致项目管理、维护变得困难。
    • 在做代码的关联配置时麻烦,会让开发人员不断在java代码和XML配置文件之间来回切换。
    • 查错困难,XML只是文本文件,很多报错只能在运行期间体现,IDE集成开发环境不能在编译期排查。


注解的优缺点:
  • 优点:
    • JDK自带反射方式解析,不引入第三方库
    • 直接配置在Java源代码中,所以不会引入额外的配置文件,项目管理和维护变得简单
    • 无论是定义、配置还是解析都是java代码,所以不存在配置时需要在不同语法格式中切换的问题。
    • IDE支持度高,排查错误方便,提高开发效率
  • 缺点
    • 任何对配置、对定义的修改都需要改动Java代码,然后重新编译,重新打包应用。
    • 不是通用标准,只能在Java中使用。



XML和注解分别配置的使用场景

XML与注解在配置这个应用场景中互补性是非常高的。XML的缺点就是注解的优点,反之亦然。这也是目前在JavaEE的各大框架中XML+Annotation配置流的原因。

一般来说,把与代码关联度不高,并且改动可能性大的配置写在XML文件中。专门配置java代码级别关联度的,以后改动度小的则使用注解Annotation。



版权声明:本文为m0_56383107原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。