企业服务网站制作,百度为什么打不开网页无法访问,wordpress实现文章目录,做网站 赚钱多吗Java注解以及自定义注解
要深入学习注解#xff0c;我们就必须能定义自己的注解#xff0c;并使用注解#xff0c;在定义自己的注解之前#xff0c;我们就必须要了解Java为
我们提供的元注解和相关定义注解的语法。
1、注解
1.1 注解的官方定义
注解是一种元数据形式。…Java注解以及自定义注解
要深入学习注解我们就必须能定义自己的注解并使用注解在定义自己的注解之前我们就必须要了解Java为
我们提供的元注解和相关定义注解的语法。
1、注解
1.1 注解的官方定义
注解是一种元数据形式。即注解是属于java的一种数据类型和类、接口、数组、枚举类似。
注解用来修饰类、方法、变量、参数、包。
注解不会对所修饰的代码产生直接的影响。
1.2 注解的使用范围
注解有许多用法其中有为编译器提供信息 - 注解能被编译器检测到错误或抑制警告。编译时和部署时的处理 -
软件工具能处理注解信息从而生成代码XML文件等等。运行时的处理 - 有些注解在运行时能被检测到。
2、元注解
一个最最基本的注解定义就只包括了两部分内容1、注解的名字2、注解包含的类型元素。但是我们在使用
JDK自带注解的时候发现有些注解只能写在方法上面比如Override有些却可以写在类的上面比如
Deprecated。当然除此以外还有很多细节性的定义那么这些定义该如何做呢接下来就该元注解出场了
元注解专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。Java5.0定义了4个标准
的meta-annotation类型它们被用来提供对其它 annotation类型作说明。
Java5.0 定义的元注解
1、Target
2、Retention
3、Documented
4、Inherited
这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应
分参数的使用说明。
2.1 Target
Target注解是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。
Target说明了Annotation所修饰的对象范围Annotation可被用于 packages、types类、接口、枚举、
Annotation类型、类型成员方法、构造方法、成员变量、枚举值、方法参数和本地变量如循环变量、
catch参数。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用用于描述注解的使用范围即被描述的注解可以用在什么地方
取值(ElementType)有
1、CONSTRUCTOR用于描述构造器
2、FIELD用于描述域
3、LOCAL_VARIABLE用于描述局部变量
4、METHOD用于描述方法
5、PACKAGE用于描述包
6、PARAMETER用于描述参数
7、TYPE用于描述类、接口(包括注解类型) 或enum声明
它使用一个枚举类型定义如下
public enum ElementType {/** 类接口包括注解类型或枚举的声明 */TYPE,/** 属性的声明 */FIELD,/** 方法的声明 */METHOD,/** 方法形式参数声明 */PARAMETER,/** 构造方法的声明 */CONSTRUCTOR,/** 局部变量声明 */LOCAL_VARIABLE,/** 注解类型声明 */ANNOTATION_TYPE,/** 包的声明 */PACKAGE,TYPE_PARAMETER,TYPE_USE
}使用实例
package com.test3;import java.lang.annotation.ElementType;
import java.lang.annotation.Target;Target(ElementType.TYPE)
public interface Table {/*** 数据表名称注解默认值为类名称* return*/public String tableName() default className;
}package com.test3;import java.lang.annotation.ElementType;
import java.lang.annotation.Target;Target(ElementType.FIELD)
public interface NoDBColumn {
}package com.test3;import java.lang.annotation.ElementType;
import java.lang.annotation.Target;//CherryAnnotation被限定只能使用在类、接口或方法上面
Target(value {ElementType.TYPE, ElementType.METHOD})
public interface CherryAnnotation {String name();int age() default 18;int[] array();
}注解Table可以用于注解类、接口(包括注解类型) 或enum声明而注解NoDBColumn仅可用于注解类的成员变
量。
2.2 Retention
Retention注解翻译为持久力、保持力。即用来修饰自定义注解的生命力。
注解的生命周期有三个阶段1、Java源文件阶段2、编译到class文件阶段3、运行期阶段。同样使用了
RetentionPolicy 枚举类型定义了三个阶段
作用表示需要在什么级别保存该注释信息用于描述注解的生命周期即被描述的注解在什么范围内有效
取值RetentionPoicy有
1、SOURCE在源文件中有效即源文件保留
2、CLASS在class文件中有效即class保留
3、RUNTIME在运行时有效即运行时保留
Retention meta-annotation类型有唯一的value作为成员它的取值来自
java.lang.annotation.RetentionPolicy的枚举类型值。
public enum RetentionPolicy {// 注解将被编译器忽略掉SOURCE,// 注解将被编译器记录在class文件中但在运行时不会被虚拟机保留这是一个默认的行为CLASS,// 注解将被编译器记录在class文件中而且在运行时会被虚拟机保留因此它们能通过反射被读取到RUNTIME
}我们再详解一下
如果一个注解被定义为RetentionPolicy.SOURCE则它将被限定在Java源文件中那么这个注解即不会参与编
译也不会在运行期起任何作用这个注解就和一个注释是一样的效果只能被阅读Java文件的人看到
如果一个注解被定义为RetentionPolicy.CLASS则它将被编译到Class文件中那么编译器可以在编译时根据
注解做一些处理动作但是运行时JVMJava虚拟机会忽略它我们在运行期也不能读取到
如果一个注解被定义为RetentionPolicy.RUNTIME那么这个注解可以在运行期的加载阶段被加载到Class对象
中。那么在程序运行阶段我们可以通过反射得到这个注解并通过判断是否有这个注解或这个注解中属性的值
从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME在默认
的情况下自定义注解是使用的RetentionPolicy.CLASS。
具体实例如下
package com.test3;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
public interface Column {public String name() default fieldName;public String setFuncName() default setField;public String getFuncName() default getField;public boolean defaultDBValue() default false;
}Column注解的的RetentionPolicy的属性值是RUNTIME这样注解处理器可以通过反射获取到该注解的属性
值从而去做一些运行时的逻辑处理。
2.3 Documented
Documented注解是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中因此可以
被例如javadoc此类的工具文档化。
Documented是一个标记注解没有成员。
package com.test3;import java.lang.annotation.*;Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface Column1 {public String name() default fieldName;public String setFuncName() default setField;public String getFuncName() default getField;public boolean defaultDBValue() default false;
}2.4 Inherited
Inherited注解是指定某个自定义注解如果写在了父类的声明部分那么子类的声明部分也能自动拥有该注
解。Inherited注解只对那些Target被定义为ElementType.TYPE的自定义注解起作用。
Inherited 元注解是一个标记注解Inherited阐述了某个被标注的类型是被继承的。如果一个使用了
Inherited修饰的annotation类型被用于一个class则这个annotation将被用于该class的子类。
注意Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation
方法并不从它所重载的方法继承annotation。
当Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME则反射API增强了这
种继承性。如果我们使用java.lang.reflect去查询一个Inherited annotation类型的annotation时反射代码检查
将展开工作检查class和其父类直到发现指定的annotation类型被发现或者到达类继承结构的顶层。
实例代码
package com.test3;import java.lang.annotation.Inherited;Inherited
public interface Greeting {public enum FontColor {BULE, RED, GREEN};String name();FontColor fontColor() default FontColor.GREEN;
}注解的继承依赖如下一个因素
1、首先要想Annotation能被继承需要在注解定义的时候加上Inherited并且如果要被反射应用的话还
需要Retention(RetentionPolicy.RUNTIME)标识。
2、JDK文档中说明的是只有在类上应用Annotation才能被继承而实际应用结果是除了类上应用的
Annotation能被继承外没有被重写的方法的Annotation也能被继承。
3、当方法被重写后Annotation不会被继承。
4、Annotation的继承不能应用在接口上。
3、自定义注解
使用interface自定义注解时自动继承了java.lang.annotation.Annotation接口由编译程序自动完成其他细
节。在定义注解时不能继承其他的注解或接口。interface用来声明一个注解其中的每一个方法实际上是声
明了一个配置参数。方法的名称就是参数的名称返回值类型就是参数的类型返回值类型只能是基本类型、
Class、String、enum。可以通过default来声明参数的默认值。
3.1 定义注解格式
public interface 注解名 {定义体}public interface CherryAnnotation {
}根据我们在自定义类的经验在类的实现部分无非就是书写构造、属性或方法。但是在自定义注解中其实现
只能定义一个东西注解类型元素annotation type element。语法
public interface CherryAnnotation {public String name();public int age();public int[] array();
}public interface CherryAnnotation {public String name();public int age() default 18;public int[] array();
}3.2 注解参数的可支持数据类型
1、所有基本数据类型int,float,boolean,byte,double,char,long,short)
2、String类型
3、Class类型
4、enum类型
5、Annotation类型
6、以上所有类型的数组
注解里面定义的是注解类型元素
3.3 定义注解类型元素时需要注意如下几点
1、只能用public或默认(default)这两个访问权修饰例如String value();这里把方法设为default默认类型。
2、参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,
Enum,Class,annotations等数据类型以及这一些类型的数组。
3、如果只有一个参数成员最好把参数名称设为value后加小括号。
4、()不是定义方法参数的地方也不能在括号中定义任何参数仅仅只是一个特殊的语法。
5、default代表默认值值必须和第2点定义的类型一致。
6、如果没有默认值代表后续使用注解时必须给该类型元素赋值。
可以看出注解类型元素的语法非常奇怪即又有属性的特征可以赋值,又有方法的特征打上了一对括
号。但是这么设计是有道理的我们在后面的章节中可以看到注解在定义好了以后使用的时候操作元素类型
像在操作属性解析的时候操作元素类型像在操作方法。
3.4 简单的自定义注解和使用注解实例
package com.test;import java.lang.annotation.*;/*** 水果名称注解*/
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface FruitName {String value() default ;
}package com.test;import java.lang.annotation.*;/*** 水果颜色注解*/
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface FruitColor {/*** 颜色枚举*/public enum Color {BULE, RED, GREEN};/*** 颜色属性** return*/Color fruitColor() default Color.GREEN;}package com.test;public class Apple {FruitName(Apple)private String appleName;FruitColor(fruitColor FruitColor.Color.RED)private String appleColor;public void setAppleColor(String appleColor) {this.appleColor appleColor;}public String getAppleColor() {return appleColor;}public void setAppleName(String appleName) {this.appleName appleName;}public String getAppleName() {return appleName;}
}3.5 注解元素的默认值
注解元素必须有确定的值要么在定义注解的默认值中指定要么在使用注解时指定非基本类型的注解元素的值
不可为null。因此使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存
在或缺失的状态因为每个注解的声明中所有元素都存在并且都具有相应的值为了绕开这个约束我们只能
定义一些特殊的值例如空字符串或者负数一次表示某个元素不存在在定义注解时这已经成为一个习惯用
法。
package com.test;import java.lang.annotation.*;/*** 水果供应者注解*/
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface FruitProvider {/*** 供应商编号** return*/public int id() default -1;/*** 供应商名称** return*/public String name() default ;/*** 供应商地址** return*/public String address() default ;
}3.6 特殊语法
特殊语法一
如果注解本身没有注解类型元素那么在使用注解的时候可以省略()直接写为注解名它和标准语法
注解名()等效
Retention(RetentionPolicy.RUNTIME)
Target(value {ElementType.TYPE})
Documented
public interface FirstAnnotation {
}//等效于 FirstAnnotation()
FirstAnnotation
public class JavaBean{
}特殊语法二
如果注解本身只有一个注解类型元素而且命名为value那么在使用注解的时候可以直接使用
注解名(注解值)其等效于注解名(value 注解值)
Retention(RetentionPolicy.RUNTIME)
Target(value {ElementType.TYPE})
Documented
public interface SecondAnnotation {String value();
}//等效于 SecondAnnotation(value this is second annotation)
SecondAnnotation(this is annotation)
public class JavaBean{
}特殊用法三
如果注解中的某个注解类型元素是一个数组类型在使用时又出现只需要填入一个值的情况那么在使用注解时可
直接写为注解名(类型名 类型值)它和标准写法注解名(类型名 {类型值})等效
Retention(RetentionPolicy.RUNTIME)
Target(value {ElementType.TYPE})
Documented
public interface ThirdAnnotation {String[] name();
}//等效于 ThirdAnnotation(name {this is third annotation})ThirdAnnotation(name this is third annotation)
public class JavaBean{
}特殊用法四
如果一个注解的Target是定义为Element.PACKAGE那么这个注解是配置在package-info.java中的而不能
直接在某个类的package代码上面配置。
上面三节定义了注解并在需要的时候给相关类类属性加上注解信息如果没有响应的注解信息处理流程注解
可以说是没有实用价值。如何让注解真真的发挥作用主要就在于注解处理方法下一步我们将学习注解信息的获
取和处理
4、自定义注解的配置使用
基于上一节已对注解有了一个基本的认识注解其实就是一种标记可以在程序代码中的关键节点类、方法、
变量、参数、包上打上这些标记然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作。因此
可以得出自定义注解使用的基本流程
第一步定义注解——相当于定义标记
第二步配置注解——把标记打在需要用到的程序代码中
第三步解析注解——在编译期或运行时检测到标记并进行特殊操作。 4.1 在具体的Java类上使用注解
首先定义一个注解和一个供注解修饰的简单Java类。
package com.test1;import java.lang.annotation.*;Retention(RetentionPolicy.RUNTIME)
Target(value {ElementType.METHOD})
Documented
public interface CherryAnnotation {String name();// 类型元素int age() default 18;int[] score();
}package com.test1;public class Student {public void study(int times) {for (int i 0; i times; i) {System.out.println(Good Good Study, Day Day Up!);}}
}简单分析下
CherryAnnotation的Target定义为ElementType.METHOD那么它书写的位置应该在方法定义的上方即
public void study(int times)之上。由于我们在CherryAnnotation中定义的有注解类型元素而且有些元素是没有
默认值的这要求我们在使用的时候必须在标记名后面打上()并且在()内以“元素名元素值“的形式挨个填上所有
没有默认值的注解类型元素有默认值的也可以填上重新赋值中间用“,”号分割。
所以最终书写形式如下
package com.test1;public class Student {CherryAnnotation(name cherry-peng, age 23, score {99, 66, 77})public void study(int times) {for (int i 0; i times; i) {System.out.println(Good Good Study, Day Day Up!);}}
}4.2 自定义注解的运行时解析(反射操作获取注解)
这一节是使用注解的核心读完此节即可明白如何在程序运行时检测到注解并进行一系列特殊操作
只有当注解的保持力处于运行阶段即使用Retention(RetentionPolicy.RUNTIME)修饰注解时才能在JVM
运行时检测到注解并进行一系列特殊操作。
在运行期探究和使用编译期的内容编译期配置的注解要用到Java中的灵魂技术——反射
Java SE5扩展了反射机制的API以帮助程序员快速的构造自定义注解处理器。
package com.test1;import java.lang.reflect.Method;/*** author zhangshixing* date 2021年11月01日 9:32*/
public class TestAnnotation {public static void main(String[] args){try {//获取Student的Class对象Class stuClass Class.forName(com.test1.Student);//说明一下这里形参不能写成Integer.class应写为int.classMethod stuMethod stuClass.getMethod(study,int.class);if(stuMethod.isAnnotationPresent(CherryAnnotation.class)){System.out.println(Student类上配置了CherryAnnotation注解);//获取该元素上指定类型的注解CherryAnnotation cherryAnnotation stuMethod.getAnnotation(CherryAnnotation.class);System.out.println(name: cherryAnnotation.name() , age: cherryAnnotation.age() , score: cherryAnnotation.score()[0]);}else{System.out.println(Student类上没有配置CherryAnnotation注解);}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}}
}# 程序输出
Student类上配置了CherryAnnotation注解
name: cherry-peng, age: 23, score: 994.3 注解处理器类库(java.lang.reflect.AnnotatedElement)
Java使用Annotation接口来代表程序元素前面的注解该接口是所有Annotation类型的父接口。除此之外Java
在java.lang.reflect 包下新增了AnnotatedElement接口该接口代表程序中可以接受注解的程序元素该接
口主要有如下几个实现类
Class类定义
Constructor构造器定义
Field类的成员变量定义
Method类的方法定义
Package类的包定义
java.lang.reflect包下主要包含一些实现反射功能的工具类实际上java.lang.reflect 包所有提供的反射API
扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后该注解才能
是运行时可见当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
AnnotatedElement 接口是所有程序元素Class、Method和Constructor的父接口所以程序通过反射获取了
某个类的AnnotatedElement对象之后程序就可以调用该对象的如下四个个方法来访问Annotation信息
isAnnotationPresent(Class? extends Annotation annotationClass)方法是专门判断该元素上是否配
置有某个指定的注解
getAnnotation(ClassA annotationClass)方法是获取该元素上指定的注解。之后再调用该注解的注解类型
元素方法就可以获得配置时的值数据如果该类型注解不存在则返回null。
反射对象上还有一个方法getAnnotations()该方法可以获得该对象身上配置的所有的注解。它会返回给我们
一个注解数组需要注意的是该数组的类型是Annotation类型这个Annotation是一个来自于
java.lang.annotation包的接口。
Annotation[] getDeclaredAnnotations()返回直接存在于此元素上的所有注解。与此接口中的其他方法不
同该方法将忽略继承的注解。如果没有注解直接存在于此元素上则返回长度为零的一个数组。该方法的调
用者可以随意修改返回的数组这不会对其它调用者返回的数组产生任何影响。
如果我们要获得的注解是配置在方法上的那么我们要从Method对象上获取如果是配置在属性上就需要从该
属性对应的Field对象上去获取如果是配置在类型上需要从Class对象上去获取。总之在谁身上就从谁身上去
获取
一个简单的注解处理器
/***********注解声明***************/package com.test2;import java.lang.annotation.*;/*** 水果名称注解*/
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface FruitName {String value() default ;
}package com.test2;import java.lang.annotation.*;/*** 水果颜色注解*/
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface FruitColor {/*** 颜色枚举** author peida*/public enum Color {BULE, RED, GREEN};/*** 颜色属性** return*/Color fruitColor() default Color.GREEN;}package com.test2;import java.lang.annotation.*;/*** 水果供应者注解*/
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface FruitProvider {/*** 供应商编号** return*/public int id() default -1;/*** 供应商名称** return*/public String name() default ;/*** 供应商地址** return*/public String address() default ;
}/***********注解使用***************/package com.test2;public class Apple {FruitName(Apple)private String appleName;FruitColor(fruitColor FruitColor.Color.RED)private String appleColor;FruitProvider(id 1, name 陕西红富士集团, address 陕西省西安市延安路89号红富士大厦)private String appleProvider;public void setAppleColor(String appleColor) {this.appleColor appleColor;}public String getAppleColor() {return appleColor;}public void setAppleName(String appleName) {this.appleName appleName;}public String getAppleName() {return appleName;}public void setAppleProvider(String appleProvider) {this.appleProvider appleProvider;}public String getAppleProvider() {return appleProvider;}public void displayName() {System.out.println(水果的名字是苹果);}
}/***********注解处理器***************/package com.test2;import java.lang.reflect.Field;public class FruitInfoUtil {public static void getFruitInfo(Class? clazz) {String strFruitName 水果名称;String strFruitColor 水果颜色;String strFruitProvicer 供应商信息;Field[] fields clazz.getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(FruitName.class)) {FruitName fruitName (FruitName) field.getAnnotation(FruitName.class);strFruitName strFruitName fruitName.value();System.out.println(strFruitName);} else if (field.isAnnotationPresent(FruitColor.class)) {FruitColor fruitColor (FruitColor) field.getAnnotation(FruitColor.class);strFruitColor strFruitColor fruitColor.fruitColor().toString();System.out.println(strFruitColor);} else if (field.isAnnotationPresent(FruitProvider.class)) {FruitProvider fruitProvider (FruitProvider) field.getAnnotation(FruitProvider.class);strFruitProvicer 供应商编号 fruitProvider.id() 供应商名称 fruitProvider.name() 供应商地址 fruitProvider.address();System.out.println(strFruitProvicer);}}}
}/***********输出结果***************/package com.test2;public class FruitRun {/*** param args*/public static void main(String[] args) {FruitInfoUtil.getFruitInfo(Apple.class);}}# 程序输出水果名称Apple水果颜色RED供应商编号1 供应商名称陕西红富士集团 供应商地址陕西省西安市延安路89号红富士大厦