一、注解是什么
概念
注解是一种元数据,一种用于描述数据的数据。如下面的代码所示:
1
2
3
4
5
6class Self {
@Override
public String toString() {
return super.toString();
}
}Self类重写了toString()方法,该方法上有Override注解表明该方法是复写了父类的方法,如果我们写错了方法名,编译器会报错,这就是注解能帮我们做的事情。从上面的例子我们可以看到注解就是用于描述数据的一种方式
注解分类
1
2
3
4@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}从上面的Override注解我们可以看到上面有一个@Retention的元注解,@Retention元注解表示该注解的保留期限。我们看下源代码如何定义@Retention。
1
2
3
4
5
6
7
8
9
10@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}可以看到注解的有效期限是根据RetentionPolicy这个枚举去确定的,我们可以看下RetentionPolicy是如何定义的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
我们可以看到共3个有效时间,第一个是Source源码时保存、第二个CLASS表示会在class文件保存但是不会在VM或者运行期保存、第三个是RUNTIME表示从编译成class文件、VM或者RUNTIME时间都会进行保存。于是可以简单的将注解分为以下几类:
1、源码注解
2、编译注解
3、运行注解
二、注解的元注解解析
我们注意到注解经常出现的四个元注解,如下代码所示:
1 | /** |
- Inherited 注解可否被继承
Target 表示注解可以使用的目标位置,比较常见的是FIELD作用于字段,TYPE作用于类、接口、枚举等等,METHOD作用于方法等等,具体的如下:
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
39public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}Documented 注解可以生成文档
- Retention 表示注解的有效持续时间,前面已经讲到了,可以分为以下三种
- SOURCE 源码保留注解
- CLASS 编译生成的class文件保留注解
- RUNTIME 运行期保留注解
三、如何写一个注解
注解用@interface关键字标识是一个注解
1
2
3public @interface PersonMessage {
}给注解加入元注解,一般加入上面我们说的四个元注解
1
2
3
4
5
6
7@Inherited
@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonMessage {
}给注解定义字段
- 注解支持多种字段类型,除了常用的基本数据类型外,还支持枚举、注解等等。
- 注解字段默认值可加可不加
如果注解只有一个字段,字段名必须为value
1
2
3
4
5
6
7
8
9
10@Inherited
@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonMessage {
int age() default 18;
String name() default "buzheng";
}
四、如何使用并解析注解
假如我们定义了两个注解,一个是作用在类上面的,一个是作用在方法上面的,在运行的时候,我们需要取解析他们,代码如下所示:
1 | package com.buzheng.annotation; |
我们可以看到,无论是拿类上面的注解信息或者是方法上面的注解信息,我们都是通过反射的方法进行获取的,流程大概如下:
- 通过反射获取需要解析的类
- 如果需要获取类上面的注解,先判断类上面是否有注解c.isAnnotationPresent(YourAnnotation.class);
- 获取类上的注解信息c.getAnnotation(YourAnnotation.class);
- 如果需要获取方法或者字段的注解信息,大致流程跟上述获取类的注解信息的方法一致,只是调用的方法不同。
比如上面的流程我们可以运行获取到运行结果:
五、简单的注解使用例子
假如我们有这样的一个需求,我们有一个Query,需要跟SQL进行字段的一一对应(不考虑MyBatis),然后我们构建出一条SQL可以用于执行。那我们可以在Query类用注解指定类名,字段名,然后到时候通过反射获取每个字段的值,这样我们就能把整条SQL拼接起来
首先我们在Query上需要使用的两个注解:
- 第一个是定义在类上面,指定类对应表名的注解
1
2
3
4
5
6
7
8
9
10
11/**
* Created by buzheng on 17/12/5.
* 该注解用于类上,标明类对应的表名是什么
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}- 第二个定义在类的字段上面,指定字段跟表的字段对应的注解
1
2
3
4
5
6
7
8
9
10
11/**
* Created by buzheng on 17/12/5.
* 该注解用于标明类字段跟表字段向的一一对应关系
*/
@Inherited
@Target(ElementType.FIELD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Row {
String value();
}- 给Query定义注解,绑定跟查询的表的一一对应关系
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/**
* Created by buzheng on 17/12/5.
*/
@Table("student")
public class Query {
@Row("student_name")
private String name;
@Row("student_age")
private int age;
@Row("student_grade")
private int grade;
@Row("student_number")
private int number;
@Row(("id"))
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
解析注解、获取字段值并拼装SQL
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/**
* Created by buzheng on 17/12/5.
*/
public class Test {
public static void main(String[] args) {
Query query = new Query();
query.setAge(1);
query.setId(1);
query.setName("buzheng");
System.out.println(getQueryFromAnnotation(query));
}
/**
* 传进来查询的Query类 解析并拼装sql
*
* @param object
* @return
*/
public static String getQueryFromAnnotation(Object object) {
Class clazz = object.getClass();
boolean flag = clazz.isAnnotationPresent(Table.class);
StringBuilder stringBuilder = new StringBuilder("select * from ");
if (!flag) {
return stringBuilder.toString();
}
Table table = (Table) clazz.getAnnotation(Table.class);
stringBuilder.append(table.value());
stringBuilder.append(" where 1=1 ");
java.lang.reflect.Field[] fileds = clazz.getDeclaredFields();
for (Field field : fileds) {
boolean isFiledAnnotation = field.isAnnotationPresent(Row.class);
if (isFiledAnnotation) {
stringBuilder.append(" and ");
Row row = (Row) field.getAnnotation(Row.class);
String fieldName = field.getName();
String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, fieldName.length());
try {
Method method = clazz.getMethod(methodName);
Object value = method.invoke(object);
if (value == null || (value instanceof Integer && (Integer) value == 0)) {
continue;
}
stringBuilder.append(row.value() + " = " + value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return stringBuilder.toString();
}
}
运行上述代码可以看到我们的效果
假如我们还有一个需求,我们需要用AOP对某些请求进行拦截,那我们也可以将注解作为切面,将需要拦截的方法增加注解的方式进行拦截,并且可以将注解的信息解析出来在切面进行使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* Created by buzheng on 17/12/5.
*/
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogAnnotation {
/**
* 操作
* @return
*/
String operateType() default "默认操作";
/**
* 操作人员工号ID
* @return
*/
int workId();
}使用AOP的方式使用注解
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/**
* Created by buzheng on 17/12/5.
*/
@Aspect
@Component
public class MyAspect {
private Logger logger = LoggerFactory.getLogger(MyAspect.class);
@Pointcut("@annotation(com.buzheng.LogAnnotation)")
public void logPointCount() {
}
@Around("logPointCount()")
public Object actionLog(ProceedingJoinPoint joinPoint) {
Object res = new Object();
try {
//获取注解的各种信息
LogAnnotation logAnnotation = getAnnotation(joinPoint);
String operateType = logAnnotation.operateType();
int workId = logAnnotation.workId();
//然后你想做点什么,比如打个log
logger.warn("操作类型变更:" + operateType + "变更人工号:" + workId);
res = joinPoint.proceed();
} catch (Throwable throwable) {
logger.error("aop is failed", throwable);
}
return res;
}
/**
* 获取注解信息
*
* @param joinPoint
* @return
* @throws Exception
*/
public LogAnnotation getAnnotation(ProceedingJoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
boolean flag = method.isAnnotationPresent(LogAnnotation.class);
if (flag) {
LogAnnotation logAnnotation = (LogAnnotation) method.getAnnotation(LogAnnotation.class);
return logAnnotation;
}
return null;
}
}