吴山青,越山青。两岸青山相送迎。谁知离别情?

——林逋《长相思》

Java基础之注解Annotation

1. 注解 (Annotation) 概述

  1. 从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是Annotation(注解)
  2. Annotation 其实就是代码里的 特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
    码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
  3. Annotation 可以像修饰符一样被使用, 可用于 修饰包, 类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation的 “name=value” 对中。
  4. 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
  5. 未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式

2. Annocation的使用示例

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

2.1 生成文档相关的注解

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

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

  • @Override: 限定重写父类方法, 该注解只能用于方法。
  • @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
  • @SuppressWarnings: 抑制编译器警告
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @Date 2020/11/23 14:04
* @Version 10.21
* @Author DuanChaojie
*/
public class AnnotationTest {
public static void main(String[] args) {

@SuppressWarnings("unused")
int a = 10;
}

@Deprecated
public void myPrint(){
System.out.println("@Deprecated修饰的方法表示已过时");
}

@Override
public String toString(){
return "重写的toString()";
}
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
@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);
}
}

web.xml

1
2
3
4
5
6
7
8
<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框架中关于“事务”的管理

1
2
3
4
5
6
7
8
9
@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);
}
1
2
3
4
5
6
7
8
<!-- 配置事务属性 -->
<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>

3. JDK 提供的4种元注解☆

元注解:对现有的注解进行解释说明的注解。

  1. @Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为)\RUNTIME只有声明为RUNTIME生命周期的注解,才能通过反射获取。@Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用@Rentention 时必须为该 value 成员变量指定值

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

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

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

    4. image-20201123143249561

    5. ~~~~java
      public enum RetentionPolicy {

      SOURCE,
      CLASS,
      RUNTIME
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

      2. `@Target:`用于指定被修饰的 Annotation 能用于修饰哪些程序元素。

      1. ![image-20201123143638017](https://oss-blogs.oss-cn-hangzhou.aliyuncs.com/blogs/itbuild/JavaSE/image-20201123143638017.png)

      2. ~~~~java
      public enum ElementType {
      TYPE,
      FIELD,
      METHOD,
      PARAMETER,
      CONSTRUCTOR,
      LOCAL_VARIABLE,
      ANNOTATION_TYPE,
      PACKAGE,
      /**
      * @since 1.8
      */
      TYPE_PARAMETER,
      /**
      * @since 1.8
      */
      TYPE_USE
      }
  2. @Documented:用于指定被该元 Annotation 修饰的 Annotation 类将被javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。

    1. 定义为Documented的注解必须设置Retention值为RUNTIME。
  3. @Inherited:被它修饰的 Annotation 将具有 继承性。如果某个类使用了被@Inherited 修饰的 Annotation, 则其子类将自动具有该注解。

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

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

4. 自定义注解Annotation

  1. 定义新的 Annotation 类型使用 @interface 关键字
  2. 自定义注解自动继承了java.lang.annotation.Annotation 接口
  3. Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String 类型 、Class 类型 、enum 类型 、Annotation 类型 、以上所有类型的 数组。
  4. 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值可使用 default 关键字
  5. 如果只有一个参数成员,建议使用 参数名为value
  6. 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
  7. 没有成员定义的 Annotation 称为 标记; 包含成员变量的 Annotation 称为元数据 Annotation
  8. ==注意:自定义注解必须配上注解的信息处理流程才有意义。==
1
2
3
4
5
6
7
8
9
10
11
/**
* @Date 2020/11/23 14:20
* @Version 10.21
* @Author DuanChaojie
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "atguigu";
}

AnnotationTest

利用反射获取注解信息

  1. JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 该接口代表程序中可以接受注解的程序元素。
  2. 当一个 Annotation 类型被定义为运行时 Annotation 后, 该注解才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取。
  3. 程序可以调用 AnnotatedElement对象的如下方法来访问 Annotation 信息
  4. image-20201123145816971
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* @Date 2020/11/23 14:04
* @Version 10.21
* @Author DuanChaojie
*/
public class AnnotationTest {
public static void main(String[] args) {
Class clazz = Student.class;
Annotation[] annotations = clazz.getAnnotations();
for (int i = 0; i < annotations.length; i++) {
System.out.println(annotations[i]);
}
}
}


@MyAnnotation(value = "hi")
class Person {
private String name;
private int age;

public Person() {
}

@MyAnnotation
public Person(String name, int age) {
this.name = name;
this.age = age;
}

@MyAnnotation
public void walk() {
System.out.println("人走路");
}

@MyAnnotation
public void eat() {
System.out.println("人吃饭");
}
}

interface Info {
void show();
}

class Student extends Person implements Info {

@Override
public void walk() {
System.out.println("学生走路");
}

@Override
public void show() {

}
}

class Generic<@MyAnnotation T> {

public void show() throws @MyAnnotation RuntimeException {

ArrayList<@MyAnnotation String> list = new ArrayList<>();

int num = (@MyAnnotation int) 10L;
}

}

5. JDK8 中注解的新特性

Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。此外,反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法参数上的注解。

image-20201123150034654

类型注解

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

在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。

  • ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
  • ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。