1、什么是注解
2、JDK内置注解
3、自定义注解
4、案例实战:模拟ORM,使用反射读取注解
Annotation是从JDK5.0开始引入的新技术.
1 Annotation不是程序本身,它是对程序作出解释;
2 可以被其他程序(比如:编译器等)读取 ,根据不同的注解做出不同的处理,比如编译器会检查@Override标记的方法是否在存在于父类、接口中;
以@注解名
形式在代码中存在,比如@Override
class Obj extends Object{ private int id; @Override public String toString() { return "Obj{" + "id=" + id + '}'; } }
可以附加在package , class , method , field等上面﹐相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
注解 @Override 限定重写父类方法或实现接口方法, 该注解只能加在方法上。
注解@Deprecated 表示所修饰的元素已过时,通常是因为使用该元素很危险(比如后续版本不支持了)或存在更好的选择。
当在非弃用代码中使用或重写@Deprecated程序元素时,编译器会发出警告。
例:
public class AnnotationTest { public static void main(String[] args) { // Date构造方法加了@Deprecated注解 Date date = new Date(2021, 10, 11); new Student().deprecatedMethod(); } } class Student{ @Deprecated void deprecatedMethod(){ System.out.println("deprecatedMethod is invoked."); } }
D:\>javac AnnotationTest.java 注: AnnotationTest.java使用或覆盖了已过时的 API。 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。 D:\>javac AnnotationTest.java -Xlint:deprecation AnnotationTest.java:12: 警告: [deprecation] Date中的Date(int,int,int)已过时 Date date = new Date(2021, 10, 11); ^ AnnotationTest.java:13: 警告: [deprecation] Student中的deprecatedMethod()已过时 new Student().deprecatedMethod(); ^ 2 个警告
@SuppressWarnings 用于抑制编译器警告
例:
public class AnnotationTest { public static void main(String[] args) { List list = new ArrayList(); } }
程序有警告,如下图
加了@SuppressWarnings(“rawtypes”)后仍然后警告
加了"unused" 后警告消失
public class AnnotationTest { @SuppressWarnings({ "rawtypes", "unused" }) public static void main(String[] args) { List list = new ArrayList(); } }
1、使用 @interface 关键字
来定义新的 Annotation 类型;
例:
public @interface NormalAnnotation { String value(); }
2、自定义注解自动继承了java.lang.annotation.Annotation接口
;
Annotation源码如下:
package java.lang.annotation; public interface Annotation { /** * 如果指定的对象表示的注释在逻辑上与此注释等价,则为True,否则为false */ boolean equals(Object obj); int hashCode(); /** * 返回此注释的字符串表示形式。表示的细节是依赖于实现的 */ String toString(); /** * @return 返回此注释的注释类型 */ Class<? extends java.lang.annotation.Annotation> annotationType(); }
3、 注解的成员
在注解的定义中以无参数方法的形式来声明
;
方法名
定义了成员的名字
返回值
定义了类型
public @interface NormalAnnotation { /** * 成员名为 value * 类型为 String */ String value(); }
类型只能是:
八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。
4、可以在定义 Annotation 的成员变量时为其指定初始值, 可以使用default关键字指定成员变量的默认值
;
public @interface NormalAnnotation { // 成员value的默认值为空字符串 String value() default ""; }
5、 如果只有一个成员,建议使用的成员名为 value
;
6、如果定义的注解含有成员,使用时必须指定成员的值,格式是 “成员名 = 成员值”
@NormalAnnotation(value="abc") public class AnnotationTest { ... }
如果在定义时使用了default 设置了某个成员的默认值,则可以不指定该成员的值 ,例:
public @interface NormalAnnotation { String value() default ""; } @NormalAnnotation public class AnnotationTest { ... }
如果只有一个参数成员且成员名称为value,可以省略“value=”
,这就是上面第5条建议的价值。例:
@NormalAnnotation("abc") public class AnnotationTest { ... }
7、没有成员定义的注解称为标记
;
比如@Override 就是一个标记,源码如下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
8、包含成员变量的 Annotation 称为元数据注解
注意:我们定义的注解,你的程序要去使用这个注解,同时你其他的程序要去获取这个注解做一些逻辑或业务上的处理,这样才能体现这个注解的价值。
class NewClass { public void m1() { } private void pm1() { } public static void main(String[] args) { Class<NewClass> cls = NewClass.class; Method[] methods = cls.getMethods(); methods = cls.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.getName()); } } private static void printMethods(Method[] methods) { for (Method method : methods) { System.out.println(method.getName()); } } }
// 返回本类中显式定义的所有方法(包括private方法,不包括未覆盖的父类的方法) Class#getDeclaredMethods Class#getMethods
元注解
(meta-annotation)JDK 的元注解
是用于修饰其他注解的注解.
可以类比数据库中元数据的概念:
数据库表中有一列一列的数据,而每一列都有列名, 这个列名用来指明这一列是什么数据,这个列名就可以认为是元数据
JDK5.0提供了4个标准的元注解类型,分别是:
@Retention @Target @Documented @Inherited
Retention /rɪˈtenʃn/ n. 保持,保留
@Retention只能用于修饰注解
, 用来指定该注解的生命周期
。
@Rentention 包含一个 RetentionPolicy 类型的成员, 使用@Rentention 时必须为该 value 成员指定值:
1、RetentionPolicy.SOURCE 表示在源文件中有效,编译器是使用,编译完直接丢弃这个注释(反编译就看不到了)。
比如 @Override的@Retention就是RetentionPolicy.SOURCE
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
2、RetentionPolicy.CLASS(默认值) 表示该注解在编译时会编译到class文件中, 当运行 Java 程序时, JVM 不会保留该注解。
3、RetentionPolicy.RUNTIME 表示在运行时也有效,即当运行 Java 程序时, JVM 也会保留该注解。程序可以通过反射获取该注解。
@Target 用来指定注解可以标注在哪里。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * 指定注解可应用在哪几个位置 */ ElementType[] value(); }
枚举类ElementType枚举了10个位置:
public enum ElementType { /** 类,接口,注解,枚举定义的位置*/ TYPE, /** 实例变量的位置(包括声明枚举常量的位置) */ FIELD, /** 方法定义的位置 */ METHOD, /** 参数位置 */ PARAMETER, /** 构造方法位置 */ CONSTRUCTOR, /** 局部变量位置 */ LOCAL_VARIABLE, /** 声明注解的位置声明*/ ANNOTATION_TYPE, /** 包定义的位置 */ PACKAGE, /** * Type parameter declaration * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type */ TYPE_USE }
@Documented 标注的注解,在使用javadoc
生成标注了该注解的元素的doc文档时,该注解会显示在文档中。
比如@Documented标注了 java.lang.Deprecated
注解,而java.util.Date的一个构造方法上加了@Deprecated注解:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD , PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { } public class Date implements java.io.Serializable,Cloneable, Comparable<Date> { /** * Allocates a <code>Date</code> object and initializes it so that * it represents the date and time indicated by the string * <code>s</code>, which is interpreted as if by the * {@link Date#parse} method. * * @param s a string representation of the date. * @see java.text.DateFormat * @see java.util.Date#parse(java.lang.String) * @deprecated As of JDK version 1.1, * replaced by <code>DateFormat.parse(String s)</code>. */ @Deprecated public Date(String s) { this(parse(s)); } }
点击查看 java8的doc文档
@Deprecated 显示在doc文档中
被@Inherited 修饰的注解将具有继承性。
如果某个类使用了被@Inherited 修饰的注解, 则其子类将自动具有该注解。
例:
@MarkablelAnnotation @NormalAnnotation("abc") public class AnnotationTest { public static void main(String[] args) { Annotation[] annotations = AnnotationTest.class.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); } annotations = SubAnnotationTest.class.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); } } }
输出
@com.annotation.NormalAnnotation(value=abc) @com.annotation.NormalAnnotation(value=abc)
⚠️⚠️注意:想要有输出,必须使用@Retention(RetentionPolicy.RUNTIME)
@Inherited @Retention(RetentionPolicy.RUNTIME) public @interface NormalAnnotation { String value(); } public @interface MarkablelAnnotation { }
@MarkablelAnnotation 没有使用@Retention(RetentionPolicy.RUNTIME)
, 所以没有输出。
可重复注解、类型注解
@Repeatable用于指示它注解的注解是可重复的。@Repeatable的值表示包含的注解类型。
@Repeatable源码如下:
/** * @Repeatable用于指示它注解的注解是可重复的。@Repeatable的值表示包含的注解类型。 * @since 1.8 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Repeatable { /** * 指示重复注解包含的注解类型 */ Class<? extends Annotation> value(); }
例:
在一个注解NormalAnnotation上加@Repeatable,@Repeatable的成员-value值为注解A的数组
@Retention(RetentionPolicy.RUNTIME) @Repeatable(NormalAnnotations.class) public @interface NormalAnnotation { String value(); } public @interface NormalAnnotations { NormalAnnotation[] value(); }
⚠️⚠️: @NormalAnnotation的@Target的ElementType值必须包含@NormalAnnotations的@Target的ElementType值
ElementType.TYPE_PARANETER
表示该注解能写在类型的声明语句处(如:泛型声明)。
@Target({ElementType.TYPE_PARAMETER}) public @interface NormalAnnotation { String value(); } class UserService<@NormalAnnotation T> { private T t; public <@NormalAnnotation T> void setT(T t) { List<T> list = new ArrayList<>(); } }
具体用在上面地方?不知道! 我搜索了SpringFramework、SpringBoot2、MyBatis的源码都没有搜到使用TYPE_PARAMETER的地方。
ELementType. TYPE_USE表示该注解能写在使用类型的任何语句中。
class UserService<@NormalAnnotation T>{ @NormalAnnotation private T t ; public <@NormalAnnotation T> void setT(@NormalAnnotation T t){ List<@NormalAnnotation T> list = new ArrayList<>(); } }
在@Target的成员中添加一个ElementType.TYPE_USE
, 编译错误消失
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE,ElementType.TYPE_PARAMETER}) public @interface NormalAnnotation { String value() default ""; }
现在我们想着想提供基本的对象/关系映射功能,能够自动生成数据库表。
一种方案:使用 XML 文件的方式提供对象/关系映射信息。
更好的方案:使用注解提供对象/关系映射信息,注解看上去更直接和方便,我们可以把映射信息(注解)都保存在 JavaBean 源文件中。
为此我们需要定义一些注解:
1、用于“映射JavaBean到数据表的注解”;
2、用于“映射JavaBean的属性到列的注解”;
/** * 类的注解,将类映射到表 */ @Target(ElementType.TYPE) // Applies to classes only @Retention(RetentionPolicy.RUNTIME) public @interface DBTable { // 类对应的表名称 public String name() default ""; }
/** * 列的限制属性 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Constraints { // 实例变量对应的列是否为主键 boolean primaryKey() default false; // 实例变量对应的列是否允许为空 boolean allowNull() default true; // 实例变量对应的列的值是否唯一 boolean unique() default false; } /** * 注解int/Integer类型的变量 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLInteger { // 实例变量对应的列名称 String name() default ""; // 实例变量对应的列的限制属性 Constraints constraints() default @Constraints; } /** * 注解 String 类型的实例变量 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLString { // 实例变量对应字段的长度 int value() default 0; // 实例变量对应字段的名称 String name() default ""; Constraints constraints() default @Constraints; }
定义一个Member类,使用我们自定义的注解
@DBTable(name = "MEMBER") public class Member { @SQLString(30) String firstName; @SQLString(50) String lastName; @SQLInteger Integer age; @SQLString(value = 30, constraints = @Constraints(primaryKey = true)) String handle; static int memberCount; // get/set 方法略... }
解析注解的成员,拼接成SQL的DDL语句
public class TableCreator { /** * @param args 类全限定名 * @throws Exception */ public static void main(String[] args) throws Exception { checkParam(args); for (String className : args) { Class<?> cl = Class.forName(className); // 1 根据class类上的注解,获取表名 String tableName = getTableName(cl); if (tableName == null) continue; // 2 根据class中各字段上的注解,生成定义列的语句 List<String> columnDefs = createColumnDefs(cl); System.out.println("Table Creation SQL for " + className + " is :"); printCreateTableDDL(tableName, columnDefs); } } private static void printCreateTableDDL(String tableName, List<String> columnDefs) { StringBuilder sqlCommandBuilder = new StringBuilder("CREATE TABLE " + tableName + "("); for (String columnDef : columnDefs) { sqlCommandBuilder.append("\n " + columnDef + ","); } // Remove trailing comma String tableCreate = sqlCommandBuilder.substring(0, sqlCommandBuilder.length() - 1) + "\n);"; System.out.println(tableCreate); } private static List<String> createColumnDefs(Class<?> cl) { List<String> columnDefs = new ArrayList<String>(); for (Field field : cl.getDeclaredFields()) { Annotation[] fieldAnns = field.getDeclaredAnnotations(); if (fieldAnns.length < 1) { continue; } Annotation firstFieldAnn = fieldAnns[0]; String columnName = null; if (firstFieldAnn instanceof SQLInteger) { SQLInteger sInt = (SQLInteger) firstFieldAnn; // Use field name if name not specified if (sInt.name().length() < 1) columnName = field.getName().toUpperCase(); else columnName = sInt.name(); columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints())); } if (firstFieldAnn instanceof SQLString) { SQLString sqlStr = (SQLString) firstFieldAnn; // Use field name if name not specified. if (sqlStr.name().length() < 1) columnName = field.getName().toUpperCase(); else columnName = sqlStr.name(); columnDefs.add(columnName + " VARCHAR(" + sqlStr.value() + ")" + getConstraints(sqlStr.constraints())); } } return columnDefs; } /** * @param cl * @return */ private static String getTableName(Class<?> cl) { DBTable dbTable = cl.getAnnotation(DBTable.class); if (dbTable == null) { System.out.println("No DBTable annotations in class " + cl.getName()); return null; } String tableName = dbTable.name(); // If the name is empty, use the Class name: if (tableName.length() < 1) { tableName = cl.getName().toUpperCase(); } return tableName; } private static void checkParam(String[] args) { if (args.length < 1) { System.out.println("arguments: annotated classes"); System.exit(0); } } 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; } }