Java-注解
# 注解简介
# 注解的形式
Java 中,注解是以 @
字符开始的修饰符。如下:
@Override
void mySuperMethod() { ... }
注解可以包含命名或未命名的属性,并且这些属性有值。
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass() { ... }
# 什么是注解
从本质上来说,注解是一种标签,其实质上可以视为一种特殊的注释,如果没有解析它的代码,它并不比普通注释强。
解析一个注解有两种形式:
- 编译期直接的扫描 - 编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。这种情况只适用于 JDK 内置的注解类。
- 运行期的反射 - 如果要自定义注解,Java 编译器无法识别并处理这个注解,它只能根据该注解的作用范围来选择是否编译进字节码文件。如果要处理注解,必须利用反射技术,识别该注解以及它所携带的信息,然后做相应的处理。
# 注解的作用
- 编译器信息 - 编译器可以使用注解来检测错误或抑制警告。
- 编译时和部署时的处理 - 程序可以处理注解信息以生成代码,XML 文件等。
- 运行时处理 - 可以在运行时检查某些注解并处理。
# 注解的代价
- 显然,它是一种侵入式编程,那么,自然就存在着增加程序耦合度的问题。
- 自定义注解的处理需要在运行时,通过反射技术来获取属性。如果注解所修饰的元素是类的非 public 成员,也可以通过反射获取。这就违背了面向对象的封装性。
- 注解所产生的问题,相对而言,更难以 debug 或定位。
# 内置注解
JDK 中内置了以下注解:
@Override
@Deprecated
@SuppressWarnnings
@SafeVarargs
(JDK7 引入)@FunctionalInterface
(JDK8 引入)
# @Override
@Override
用于表明被修饰方法覆写了父类的方法。
public class OverrideAnnotationDemo {
static class Person {
public String getName() {
return "getName";
}
}
static class Man extends Person {
@Override
public String getName() {
return "override getName";
}
/**
* 放开下面的注释,编译时会告警
*/
/*
@Override
public String getName2() {
return "override getName2";
}
*/
}
public static void main(String[] args) {
Person per = new Man();
System.out.println(per.getName());
}
}
# @Deprecated
@Deprecated
用于标明被修饰的类或类成员、类方法已经废弃、过时,不建议使用。
# @SuppressWarnnings
@SuppressWarnnings
用于关闭对类、方法、成员编译时产生的特定警告。
# @SafeVarargs
@SafeVarargs
的作用是:告诉编译器,在可变长参数中的泛型是类型安全的。可变长参数是使用数组存储的,而数组和泛型不能很好的混合使用。
@SafeVarargs
注解使用范围:
@SafeVarargs
注解可以用于构造方法。@SafeVarargs
注解可以用于static
或final
方法。
public class SafeVarargsAnnotationDemo {
/**
* 此方法实际上并不安全,不使用此注解,编译时会告警
*/
@SafeVarargs
static void wrongMethod(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; // 语法错误,但是编译不告警
String s = stringLists[0].get(0); // 运行时报 ClassCastException
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
List<String> list2 = new ArrayList<>();
list.add("1");
list.add("2");
wrongMethod(list, list2);
}
}
# @FunctionalInterface
@FunctionalInterface
用于指示被修饰的接口是函数式接口。
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。
# 元注解
Java 中提供了以下元注解类型:
@Retention
@Target
@Documented
@Inherited
(JDK8 引入)@Repeatable
(JDK8 引入)
# @Retention
@Retention
指明了注解的保留级别,源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
RetentionPolicy
是一个枚举类型,它定义了被 @Retention
修饰的注解所支持的保留级别:
RetentionPolicy.SOURCE
- 标记的注解仅在源文件中有效,编译器会忽略。RetentionPolicy.CLASS
- 标记的注解在 class 文件中有效,JVM 会忽略。RetentionPolicy.RUNTIME
- 标记的注解在运行时有效。
@Retention
示例:
@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;
}
# @Documented
@Documented
表示无论何时使用指定的注解,都应使用 Javadoc(默认情况下,注释不包含在 Javadoc 中)。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
# @Target
@Target
指定注解可以修饰的元素类型,源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType
是一个枚举类型,它定义了被 @Target
修饰的注解可以应用的范围:
ElementType.ANNOTATION_TYPE
- 标记的注解可以应用于注解类型。ElementType.CONSTRUCTOR
- 标记的注解可以应用于构造函数。ElementType.FIELD
- 标记的注解可以应用于字段或属性。ElementType.LOCAL_VARIABLE
- 标记的注解可以应用于局部变量。ElementType.METHOD
- 标记的注解可以应用于方法。ElementType.PACKAGE
- 标记的注解可以应用于包声明。ElementType.PARAMETER
- 标记的注解可以应用于方法的参数。ElementType.TYPE
- 标记的注解可以应用于类的任何元素。
@Target(ElementType.TYPE)
public @interface Table {
/**
* 数据表名称注解,默认值为类名称
* @return
*/
public String tableName() default "className";
}
@Target(ElementType.FIELD)
public @interface NoDBColumn {}
# @Inherited
@Inherited
表示注解类型可以被继承(默认情况下不是这样)。表示自动继承注解类型。 如果注解类型声明中存在 @Inherited
元注解,则注解所修饰类的所有子类都将会继承此注解。
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}
# @Repeatable
@Repeatable
表示注解可以重复使用
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Schedules {
Scheduled[] value();
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
// ...
}
public class TaskRunner {
@Scheduled("0 0/15 * * * ?")
@Scheduled("0 0 12 * ?")
public void task1() {}
}
# 自定义注解
使用 @interface
自定义注解时,自动继承了 java.lang.annotation.Annotation
接口,由编译程序自动完成其他细节。
# 注解的定义
注解的语法格式如下:
public @interface 注解名 {定义体}
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {}
@Documented
表示@RegexValid
应该使用 javadoc。@Target({ElementType.FIELD, ElementType.PARAMETER})
表示@RegexValid
可以在类成员或方法参数上修饰。- @Retention(RetentionPolicy.RUNTIME) 表示
@RegexValid
在运行时有效。
# 注解属性
注解属性的语法形式如下:
访问级别修饰符] [数据类型] 名称() default 默认值;
例如,我们要定义在注解中定义一个名为 value 的字符串属性,其默认值为空字符串,访问级别为默认级别,那么应该定义如下:
String value() default "";
在注解中,我们定义属性时,属性名后面需要加
()
。
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {
enum Policy {
// @formatter:off
EMPTY(null),
DATE("^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\\1"
+ "(?:29|30)|(?:0?[13578]|1[02])\\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|"
+ "(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\\2(?:29))$"),
MAIL("^[A-Za-z0-9](([_\\.\\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\\.\\-]?[a-zA-Z0-9]+)*)\\.([A-Za-z]{2,})$");
// @formatter:on
private String policy;
Policy(String policy) {
this.policy = policy;
}
public String getPolicy() {
return policy;
}
}
String value() default "";
Policy policy() default Policy.EMPTY;
}
在上面的示例代码中,我们定义了两个注解属性:String
类型的 value 属性和 Policy
枚举类型的 policy 属性。Policy
枚举中定义了几个默认的正则表达式,这是为了直接使用这几个常用表达式去正则校验。
# 注解处理器
java.lang.annotation.Annotation
是一个接口,程序可以通过反射来获取指定程序元素的注解对象,然后通过注解对象来获取注解里面的元数据。
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
除此之外,Java 中支持注解处理器接口 java.lang.reflect.AnnotatedElement
,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:
Class
- 类定义Constructor
- 构造器定义Field
- 累的成员变量定义Method
- 类的方法定义Package
- 类的包定义
AnnotatedElement
接口是所有程序元素(Class、Method 和 Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement
对象之后,程序就可以调用该对象的如下四个个方法来访问注解信息:
getAnnotation
- 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回 null。getAnnotations
- 返回该程序元素上存在的所有注解。isAnnotationPresent
- 判断该程序元素上是否包含指定类型的注解,存在则返回 true,否则返回 false。getDeclaredAnnotations
- 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexValidUtil {
public static boolean check(Object obj) throws Exception {
boolean result = true;
StringBuilder sb = new StringBuilder();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
// 判断成员是否被 @RegexValid 注解所修饰
if (field.isAnnotationPresent(RegexValid.class)) {
RegexValid valid = field.getAnnotation(RegexValid.class);
// 如果 value 为空字符串,说明没有注入自定义正则表达式,改用 policy 属性
String value = valid.value();
if ("".equals(value)) {
RegexValid.Policy policy = valid.policy();
value = policy.getPolicy();
}
// 通过设置 setAccessible(true) 来访问私有成员
field.setAccessible(true);
Object fieldObj = null;
try {
fieldObj = field.get(obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (fieldObj == null) {
sb.append("\n")
.append(String.format("%s 类中的 %s 字段不能为空!", obj.getClass().getName(), field.getName()));
result = false;
} else {
if (fieldObj instanceof String) {
String text = (String) fieldObj;
Pattern p = Pattern.compile(value);
Matcher m = p.matcher(text);
result = m.matches();
if (!result) {
sb.append("\n").append(String.format("%s 不是合法的 %s !", text, field.getName()));
}
} else {
sb.append("\n").append(
String.format("%s 类中的 %s 字段不是字符串类型,不能使用此注解校验!", obj.getClass().getName(), field.getName()));
result = false;
}
}
}
}
if (sb.length() > 0) {
throw new Exception(sb.toString());
}
return result;
}
}
- 通过 getDeclaredFields 反射方法获取传入对象的所有成员。
- 遍历成员,使用 isAnnotationPresent 判断成员是否被指定注解所修饰,如果不是,直接跳过。
- 如果成员被注解所修饰,通过
RegexValid valid = field.getAnnotation(RegexValid.class);
这样的形式获取,注解实例化对象,然后,就可以使用valid.value()
或valid.policy()
这样的形式获取注解中设定的属性值。- 根据属性值,进行逻辑处理。
# 使用注解
public class RegexValidDemo {
static class User {
private String name;
@RegexValid(policy = RegexValid.Policy.DATE)
private String date;
@RegexValid(policy = RegexValid.Policy.MAIL)
private String mail;
@RegexValid("^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")
private String phone;
public User(String name, String date, String mail, String phone) {
this.name = name;
this.date = date;
this.mail = mail;
this.phone = phone;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", date='" + date + '\'' + ", mail='" + mail + '\'' + ", phone='"
+ phone + '\'' + '}';
}
}
static void printDate(@RegexValid(policy = RegexValid.Policy.DATE) String date){
System.out.println(date);
}
public static void main(String[] args) throws Exception {
User user = new User("Tom", "1990-01-31", "xxx@163.com", "18612341234");
User user2 = new User("Jack", "2019-02-29", "sadhgs", "183xxxxxxxx");
if (RegexValidUtil.check(user)) {
System.out.println(user + "正则校验通过");
}
if (RegexValidUtil.check(user2)) {
System.out.println(user2 + "正则校验通过");
}
}
}