直接就说注解的概念还是挺让人懵逼的,注解不太好理解,那么注释相信大家一定不会陌生。
注释就是用文字描述程序。
注释是给程序员看的。不需要让程序读取注释。
注解和注释类似,是用来说明程序的。不同于注释,注解是给计算机看的
比如我们重写一个方法,会在前面添加@Override
,这就是一个注解,告诉计算机:这个方法是一个重写方法。
- 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
-
概念描述:
- JDK1.5之后的新特性
- 用来说明程序
- 使用注解:@注解名称
-
作用分类:
①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
-
编写文档
新建一个AnnoTest.java,内容如下:
package com.ahmu.yx.annotation;
/**
* 注解javadoc演示
*
* @author yx
* @version 1.0
* @since 1.5
*/
public class AnnoTest {
/**
* 计算两数的和
* @param a 整数
* @param b 整数
* @return a,b之和
*/
public int add(int a, int b) {
return a + b;
}
}
右键点击选中之后在终端打开:
输入javadoc AnnoTest.java
原本的目录下也生成了很多文件:
打开index.html 文件,然后在浏览器打开
可以看到注释文档:
JDK中预定义的一些注解
- @Override :检测被该注解标注的方法是否是继承自父类(接口)的
- @Deprecated:该注解标注的内容,表示已过时
- @SuppressWarnings:压制警告
一般传递参数all @SuppressWarnings(“all”)
如果在方法前添加@SuppressWarnings("all")
就会压制所有的警告,一般在类前加入压制警告
自定义注解
格式
元注解
public @interface 注解名称{
属性列表;
}
一个简单的注解:
使用注解:
这样肯定看不出来注解到底有什么用
我们打开终端,就和前面一样,然后输入
javac MyAnno.java
没有任何提示,但是我们可以看到生成了一个MyAnno.class
文件
内容如下:
反编译MyAnno.class
文件
输入javap MyAnno.class
提示生成了一个MyAnno.java
文件,内容就是:
public interface com.ahmu.yx.annotation.MyAnno extends java.lang.annotation.Annotation {
}
根据内容可以知道注解的本质就是:
- 本质:注解本质上就是一个接口,该接口默认继承Annotation接口
既然注解是一个接口,接口中可以定义方法,在注解中同样也可以。
属性:接口中的抽象方法
- 要求:
-
**属性(方法)**的返回值类型有下列取值
- 基本数据类型
- String
- 枚举
- 注解 (
MyAnno anno();
) - 以上类型的数组
- 注意(没有void方法)
-
定义了属性,在使用时需要给属性赋值
使用这样一个注解时,会发现提示注解中的属性没有被赋值:
将属性赋值即可:@MyAnno(show = "show()", say = "hello()");
-
如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
public String show() default "say()";
-
如果只有一个属性需要赋值,(如果有两个就需要指定属性名了)并且属性的名称是
value
,则value
可以省略,直接定义值即可。
使用: -
数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
使用:
-
元注解:用于描述注解的注解
- @Target:描述注解能够作用的位置
- ElementType取值:
- TYPE:可以作用于类上
- METHOD:可以作用于方法上
- FIELD:可以作用于成员变量上
- TYPE:可以作用于类上
- ElementType取值:
既可以作用于类上,也可以作用于方法和成员变量上:
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//表示MyAnno只能作用与类上
public @interface MyAnno {
}
- @Retention:描述注解被保留的阶段
- @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到(一般我们自己写的注解都是这个)
- @Documented:描述注解是否被抽取到api文档中(默认注解是不会被添加到api文档中的)
- @Inherited:描述注解是否被子类继承
解析注解
前面说过了注解的定义与基本概念,但是注解到底有什么用呢?毕竟我们还没有在程序中真正使用注解。
联想到之前的内容,
- 注解是一个接口,
- 注解的方法叫属性,
- 在使用注解时需要给属性赋值
于是我们可以想到:注解对程序起作用的主要工具是不是就是属性呢?
下面的内容需要用到反射,如果不懂反射的可以参考下面一篇文章:
https://blog.csdn.net/weixin_45468845/article/details/107362154
在反射的“框架”案例中,我们通过读取配置文件来获取对象和方法信息
而注解,就是为了替代加载配置文件的
将反射的“框架”案例修改一下,目标不变:
目的:不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
- 在程序使用(解析)注解:获取注解中定义的属性值
- 获取注解定义的位置的对象 (Class,Method,Field)
- 获取指定的注解getAnnotation(Class)
- 调用注解中的抽象方法获取配置的属性值
新建一个注解 Pro.java
package com.ahmu.yx.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 描述需要去执行的类型和方法名
*/
@Target(ElementType.TYPE)//注解作用在类上
@Retention(RetentionPolicy.RUNTIME)//注解在RUNTIME时起作用
public @interface Pro {
String className();
String methodName();
}
通过注解获取就可以不需要加载配置文件而直接获得对象与执行方法:
@Pro(className = "com.ahmu.yx.annotation.Student", methodName = "sleep")
ReflectTest.java类
:
package com.ahmu.yx.annotation;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* 框架类
*/
@Pro(className = "com.ahmu.yx.annotation.Student", methodName = "sleep")
public class ReflectTest {
public static void main(String[] args) throws Exception {
//能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
//解析注解
//1.1 获取该类的字节码文件对象(加了注解的类)
Class<ReflectTest> reflectClass = ReflectTest.class;
//2获取上面注解的对象
//其实就是在内存中去生成了一个该注解接口的子类实现对象
/*
public class ProImpl implements Pro{
public String className(){
return "com.ahmu.yx.annotation.Student";
}
public String method(){
return "sleep()";
}
}
*/
Pro p = reflectClass.getAnnotation(Pro.class);
//3.调用注解对象中定义的抽象方法,获取返回值
String className = p.className();
//4.获取返回值
String methodName = p.methodName();
//5.加载该类进内存
Class c = Class.forName(className);
//6.创建对象
Object o = c.newInstance();
//7.获取对象方法
Method method = c.getMethod(methodName);
//执行方法
method.invoke(o);
}
}
结果:
使用注解的核心就是获取注解对象:Pro p = reflectClass.getAnnotation(Pro.class);
前面说过注解其实就是接口,相当于在内存中生成了一个实现该注解接口的子类实现对象(即 p)
//其实就是在内存中去生成了一个实现Pro注解接口的子类实现对象
public class ProImpl implements Pro{
public String className(){
return "com.ahmu.yx.annotation.Student";
}
public String method(){
return "sleep()";
}
}
案例:简单的测试框架
有这样一个计算器类:
public class Calculator {
//加法
public void add() {
System.out.println("1+0=" + (1 + 0));
}
//加法
public void sub() {
System.out.println("1-0=" + (1 - 0));
}
//乘法
public void mul() {
System.out.println("1*1=" + (1 * 0));
}
//加法
public void div() {
System.out.println("1/0=" + (1 / 0));
}
public void show(){
System.out.println("旭哥好帅");
}
}
想要测试这个类中的代码有没有问题,如果一个一个测试未免太过麻烦,我们定义一个Check
注解
想要检查哪一个注解有问题就在方法前加一个注解
光加注解肯定没有用呀,我们看一下注解的内容:
注解里面竟然什么都没有?
这怎么测试?
package com.ahmu.yx.annotation.com.ahmu.yx.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
不要急,我们编写一个TestCheck
类:
测试所有添加了Check
注解的方法并执行,这里会用到反射的知识。
然后将异常信息写入bug.txt
文件中
内容如下:
package com.ahmu.yx.annotation.com.ahmu.yx.annotation;
import com.ahmu.yx.annotation.Calculator;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 简单的测试框架
* <p>
* 当主方法执行后,会自动执行被检测的所有方法(加了Check注解的方法)
* 判断方法是否异常,并记录到文件中
*/
public class TestCheck {
public static void main(String[] args) throws Exception {
int number = 0;//出现异常的次数
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
//创建类对象
Calculator calculator = new Calculator();
///2.获取字节码文件对象
Class c = calculator.getClass();
//3.获取所有的方法
Method[] methods = c.getMethods();
for (Method method : methods){
//4.判断方法上是否有注解
if (method.isAnnotationPresent(Check.class)){
//5.有注解,执行代码
try {
method.invoke(calculator);
} catch (Exception e) {
//6.捕获异常,记录到文件信息
number++;//出现异常次数+1
//将异常方法写入文件
bw.write(method.getName() + "方法出现异常");
bw.newLine();
//将异常名称写入文件
bw.write("异常名称:" + e.getCause().getClass().getSimpleName());
bw.newLine();
//将异常原因写入文件
bw.write("异常的原因:" + e.getCause().getMessage());
bw.newLine();
//分隔符
bw.write("-------------------------");
e.printStackTrace();
}
}
}
bw.write("本次测试一共出现" + number + "次异常");
bw.flush();
bw.close();
}
}
结果:
小结:
- 以后大多数时候,我们会使用别人写好的注解,而不是自定义注解
- 注解给谁用?
- 编译器
- 给解析程序用
- 注解不是程序的一部分,可以理解为注解就是一个标签