文章目录
理解Java注解
声明注解与元注解
声明注解
- Java注解就是一种特殊的接口,
@interface
关键字声明注解,如自定义注解
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {}
元注解
@Documented
:被其修饰的注解会生成到javadoc
中。使用javadoc
命令生成文档@Inherited
:允许子类继承父类中的注解,但这并不是真的继承,只是使用@Inherited
,可以让子类Class
对象使用getAnnotations()
获取父类被@Inherited
修饰的注解@Target
:用来约束注解可以应用的地方(如方法、类或字段),其中ElementType
是枚举类型:
- 当注解未指定
Target
值时,则此注解可以用于任何元素之上 - 多个值使用{}包含并用逗号:
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public enum ElementType {
/**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
/** 标明该注解可以用于参数声明 */
PARAMETER,
/** 标明注解可以用于构造函数声明 */
CONSTRUCTOR,
/** 标明注解可以用于局部变量声明 */
LOCAL_VARIABLE,
/** 标明注解可以用于注解声明(应用于另一个注解上)*/
ANNOTATION_TYPE,
/** 标明注解可以用于包声明 */
PACKAGE,
/**
* 标明注解可以用于类型参数声明(1.8新加入)
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 用于标注任意类型(不包括class)(1.8新加入)
* @since 1.8
*/
TYPE_USE
}
@Retention
:用来约束注解的生命周期,分别有三个值:
-
源码级别
SOURCE
:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里) -
类文件级别
CLASS
:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention
值时,默认值是CLASS
,如Java内置注解,@Override、@Deprecated、@SuppressWarnning
等 -
运行时级别
RUNTIME
:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc
中的@Controller、@Autowired、@RequestMapping
等
注解元素及其数据类型
- 注解元素支持的数据类型(不允许使用任何包装类型)
- 所有基本类型
- String
- Class
- enum
- Annotation(嵌套注解)
- 上述类型的数组
- 自定义注解代码:
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
/**
* 字符串类型
*
* @return
*/
String name() default "";
/**
* 布尔类型
*
* @return
*/
boolean canShow() default false;
/**
* int 类型
*
* @return
*/
int value() default 0;
/**
* 数组类型,要么具有默认值,要么在使用注解时传入
*
* @return
*/
String[] names() default "";
/**
* 枚举类型
*/
enum Status {
/**
* 修复
*/
FIXED,
/**
* 正常
*/
NORMAL
}
/**
* class类型
*/
Class<?> clazz() default Void.class;
}
- 编译器对默认值的限制
- 注解的元素,要么具有默认值,要么在使用注解时提供元素的值
- 对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以
null
作为值
注解的其他属性
注解不支持继承
- 注解不支持继承,不能使用关键字
extends
来继承某个@interface
- 注解被编译器编译后,会自动继承
java.lang.annotation.Annotation
接口
// 特殊接口,继承 Annotation
public interface CustomAnnotation extends Annotation{}
快捷方式
- 如果注解中定义了名为
value
的元素,且只需要给value赋值,可以省略value=20
为直接赋值20
- 这限制了元素名必须为
value
// 定义注解
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GroupAnnotation {
int value() default 0;
String name() default "";
}
// 使用注解
/ 当name也需要赋值时必须采用key=value的方式赋值
@GroupAnnotation(value = 20, name = "Sam")
class AnnotationSuperClass {
}
// 当只想给 value 赋值时,可以使用以下快捷方式,省略value=20
@GroupAnnotation(20)
class AnnotationClass extends AnnotationSuperClass {
}
Java内置注解与其它元注解
@Override
:用于标明此方法覆盖了父类的方法,源码如下@Deprecated
:用于标明已经过时的方法或类,源码如下@SuppressWarning()
:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下
deprecation:使用了不赞成使用的类或方法时的警告;
unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path:在类路径、源文件路径等中有不存在的路径时的警告;
serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
finally:任何 finally 子句不能正常完成时的警告;
all:关于以上所有情况的警告。
注解与反射机制
- 注解都继承了
Annotation
接口,Java使用Annotation
接口代表注解元素 - Java 在反射包下有
AnnotatedElement
接口,主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,Constructor
、Field
、Method
、Package
、Class
类都实现了该接口 AnnotatedElement
中相关的API方法如下:
返回值 | 方法名称 | 说明 |
---|---|---|
getAnnotation(Class annotationClass) | 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。 | |
Annotation[] | getAnnotations() | 返回此元素上存在的所有注解,包括从父类继承的 |
boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。 |
Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组 |
- 简单示例
@CustomAnnotation
@GroupAnnotation(value = 20, name = "Sam")
class AnnotationSuperClass {
}
@GroupAnnotation(20)
class AnnotationClass extends AnnotationSuperClass {
}
static void test() {
Class<AnnotationClass> clazz = AnnotationClass.class;
// 根据指定注解类型获取该注解
GroupAnnotation annotation = clazz.getAnnotation(GroupAnnotation.class);
System.out.println("GroupAnnotation: " + annotation);
// 获取该元素上的所有注解,包含从父类继承
Annotation[] annotations = clazz.getAnnotations();
System.out.println("getAnnotations: " + Arrays.toString(annotations));
// 获取该元素上的所有注解,但不包含继承!
Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
System.out.println("getDeclaredAnnotations: " + Arrays.toString(declaredAnnotations));
//判断注解 CustomAnnotation 是否在该元素上
boolean annotationPresent = clazz.isAnnotationPresent(CustomAnnotation.class);
System.out.println("isAnnotationPresent: " + annotationPresent);
/*
输出:
GroupAnnotation: @com.learning.optimize.jdk.annotation.GroupAnnotation()
getAnnotations: [@com.learning.optimize.jdk.annotation.CustomAnnotation(name=, names=[], value=0, showSupport=false), @com.learning.optimize.jdk.annotation.GroupAnnotation()]
getDeclaredAnnotations: [@com.learning.optimize.jdk.annotation.GroupAnnotation()]
isAnnotationPresent: true
*/
}
输出:
GroupAnnotation: @com.learning.optimize.jdk.annotation.GroupAnnotation(name=, value=20)
getAnnotations: [@com.learning.optimize.jdk.annotation.CustomAnnotation(name=, canShow=false, names=[], clazz=class java.lang.Void, value=0), @com.learning.optimize.jdk.annotation.GroupAnnotation(name=, value=20)]
getDeclaredAnnotations: [@com.learning.optimize.jdk.annotation.GroupAnnotation(name=, value=20)]
isAnnotationPresent: true
注解的简单使用:演示利用运行时注解来组装数据库SQL的构建语句的过程
- 注解定义
// 表注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
/**
* 表的名字
*
* @return
*/
String name();
}
// 列注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
/**
* 对应数据库表的列名
*
* @return
*/
String name() default "";
/**
* 列类型分配的长度,如varchar(30)的30
*
* @return
*/
int value() default 16;
/**
* 列约束
*
* @return
*/
Constraints constraint() default @Constraints;
}
// 列约束
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
/**
* 判断是否作为主键约束
*/
boolean primaryKey() default false;
/**
* 判断是否允许为null,默认不为 null
*
* @return
*/
boolean allowNull() default false;
/**
* 判断是否唯一,默认不唯一
*
* @return
*/
boolean unique() default false;
}
- 注解使用的实体
@Table(name = "sys_person")
@Setter
@Getter
public class Person {
/**
* 主键ID
*/
@Column(name = "ID", value = 50, constraint = @Constraints(primaryKey = true))
private String id;
/**
* 姓名
*/
@Column(name = "NAME", value = 30)
private String name;
/**
* 年龄
*/
@Column(name = "AGE")
private int age;
/**
* 个人描述
*/
@Column(name = "DESCRIPTION", value = 150, constraint = @Constraints(allowNull = true))
private String description;
}
- 组装数据库SQL的构建(表创建)
public class TableCreator {
public static String createTableSql(String className) throws ClassNotFoundException {
Class<?> cl = Class.forName(className);
Table dbTable = cl.getAnnotation(Table.class);
//如果没有表注解,直接返回
if (dbTable == null) {
System.out.println("No DBTable annotations in class " + className);
return null;
}
String tableName = dbTable.name();
// If the name is empty, use the Class name:
if (tableName.length() < 1) {
tableName = cl.getName().toUpperCase();
}
List<String> columnDefs = new ArrayList<String>();
//通过Class类API获取到所有成员字段
for (Field field : cl.getDeclaredFields()) {
String columnName;
String columnType;
//获取字段上的注解
Annotation[] anns = field.getDeclaredAnnotations();
if (anns.length < 1) {
continue; // Not a db table column
}
// 这边简单处理
Class<?> type = field.getType();
if (type == int.class || type == Integer.class) {
columnType = " INT(%s)";
} else {
columnType = " VARCHAR(%s)";
}
for (Annotation ann : anns) {
if (ann instanceof Column) {
Column column = (Column) ann;
if (column.name().length() < 1) {
columnName = field.getName().toUpperCase();
} else {
columnName = column.name();
}
//构建语句
columnDefs.add(columnName + String.format(columnType, column.value()) + getConstraints(column.constraint()));
}
}
}
//数据库表构建语句
String columns = Joiner.on(",\n ").join(columnDefs);
return "CREATE TABLE " + tableName + "(\n " + columns + "\n);";
}
/**
* 判断该字段是否有其他约束
*
* @param con
* @return
*/
private static String getConstraints(Constraints con) {
String constraints = "";
if (!con.allowNull()) {
constraints += " NOT NULL";
}
if (con.primaryKey()) {
constraints += " PRIMARY KEY";
}
if (con.unique()) {
constraints += " UNIQUE";
}
return constraints;
}
public static void main(String[] args) throws Exception {
String className = "com.learning.optimize.jdk.annotation.sql.Person";
System.out.println(createTableSql(className));
}
}
输出:
CREATE TABLE sys_person(
ID VARCHAR(50) NOT NULL PRIMARY KEY,
NAME VARCHAR(30) NOT NULL,
AGE INT(16) NOT NULL,
DESCRIPTION VARCHAR(150)
);
Java 8中注解增强
元注解Repeatable
- 表示在同一个位置重复相同的注解。
- 示例
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
String [] value();
}
// JDK8 以前这样使用
@FilterPath({"/update","/add"})
public class A { }
// 使用Java8新增 @Repeatable原注解
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class) // 参数指明接收的注解class
public @interface FilterPath {
String value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
FilterPath[] value();
}
// Java8 可以这样使用
@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}
新增的两种 ElementType
static void test2() {
// 可以用于标注类型参数 class D<@Parameter T> { }
ElementType typeParameter = ElementType.TYPE_PARAMETER;
// 用于标注任意类型(不包括class)
ElementType typeUse = ElementType.TYPE_USE;
}