Java 注解(Annotation)概念及实战

时间:2022-08-28 20:15:36

摘要:

  java注解:java 提供了一种源程序中的 元素 关联任何信息和任何元数据的途径和方法。

  学习注解的目的:

  • 能够读懂别人写的代码,特别是框架相关的代码
  • 让编程更加简洁,代码更加清晰
  • 让别人高看一眼,特别是会使用自定义注解

目录:

  1. java中的常见注解

  2. 注解分类

  3. 自定义注解

  4. 注解应用实践

1. java中的常见注解

1) JDK 自带的注解
  @Override @Deprecated @SuppressWarnings
2) 常见的第三方注解
  Spring @Autowired @Service @Repository
  MyBatis @InsertProvider @UpdateProvider @Options

2. 注解分类

1) 按照运行机制分:源码注解、编译时注解(如jDK 自带的三个注解)、运行时注解(如@Autowired)
2) 按照来源来分:JDK的注解、第三方的注解、用户自定义的注解

3. 自定义注解

1) 自定义注解的语法要求

 1 import java.lang.annotation.Documented;
2 import java.lang.annotation.ElementType;
3 import java.lang.annotation.Inherited;
4 import java.lang.annotation.Retention;
5 import java.lang.annotation.RetentionPolicy;
6 import java.lang.annotation.Target;
7
8 /*元注解*/
9
10 /*注解作用域的列表
11 CONSTRUCTOR 构造方法声明
12 FIELD 字段声明
13 LOCAL_VARIABLE 局部变量声明
14 METHOD 方法声明
15 PACKAGE 包声明
16 PARAMETER 参数声明
17 TYPE       类,接口*/
18 @Target({ ElementType.METHOD, ElementType.TYPE })
19
20 /*
21 * 注解的生命周期
22 * SOURCE 自在源码中显示,编译时会丢弃
23 * CLASS 编译时会记录到class中,运行时忽略
24 * RUNTIME运行时存在,可以通过反射读取
25 */
26 @Retention(RetentionPolicy.RUNTIME)
27
28 /* 标识性元注解,允许子类继承父类的注解,只在类上有效,接口和方法无效 */
29 @Inherited
30
31 /* 标识性元注解,生成 javadoc 时会包含注解 */
32 @Documented
33
34 /* 使用 @interface 关键字定义注解 */
35 public @interface Description {
36
37 /**
38 * 成员类型是受限的,合法的类型包括原始类型及String,Class,Annotation,Enumeration
39 * 如果注解只有一个成员,则成员名必须为 value(),在使用时可以忽略成员名和赋值号
40 * 注解类可以没有成员,没有成员的注解类成为标识注解
41 */
42
43 String deco(); //成员以无参无类型方式声明
44
45 String author();
46
47 int age() default 18; //可以用default为成员制定一个默认的值
48 }

2) 元注解(注解的注解)

  @Target

  @Retention

  @Inherited

  @Documented

  元注解,相当于是注解的注解,用来对注解的作用域、作用时期等进行解释声明(具体内容看上面的代码)。

3) 自定义注解的使用

  @<注解名>(<成员名1>=<成员值1>,<成员名2>=<注解值2>,...)
例:
  @Description(desc="I am eyeColor", author="Mooc Boy", age=18)
  public String eyeColor() {
   return "red";
  }

特殊情况:

  标识注解:
  @<注解名>
  只有一个成员的注解:
  @<注解名>(<成员值>)

4) 解析注解

  通过反射获取类、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。

  在本例中,定义一个接口 Person,一个实现类 Child,一个自定义的解析类 Description 以及一个解析注解的测试类ParseAnn,通过此例可以看到解析注解的作用。

Person.java

1 public interface Person {
2 public String name();
3
4 public int age();
5
6 @Deprecated //表明该方法已经过时,可以使用,但不推荐使用
7 public void sing();
8 }

Child.java

 1 @Description("I am class annotation")
2 public class Child implements Person {
3
4 @Override
5 @Description("I am method annotation")
6 public String name() {
7 return null;
8 }
9
10 @Override
11 public int age() {
12 return 0;
13 }
14
15 @Override
16 public void sing() {
17
18 }
19 }

Description.java

 1 import java.lang.annotation.Documented;
2 import java.lang.annotation.ElementType;
3 import java.lang.annotation.Inherited;
4 import java.lang.annotation.Retention;
5 import java.lang.annotation.RetentionPolicy;
6 import java.lang.annotation.Target;
7
8 @Target({ ElementType.METHOD, ElementType.TYPE })
9 @Retention(RetentionPolicy.RUNTIME)
10 @Inherited
11 @Documented
12
13 /* 使用 @interface 关键字定义注解 */
14 public @interface Description {
15 String value();
16 }

ParseAnn.java

 1 import java.lang.annotation.Annotation;
2 import java.lang.reflect.Method;
3
4 public class ParseAnn {
5
6 public static void main(String[] args) {
7 // 1.使用类加载器加载类
8 try {
9 Class c = Class.forName("com.ann.test.Child");
10 // 2.找到类上面的注解
11 boolean isExist = c.isAnnotationPresent(Description.class);
12 if (isExist) {
13 // 3.拿到注解实例
14 Description d = (Description) c.getAnnotation(Description.class);
15 System.out.println(d.value());
16 }
17
18 // 4.找到方法上的注解
19 Method[] ms = c.getMethods();
20 for (Method m : ms) {
21 boolean isMExist = m.isAnnotationPresent(Description.class);
22 if (isMExist) {
23 Description d = (Description) m.getAnnotation(Description.class);
24 System.out.println(d.value());
25 }
26 }
27
28 // 另外一种解析方法
29 for (Method m : ms) {
30 Annotation[] as = m.getAnnotations();
31 for (Annotation a : as) {
32 if (a instanceof Description) {
33 Description d = (Description) a;
34 System.out.println(d.value());
35 }
36 }
37 }
38 } catch (ClassNotFoundException e) {
39 e.printStackTrace();
40 }
41 }
42 }

输出结果:

I am class annotation
I am method annotation
I am method annotation

4. 注解应用实践

需求:
  1.有一个用户表,字段包括用户ID,用户名,昵称,年龄,性别,所在城市,邮箱,手机号。
  2.方便的对每个字段或字段的组合条件进行检索,并打印出SQL。
  3.使用方式要足够简单,见代码示例。

  首先考虑代码如何与数据库进行一个映射,比如程序如何知道数据库的表名、字段名,这就可以用到注解,利用注解解析实现。

      每一个字段都有一个 get 方法,通过反射调用 get 方法来取得字段的值 getMethod.invoke()。

  对于注解,特别是运行时注解,相当于在类、方法、字段等内容上套上一层“外衣”,可以根据“外衣”分类,找到相关内容,从而进行操作。
  

  java类:Filter.java 定义了字段内容,Test.java 测试类。

  解析类:Table.java 表解析,Column.java 字段解析。

Table.java

1 @Target({ ElementType.TYPE })
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface Table {
4 String value();
5 }

Column.java

1 @Target({ ElementType.FIELD })
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface Column {
4 String value();
5 }

Filter.java

 1 @Table("user")
2 public class Filter {
3
4 @Column("id")
5 private int id;
6
7 @Column("userName")
8 private String userName;
9
10 @Column("nickName")
11 private String nickName;
12
13 @Column("age")
14 private int age;
15
16 @Column("city")
17 private String city;
18
19 @Column("email")
20 private String email;
21
22 @Column("mobile")
23 private String mobile;
24
25 public int getId() {
26 return id;
27 }
28
29 public void setId(int id) {
30 this.id = id;
31 }
32
33 public String getUserName() {
34 return userName;
35 }
36 /*自动生成的get set 方法*/
37 }

Test.java

 1 public class Test {
2
3 public static void main(String[] args) {
4 Filter f1 = new Filter();
5 f1.setId(10);// 表示查询ID为10的用户
6
7 Filter f2 = new Filter();
8 f2.setUserName("lucy");// 查询用户名为lucy的用户
9 f2.setAge(18);
10
11 Filter f3 = new Filter();
12 f3.setEmail("liu@sina.com,zh@163.com,123@qq.com");// 查询邮箱为其中任意一个的用户
13
14 String sql1 = query(f1);
15 String sql2 = query(f2);
16 String sql3 = query(f3);
17
18 System.out.println(sql1);
19 System.out.println(sql2);
20 System.out.println(sql3);
21 }
22
23 private static String query(Filter f) {
24 StringBuilder sb = new StringBuilder();
25 // 1.获取到class
26 Class c = f.getClass();
27 // 2.获取到Table的名字
28 boolean exists = c.isAnnotationPresent(Table.class);
29 if (!exists) {
30 return null;
31 }
32 Table t = (Table) c.getAnnotation(Table.class);
33 String tableName = t.value();
34 sb.append("select * from ").append(tableName).append(" where 1=1");
35 // 3.遍历所有的字段
36 Field[] fArray = c.getDeclaredFields();
37 for (Field field : fArray) {
38 // 4.处理每个字段对应的sql
39 // 4.1 拿到字段名
40 boolean fExists = field.isAnnotationPresent(Column.class);
41 if (!fExists) {
42 continue; // 如果不是数据库的字段,不做处理
43 }
44 Column column = field.getAnnotation(Column.class);
45 String columnName = column.value();
46 // 4.2 拿到字段值
47 String fieldName = field.getName();
48 String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); // 利用字段名获取方法名
49 Object fieldValue = null;
50 try {
51 Method getMethod = c.getMethod(getMethodName); // 利用方法名得到方法
52 fieldValue = getMethod.invoke(f); // 反射调用方法
53 } catch (Exception e) {
54 e.printStackTrace();
55 }
56 // 4.3 拼装sql
57 if(fieldValue == null || (fieldValue instanceof Integer && (Integer)fieldValue == 0)) {
58 continue;
59 }
60 sb.append(" and ").append(fieldName);
61 if(fieldValue instanceof String) {
62 if(((String) fieldValue).contains(",")) {
63 String[] values = ((String) fieldValue).split(",");
64 sb.append(" in (");
65 for (String string : values) {
66 sb.append("'").append(string).append("'").append(",");
67 }
68 sb.deleteCharAt(sb.length()-1);
69 sb.append(")");
70 } else {
71 sb.append("=").append("'").append(fieldValue).append("'");
72 }
73 } else if(fieldValue instanceof Integer) {
74 sb.append("=").append(fieldValue);
75 }
76 }
77 return sb.toString();
78 }
79 }

输出结果:

select * from user where 1=1 and id=10
select * from user where 1=1 and userName='lucy' and age=18
select * from user where 1=1 and email in ('liu@sina.com','zh@163.com','123@qq.com')

 

Eclipse快捷键:

shift + alt + s 调出有用命令,比如生成set get 方法。
ctrl + d 删除整行
ctrl + alt + down 复制整行