【JAVA高级&注解】Annotation

【JAVA高级&注解】Annotation

概述

通过整理该笔记,复盘 JAVA 中 "注解" 在日常开发中的使用场景及相关的一些理论知识,以达到 “温故知新” 的目的。

目录

主要内容

0x01:注解(Annotation)概述

JDK 5.0 开始, Java 增加了对元数据 (MetaData) 的支持, 也就是 Annotation(注解)

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

  • Annotation 可以像修饰符一样被使用, 可用于修饰包、类,、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在 Annotation 的 “name=value” 对中。

JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。

JavaEE/Android 中注解占据了更重要的角色,例如用来配置应用程序的任何切面, 代替JavaEE 旧版中所遗留的繁冗代码和 XML 配置等。

未来的开发模式都是基于注解的, JPA (数据库ORM框架)是基于注解, Spring2.5 以上都是基于注解的, Hibernate3.x 以后也是基于注解的,现在的 Struts2 有一部分也是基于注解的了,注解是一种趋势,在一定程度上可以说: 框架 = 注解 + 反射 + 设计模式

0x02:常见的注解

使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。 用于修饰它支持的程序元素。

示例一:生成文档相关的注解

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

上述的一些注解,需要注意他们的适用范围和使用格式

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

使用案例如下

/**
* @author shkstart
* @version 1.0
* @see Math.java
*/
public class JavadocTest {
    /**
* 程序的主方法,程序的入口
* @param args String[] 命令行参数
*/
    public static void main(String[] args) {
    }
    /**
* 求圆面积的方法
* @param radius double 半径值
* @return double 圆的面积
*/
    public static double getArea(double radius){
        return Math.PI * radius * radius;
    }
}

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

  • @Override: 限定重写父类方法, 该注解只能用于方法
  • @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
  • @SuppressWarnings : 抑制编译器警告

使用例子如下

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方法()";
    }
}

示例三: 跟踪代码依赖性,实现替代配置文件功能

Servlet3.0 提供了注解 (annotation),使得不再需要在 web.xml 文件中进行 Servlet 的部署

  • @WebServlet
@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);
    } 
}

代替原有的繁琐 xml 配置

<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
@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);
}

代替的 xml 配置

<!-- 配置事务属性 -->
<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>

0x03:自定义注解

如何自定义注解?

  • 使用 @interface 关键字
  • 自定义注解自动继承了 java.lang.annotation.Annotation 接口
  • Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。 其方法名和返回值定义了该成员的名字和类型。 我们称为配置参数。 类型只能是八种基本数据类型、String类型、 Class 类型、 enum 类型、 Annotation 类型、以上所有类型的数组。

注解的成员

  • 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值可使用 default 关键字,如果只有一个参数成员, 建议使用参数名为value

  • 如果定义的注解含有配置参数, 那么使用时必须指定参数值, 除非它有默认值。 格式是“参数名 = 参数值” , 如果只有一个参数成员, 且名称为 value,可以省略 “value=” ,如 @MyAnnotation(value="codeyee")@MyAnnotation("codeyee")

  • 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation

使用举例

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

@interface MyAnnotation{
    String value() default "codeyee";
}

注解的具体行为,将通过反射来实现

0x04:JDK中的元注解

JDK5.0 提供了4个标准的 meta-annotation 类型, 分别是:

  • Retention
  • Target
  • Documented
  • Inherited

使用频率较高

1、@Retention

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

  • RetentionPolicy.SOURCE:

    在源文件中有效(即源文件保留) , 编译器直接丢弃这种策略的注释

  • RetentionPolicy.CLASS:

    在class文件中有效(即class保留) , 当运行 Java 程序时, JVM不会保留注解。 这是默认值

  • RetentionPolicy.RUNTIME:

    在运行时有效(即运行时保留) , 当运行 Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释

注解生命周期

使用举例

@Retention(RetentionPolicy.SOURCE)
@interface MyAnnotation1{ }
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{ }

2、@Target:

用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。

以下的注解使用的频率较低

3、@Documented :

表示所修饰的注解在被javadoc解析时,保留下来。

4、@Inherited :

被它修饰的 Annotation 将具有继承性。

0x05:JDK8中注解的新特性

可重复注解

在我们没有进行任何配置之前,无法在同一个地方使用两个重复的注解

JDK8 之前解决重复注解的写法如下:

1、定义一个 value 的值为需要重复注解的类型

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {
    MyAnnotation[] value();
}

2、使用定义的 @MyAnnotations 代替之前的 @MyAnnotation ,并且 value 的值为两个 @MyAnnotation 注解,实现重复注解的效果

//jdk 8之前的写法:
@MyAnnotations({@MyAnnotation(value="hi"),@MyAnnotation(value="hi")})
//@MyAnnotation(value="hi")
//@MyAnnotation(value="abc")
class Person{
    private String name;
    private int age;

    public Person() {
    }
}

JDK8 之后的实现方法:

1、在 MyAnnotation 上声明 @Repeatable,成员值为 MyAnnotations.class

@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
    String value() default "hello";
}

2、MyAnnotation 的 TargetRetention 等元注解与 MyAnnotations相同。MyAnnotations的代码如下

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {
    MyAnnotation[] value();
}

测试重复注解的使用

//jdk8之后的写法
@MyAnnotation(value="hi")
@MyAnnotation(value="abc")
class Person{
    private String name;
    private int age;
    public Person() {
    }
}

类型注解

JDK1.8 之后,关于元注解 @Target 的参数类型 ElementType 枚举值多了两个:TYPE_PARAMETER、TYPE_USE。

  • ElementType.TYPE_PARAMETER

    表示该注解能写在类型变量的声明语句中(如:泛型声明)。如下代码

    class Generic<@MyAnnotation T>{
    
    }
    
  • ElementType.TYPE_USE

    表示该注解能写在使用类型的任何语句中,如下代码

    class Generic<@MyAnnotation T>{
        public void show() throws @MyAnnotation RuntimeException{
            ArrayList<@MyAnnotation String> list = new ArrayList<>();
            int num = (@MyAnnotation int) 10L;
        }
    }
    

总结

通过本篇笔记的整理,再次巩固了 JAVA 中一些常见注解的使用,以及如何自定义注解、元注解的使用、JDK8 中新增了哪些关于注解的特性等。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://codeyee.com/archives/java-annotation.html