Java 注解的理解和使用
什么是注解
官方定义:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。
通俗解释:注解可以直观的理解为注释,只不过这个注释在任何时候都就可以用,包括编译期和运行期,主要是用作补充作用。也可以将其理解成标签,可以给方法,类等地方做标注。同时我们还能够在任何时间或地点获得这种标注。从而用来提供一些补充信息和配置。
可能到这还不是很清楚,那么我们先继续向下看。
注解的定义
首先来看注解的使用,注解通过 @interface 关键字进行定义。
public @interface TestAnnotation {
}
@interface
就类似于Java 中的class, interface
关键字,都是一种类型。
上述定义就可以理解为创建了一个标签或者标注。
什么是元注解
什么是元注解呢?当我们定义了一个注解之后,该注解还是无法正常工作的,我们还需要给注解做一些配置,比如该注解的生命周期,该注解覆盖的范围。
其实也比较好理解,当我们创建一个标签的时候,我们要知道该标签可以用在什么地方或者一些其他的配置。
此时就有了元注解,顾名思义,元注解就是应用到注解上的注解,常见的元注解有五种:
@Target
:指定注解可以应用的元素类型。它的取值可以是ElementType.TYPE
(类、接口、枚举)、ElementType.FIELD
(字段)、ElementType.METHOD
(方法)、ElementType.PARAMETER
(参数)等。@Retention
:指定注解的生命周期。它的取值可以是RetentionPolicy.SOURCE
(编译期可见)、RetentionPolicy.CLASS
(类加载期可见)或RetentionPolicy.RUNTIME
(运行期可见)。@Documented
:指定注解是否会包含在文档中。@Inherited
:指定注解是否可以被继承。如果一个注解被标记为@Inherited
,则子类会继承父类的注解。@Repeatable
:指定注解是否可重复。Java 8引入了可重复注解的概念,允许在同一个元素上多次使用相同的注解类型。通常是一个注解的值可以有多个。
这些元注解可以用于定义自定义注解,并对自定义注解的行为进行约束和配置。例如,通过在自定义注解上使用@Target(ElementType.METHOD)
,可以限制该注解只能应用于方法上;通过使用@Retention(RetentionPolicy.RUNTIME)
,可以指定该注解在运行时可见。
注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
注解的使用
上面说明了Java 中注解的一些相关概念,那么注解到底如何使用呢?我们平时在哪些地方都用到了注解呢?
我们知道在运行时可以使用反射来获取一些Class的信息,那么当一个注解的生命周期设置为运行时的时候,我们依然可以通过反射来获取注解的一些信息。
所以注解一般是配合反射来使用的,由于注解的特性,我们可以对注解的声明周期,覆盖范围,属性做一些配置,由于Java反射的存在,我们可以在运行时来提取注解,从而获取注解的一些属性,从而我们能够做一些自己的处理。
此时又有疑问了,我们获取这些注解的信息有什么作用呢?
其实当我们使用注解修饰了类和方法等成员之后,注解自己是不会生效的,必须由开发者提供相应的代码来提取并处理注解信息,这些处理注解的代码称为 APT(Annotation Processing Tool)。这也就是为什么官方解释注解并不会对源代码有什么影响了,注解只是一个标签工具,再打上这个标签之后,开发者可以使用相应的代码来获取,从而做一些处理工作。
那么一般都是什么处理工作呢?非常好理解,我们自己定义一个注解然后进行处理。
举例:
我们可以写一个检测代码是否出错的注解,如果出错我们记录一下错误信息。
首先写一段代码:
package demo;
/**
* @ClassName NoBug
* @Author cuisenghe
* @Date DATE
*/
public class NoBug {
@Check
public void add(){
System.out.println("1+1=" + 1+1);
}
@Check
public void minus(){
System.out.println("1-1=" + (1-1));
}
@Check
public void multi(){
System.out.println("1*1=" + 1*1);
}
@Check
public void divide(){
System.out.println("1/0=" + 1/0);
}
}
check注解:
package demo;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
检查工具类:
package demo;
import java.lang.reflect.Method;
/**
* @ClassName CheckTest
* @Author cuisenghe
* @Date DATE
*/
public class CheckTest {
public static void main(String[] args) {
// 创建对象
NoBug noBug = new NoBug();
// 获取对象的Class
Class<? extends NoBug> aClass = noBug.getClass();
// 根据class 获取所有的方法
Method[] declaredMethods = aClass.getDeclaredMethods();
//用来记录测试产生的 log 信息
StringBuilder log = new StringBuilder();
// 记录异常的次数
int errornum = 0;
for (Method method : declaredMethods) {
// 判断方法上是否由@Check注解
if (method.isAnnotationPresent(Check.class)) {
try {
// 有则执行该方法
method.setAccessible(true);
method.invoke(noBug, null);
} catch (Exception e) {
// 捕获异常
// 记录到log中
errornum++;
log.append(method.getName());
log.append(" ");
log.append("has error:");
log.append("\n\r caused by ");
// 记录异常的次数
//记录测试过程中,发生的异常的名称
log.append(e.getCause().getClass().getSimpleName());
log.append("\n\r");
//记录测试过程中,发生的异常的具体信息
log.append(e.getCause().getMessage());
log.append("\n\r");
}
}
}
log.append(aClass.getSimpleName());
log.append(" has ");
log.append(errornum);
log.append(" error.");
// 生成测试报告
System.out.println(log.toString());
}
}
运行结果:
可以发现,注解帮我们检测出了有方法执行时发生了错误,这就是为什么官方说明,注解并不会自动生效,而是需要开发者们自己编写处理逻辑。
那有人又有疑问了?为什么我平时使用的注解都是自动生效的?
其实是当我们使用一些框架的时候,开发者们已经写好了关于这些注解的逻辑,当我们使用这些注解,并执行程序的时候,框架会自动帮我们运行这些处理逻辑,从而使得这些注解生效。
常见的一些官方注解有:
@Deprecated:这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
这个注解是编译期的注解,这个注解的处理逻辑是编译器完成的,其实也是编译器的开发者完成的。
@Override:提示子类要复写父类中被 @Override 修饰的方法。
在我们使用spring框架进行开发的时候,也会有一些注解,比如@Autowired注解,@Resource注解等等,这些注解都是配合spring框架完成一些工作,这些注解的处理逻辑都是框架帮我们完成的,我们只需要按照要求使用这些注解就可以了。