目录
一、注解定义
注解(Annotation),也叫元数据。一种代码级别的说明。
它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。
它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类
- 辅助执行部分代码检查,便于在编译期提前发现问题。
- 配合代码处理,可以简化配置,之前依赖外部文件的配置方式可以简化为在代码中。
- 基于注解,可以在编译期自动生成一些模板式代码,比如lombok。
- 底层框架,基于注解+反射可灵活实现一些算法逻辑,而不用关注具体对象。
- 通过代码里标识的注解生成文档。
二、自定义注解
创建自定义注解使用@interface。
- 使用@interface声明,不能继承其他类或者接口。
- 注解方法不能带有参数。
- 注解方法返回值类型限定为:基本类型、String、Class、Enum、Annotation或者是这些类型的数组。
- 注解方法可以有默认值,且不能为null。
- 注解本身能够包含元注解,元注解被用来注解其它注解。
- 注解类中的方法只能用public或者默认这两个访问权修饰,不写public就是默认。
- 如果注解类中只有一个成员,最好把方法名设置为"value"。
三、元注解(注解的注解)
1. 限制注解的使用范围(@Target)
@Target约束自定义注解只能在哪些地方使用,即约束自定义注解可以标记的范围
@Target中可使用的值定义在ElementType枚举类中,常用值如下
- TYPE,类,接口
- FIELD, 成员变量
- METHOD, 成员方法
- PARAMETER, 方法参数
- CONSTRUCTOR, 构造器
- LOCAL_VARIABLE, 局部变量
- 等等等
用@Target指定ElementType属性
public enum ElementType {
// 只能修饰类、接口(包含注解类型)或者枚举
TYPE,
// 只能修饰成员变量,包含枚举常量
FIELD,
// 只能修饰方法
METHOD,
// 只能修饰参数
PARAMETER,
// 只能修饰构造函数
CONSTRUCTOR,
// 只能修饰局部变量
LOCAL_VARIABLE,
// 只能修饰Annotation(注解)
ANNOTATION_TYPE,
// 只能修饰包定义
PACKAGE,
// 只能修饰泛型的类型(jdk1.8之后的新特性 类型注解)
TYPE_PARAMETER,
// 只能写在使用类型的任何语句中(jdk1.8之后的新特性 类型注解)
TYPE_USE
}
2. 保持性策略(@Retention)
@Retention申明注解的生命周期,即约束自定义注解的存活范围
@Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下
- SOURCE: 注解只作用在源码阶段,生成的字节码文件中不存在
- CLASS: 注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
- RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
用@Retention指定RetentionPolicy
public enum RetentionPolicy {
// 此类型会被编译器丢弃
SOURCE,
// 此类型注解会保留在class文件中,但JVM会忽略它
CLASS,
// 此类型注解会保留在class文件中,JVM会读取它
RUNTIME
}
3. 文档化功能(@Documented)
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例
如javadoc此类的工具文档化。
Documented是一个标记注解,没有成员。
4. 标注继承(@Inherited)
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。
如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被自动用于该
class的子类。
*:@Inherited annotation类型是被标注过的class的子类所继承。
类并不从它所实现的接口继承 annotation,方法并不从它所重载的方法继承annotation。
5. 标注是否可重复使用(@Repeatable)
@Repeatable:标注某注解可以在同一个声明上使用多次
5.1. Repeatable
当我们需要重复使用某个注解时,希望利用相同的注解来表现所有的形式时,我们可以借助@Repeatable
注解。
5.2. 实例
在生活中一个人往往是具有多种身份,例如我是一家公司的老板,同时我还是我妻子的丈夫,更是我父母
的孩子,如果希望借助注解的方式来表达该如何呢?
- 首先定义一个Persons类来表示我所有的身份:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Persons {
Person[] value();
}
这里@Target是声明Persons注解的作用范围,参数ElementType.Type代表可以给一个类型进行注解,比
如类,接口,枚举。
@Retention是注解的有效时间,RetentionPolicy.RUNTIME是指程序运行的时候。
- 接下来我们就定义一个注解,这里用到了@Repeatable注解,来真正表达我们的身份:
@Repeatable(Persons.class)
public @interface Person{
String role() default "";
}
@Repeatable括号内的就相当于用来保存该注解内容的容器。
- 然后,为“我”来创建一个实体类:
@Person(role="CEO")
@Person(role="husband")
@Person(role="father")
@Person(role="son")
public class Man {
String name="";
}
- 最后测试一下,获取所有的身份信息并输出:
public static void main(String[] args) {
Annotation[] annotations = Man.class.getAnnotations();
System.out.println(annotations.length);
Persons p1=(Persons) annotations[0];
for(Person t:p1.value()){
System.out.println(t.role());
}
}
if(Man.class.isAnnotationPresent(Persons.class)) {
Persons p2=Man.class.getAnnotation(Persons.class);
for(Person t:p2.value()){
System.out.println(t.role());
}
}
以上两种方式都能得到如下输出结果:
1
CEO
husband
father
son
四、内置注解
1. @Override
当我们想要复写父类中的方法时,我们需要使用该注解去告知编译器我们想要复写这个方法。
这样一来当父类中的方法移除或者发生更改时编译器将提示错误信息。
2. @Deprecated
当我们希望编译器知道某一方法不建议使用时,我们应该使用这个注解。
Java在javadoc 中推荐使用该注解,我们应该提供为什么该方法不推荐使用以及替代的方法。
3. @SuppressWarnings
这个仅仅是告诉编译器忽略特定的警告信息,例如在泛型中使用原生数据类型。
它的保留策略是SOURCE(译者注:在源文件中有效)并且被编译器丢弃。
其参数有:
- deprecation,使用了过时的类或方法时的警告
- unchecked,执行了未检查的转换时的警告
- fallthrough,当 switch 程序块直接通往下一种情况而没有 break 时的警告
- path,在类路径、源文件路径等中有不存在的路径时的警告
- serial,当在可序列化的类上缺少serialVersionUID 定义时的警告
- finally ,任何 finally 子句不能正常完成时的警告
- all,关于以上所有情况的警告
4. @SafeVarargs
修饰”堆污染”警告
Java 7之前在使用可变长参数的方法时,如果参数传递的是不可具体化的类型(如泛型类型List)会产生警
告信息,如果希望进制该警告,需要使用@SuppressWarnings("unchecked")注解进行声明,Java 7中,
如果开发人员确信某个使用了可变长参数的方法在与泛型类一起使用时不会出现类型安全问题,就可以使
用@SafeVarargs注解来声明。注意该注解只能用于可变长参数的方法或者构造方法,并且方法必须声明为
static或final。
5. @FunctionalInterface
Java8开始提供的函数式接口
@FunctionalInterface标记在接口上,函数式接口”是指仅仅只包含一个抽象方法的接口。
1、该注解只能标记在"有且仅有一个抽象方法"的接口上。
2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
4、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。
加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了
@FunctionInterface,那么编译器会报错。
五、注解使用机制
当Java源代码被编译时,编译器的一个插件annotation处理器则会处理这些annotation。处理器可以产生
报告信息,或者创建附加的Java源文件或资源。如果annotation本身被加上了RententionPolicy的运行时
类,则Java编译器则会将annotation的元数据存储到class文件中。
然后,Java虚拟机或其他的程序可以查找这些元数据并做相应的处理。
当然除了annotation处理器可以处理annotation外,我们也可以使用反射自己来处理annotation。
Java5有一个名为AnnotatedElement的接口,Java的反射对象类Class,Constructor,Field,Method以及
Package都实现了这个接口。
这个接口用来表示当前运行在Java虚拟机中的被加上了annotation的程序元素。通过这个接口可以使用反
射读取annotation。
AnnotatedElement接口可以访问被加上RUNTIME标记的annotation,相应的方法
getAnnotation,getAnnotations,isAnnotationPresent。
由于Annotation类型被编译和存储在二进制文件中就像class一样,所以可以像查询普通的Java对象一样查
询这些方法返回的Annotation。
六、注解能标注什么?
Java 语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注
七、注解作用
对 Java 中类、方法、成员变量做标记,然后进行特殊处理
例如:Junit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试
方法执行
八、如何定义注解
1. 格式
public @interface 注解名称 {
public 属性类型 属性名() default 默认值 ;
}
2. 属性类型
Java支持的数据类型基本上都支持
3. 特殊属性
value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写
但是如果有多个属性, 且多个属性没有默认值,那么value名称是不能省略的
九、注解解析
注解操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容
1. 与注解解析相关的接口
Annotation:注解的顶级接口,注解都是Annotation类型的对象
AnnotatedElement:该接口定义了与注解解析相关的解析方法
Annotation[] getDeclaredAnnotations():获得当前对象上使用的所有注解,返回注解数组
T getDeclaredAnnotation(Class<T> annotationClass):根据注解类型获得对应注解对象
boolean isAnnotationPresent(Class<Annotation> annotationClass):判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false,所有的类成分Class,Method , Field, Constructor,都实现了AnnotatedElement接口他们都拥有解析注解的能力:
2. 解析注解的技巧
- 注解在哪个成分上,我们就先拿哪个成分对象
- 比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解
- 比如注解作用在类上,则要该类的Class对象,再来拿上面的注解
- 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解
十、代码演示
1. 学会自定义注解
1.1. Book
定义书籍注解,两个属性,值,书籍价格
public @interface Book {
String value(); // 特殊属性
double price() ;
//double price() default 9.9;
}
1.2. Bookk
定义第二个书籍注解,值,书籍作者,书籍价格,赋默认值100。
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Bookk {
String value();
String[] author();
double price() default 100;
}
1.3. MyBook
定义第三个书籍注解,书籍名称,书籍作者,书籍价格。
public @interface MyBook {
String name();
String[] authors();
double price();
}
1.4. Anno tationDemo1
/**
目标:学会自定义注解。掌握其定义格式和语法。
*/
@MyBook(name="《精通JavaSE》",authors = {"政哥", "dlei"} , price = 199.5)
//@Book(value = "/delete")
// @Book("/delete")
@Book(value = "/delete", price = 23.5)
//@Book("/delete")
public class AnnotationDemo1 {
@MyBook(name="《精通JavaSE2》",authors = {"政哥", "dlei"} , price = 199.5)
private AnnotationDemo1(){
}
@MyBook(name="《精通JavaSE1》",authors = {"政哥", "dlei"} , price = 199.5)
public static void main(String[] args) {
@MyBook(name="《精通JavaSE2》",authors = {"政哥", "dlei"} , price = 199.5)
int age = 21;
}
}
1.5. MyTest
定义测试类,@Target 限制注解的使用范围,@Retention指定生命周期
@Target({ElementType.METHOD,ElementType.FIELD}) // 元注解
@Retention(RetentionPolicy.RUNTIME) // 一直活着,在运行阶段这个注解也不消失
public @interface MyTest {
}
2. 认识元注解
AnnotationDemo2
/**
目标:认识元注解
*/
//@MyTest // 只能注解方法和成员变量
public class AnnotationDemo2 {
@MyTest
private String name;
@MyTest
public void test(){
}
public static void main(String[] args) {
}
}
3. 完成注解的解析
AnnotationDemo3
/**
目标:完成注解的解析
*/
public class AnnotationDemo3 {
@Test
public void parseClass(){
// a.先得到类对象
Class c = BookStore.class;
// b.判断这个类上面是否存在这个注解
if(c.isAnnotationPresent(Bookk.class)){
//c.直接获取该注解对象
Bookk book = (Bookk) c.getDeclaredAnnotation(Bookk.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.author()));
}
}
@Test
public void parseMethod() throws NoSuchMethodException {
// a.先得到类对象
Class c = BookStore.class;
Method m = c.getDeclaredMethod("test");
// b.判断这个类上面是否存在这个注解
if(m.isAnnotationPresent(Bookk.class)){
//c.直接获取该注解对象
Bookk book = (Bookk) m.getDeclaredAnnotation(Bookk.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.author()));
}
}
}
@Bookk(value = "《情深深雨濛濛》", price = 99.9, author = {"琼瑶", "dlei"})
class BookStore{
@Bookk(value = "《三少爷的剑》", price = 399.9, author = {"古龙", "熊耀华"})
public void test(){
}
}
4. 注解测试
AnnotationDemo4
public class AnnotationDemo4 {
public void test1(){
System.out.println("===test1===");
}
@MyTest
public void test2(){
System.out.println("===test2===");
}
@MyTest
public void test3(){
System.out.println("===test3===");
}
/**
启动菜单:有注解的才被调用。
*/
public static void main(String[] args) throws Exception {
AnnotationDemo4 t = new AnnotationDemo4();
// a.获取类对象
Class c = AnnotationDemo4.class;
// b.提取全部方法
Method[] methods = c.getDeclaredMethods();
// c.遍历方法,看是否有MyTest注解,有就跑它
for (Method method : methods) {
if(method.isAnnotationPresent(MyTest.class)){
// 跑它
method.invoke(t);
}
}
}
}
十一、重复注解与类型注解(JDK8新特性)
1. 简介
自从Java 5中引入注解以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。
不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。
JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。
在JDK 8中使用@Repeatable注解定义重复注解
2. 重复注解的使用步骤
2.1. 定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyTests {
MyTest[] value();
}
2.2. 定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
String value();
}
2.3. 配置多个重复的注解
@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")
public class Demo01 {
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
}
}
2.4. 解析得到指定注解
// 3.配置多个重复的注解
@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")
public class Demo01 {
@Test
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
// 4.解析得到类上的指定注解
MyTest[] tests = Demo01.class.getAnnotationsByType(MyTest.class);
for (MyTest test : tests) {
System.out.println(test.value());
}
// 得到方法上的指定注解
Annotation[] tests1 =
Demo01.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (Annotation annotation : tests1) {
System.out.println("annotation = " + annotation);
}
}
}
3. 类型注解的使用
JDK 8为@Target元注解新增了两种类型:TYPE_PARAMETER,TYPE_USE
① TYPE_PARAMETER:表示该注解能写在类型参数的声明语句中。 类型参数声明如:
② TYPE_USE:表示注解可以再任何用到类型的地方使用
3.1. TYPE_PARAMETER的使用
@Target(ElementType.TYPE_PARAMETER)
@interface TyptParam {
}
public class Demo02<@TyptParam T> {
public static void main(String[] args) {
}
public <@TyptParam E> void test(String a) {
}
}
3.2. TYPE_USE的使用
@Target(ElementType.TYPE_USE)
@interface NotNull {
}
public class Demo02<@TyptParam T extends String> {
private @NotNull
int a = 10;
public static void main(@NotNull String[] args) {
@NotNull int x = 1;
@NotNull String s = new @NotNull String();
}
public <@TyptParam E> void test(String a) {
}
}
4. 总结
通过@Repeatable元注解可以定义可重复注解, TYPE_PARAMETER 可以让注解放在泛型上,
TYPE_USE 可以让注解放 在类型的前面
十二、应用场景
1. 业务场景1:权限校验
自定义注解经常和springAOP一起配合使用,能让aop的增强更加轻松和优雅,消除冗余代码。
例如日志,登录校验,权限校验等。
2. 业务场景2:敏感数据的脱敏
在很多项目中,存储了大量的用户敏感信息,而这些信息如果直接显示在网页上,是具有很大的安全问
题,所以我们需要在数据向网页展示时,先经过脱敏的处理。
所谓脱敏其实就是敏感数据的半隐藏,例如身份证,手机号,用户名