Java注解介绍
注解是java语言用于工具处理的标注
从JVM的角度来看,注解本身对代码逻辑没有任何影响,想要实现的功能完全由程序代码决定
注解的分类(按注解的生命周期):
SOURCE:
形如@Override等,不会被编译到.class文件中,在编译期由编译期进行覆写方法的检查,编译后就被忽略了。CLASS:
还有一些会被编译到.class,但JVM加载.class文件时不会把这类注解放到内存中,一般被一些底层库使用。RUNTIME:
运行期注解,最常见的注解,会被加载到JVM中,在运行时可以获取注解信息。
注解的参数:
支持的参数类型:基本类型、String、Class以及枚举的数组。
配置参数必须是常量,大部分注解会有名为value的参数,对此参数赋值,可以只写常量,相当于省略了value参数。
例如:@Service(value=“TestServiceName”)可以写成@Service(“TestServiceName”)
如何自定义Java注解
Java语言使用@interface语法来定义注解(Annotation)
// 看一看@RestController注解源码
@Target(ElementType.TYPE) //表示@RestController可以定义在类上
@Retention(RetentionPolicy.RUNTIME) //运行期注解,可以在运行时获取注解信息并处理
@Documented // 表示该注解会出现在javadoc文档中
// RestController集成了@Controller和@ResponseBody,所以使用@RestController相当于同时使用了Controller和responseBody两个注解
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(annotation = Controller.class)
String value() default "";
}
@Target、@Retention、@Documented都是用于修饰其他注解的元注解
我们使用这些元注解来自定义注解。
Java内置元注解说明
-
@Retention(RetentionPolicy)
用来标识注解的生命周期(如果@Retention不存在,默认为CLASS)
RetentionPolicy.SOURCE 仅编译期
RetentionPolicy.CLASS (默认) 仅class文件
RetentionPolicy.RUNTIME 运行期
通常我们自定义的Annotation都是RUNTIME所以,务必加上@Retention(RetentionPolicy.RUNTIME) -
@Target(ElementType)
可以定义Annotation能够被应用于哪些注解
ELementType.TYPE 类或接口
ELementType.FIELD 字段
ElementType.METHOD 方法
ElementType.PARAMETER 方法参数
ElementType.CONSTRUCTOR 构造方法
@Target可以接收ElementType数组:
@Target([
ElementType.METHOD,
ElementType.FIELD
])
表示被修饰的注解可以应用于方法和字段上
-
@Documented
标识注解是否成为api的一部分 -
@Inherited
(了解,应用不广泛)
使用@Inherited定义子类是否可继承父类定义的Annotation,例如B继承了A,A添加了注解C(该注解被@Inherited修饰),那么B也添加了注解C(可以获取注解C上的信息)
@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效(类),并且仅针对class的继承,对interface的继承无效 -
@Repeatable
(了解,应用不广泛,可以定义注解是否可以重复)
注解默认只能直接修饰某个元素一次,如果修饰了多次那么会出现编译异常,如果想要注解在某个元素上出现多次,可以使用@Repeatable
自定义注解小结
- Java使用@interface定义注解;
- 可定义多个参数和默认值,核心参数使用value名称;
- 必须设置@Target来指定Annotation可以应用的范围;
- 应当设置@Retention(RetentionPolicy.RUNTIME)便于运行期读取该Annotation。
使用注解,给注解添加逻辑
单纯标注注解并不会对程序逻辑有任何影响,注解的功能由读取它并执行逻辑的代码决定
注解定义后也属于class,所有注解都继承自java.lang.annotation.Annotation
读取java注解,需要使用反射API
// @Report注解示例
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
Java提供使用反射API读取Annotation的方法包括:
- 判断注解是否存在于Class\FIeld\Method\Constructor:
- Class.isAnnotationPresent(Class)
- Field.isAnnotationPresent(Class)
- Method.isAnnotationPresent(Class)
- Constructor.isAnnotationPresent(Class)
例如:判断@Report是否存在于User类
User.class.isAnnotationPresent(Report.class);
- 使用反射API读取注解信息
- XXX.getAnnotation(Class)
例如:获取User类中定义的@Report注解
// 当注解是一种接口,获取之后可以调用接口方法得到定义的值
Report report = User.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();
读取方法、字段和构造方法的Annotation和Class类似。
(方法参数上的注解,不感兴趣可以忽略这个部分)
但要读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。例如,对于以下方法定义的注解:
public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}
要读取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解:
// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
if (anno instanceof Range) { // @Range注解
Range r = (Range) anno;
}
if (anno instanceof NotNull) { // @NotNull注解
NotNull n = (NotNull) anno;
}
}
读取注解时一般可以先判断注解是否存在,再读取注解,或者直接读取,不存在则返回null
自定义注解Demo
JVM不会对自定义的注解添加任何逻辑,怎样处理完全由Java代码决定
下边定义一个注解@Range用来校验对象的字段长度是否符合自定义约束
Range.class
package com.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author sunhongmin
* @Date 2020-10-21
* @Describe 字段长度约束,默认0-255
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Range {
int min() default 0;
int max() default 255;
}
RangeTestDemo.class
package com.annotation;
import java.lang.reflect.Field;
class User {
/**
* 最大长度为10
*/
@Range(max = 10)
public String name;
/**
* 使用默认值255
*/
@Range
public String address;
public User() {
}
public User(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
public class RangeTestDemo {
public static void checkRange(User user) throws IllegalAccessException {
Field[] declaredFields = user.getClass().getDeclaredFields();
for (Field field : declaredFields) {
// 如果标注了Range注解
if (field.isAnnotationPresent(Range.class)) {
// 获取注解
Range range = field.getAnnotation(Range.class);
// 获取field值
Object value = field.get(user);
if (value instanceof String) {
String str = (String) value;
if (str.length() < range.min() || str.length() > range.max()) {
throw new IllegalArgumentException("Invalid field:{" + field.getName() + "},value:{" + str + "},Limit range [" + range.min() + "," + range.max() + "]");
}
}
}
}
}
public static void main(String[] args) throws IllegalAccessException {
User user = new User("张大彪啊啊啊啊啊啊啊asdasdasdasda", "地址在哪呢在哪在哪在哪在哪在哪在哪在哪在哪在哪在哪在哪在哪");
RangeTestDemo.checkRange(user);
}
}
执行结果:
拓展应用
- 实践-仿照@EnableEurekaServer实现自动装配
- 大批量数据导出优化 中通过自定义注解+AOP 实现并发锁
全文结束
参考链接:
廖雪峰的官方网站