本文内容
1、什么是注解
2、JDK内置注解
3、自定义注解
4、案例实战:模拟ORM,使用反射读取注解
1、什么是注解
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等上面﹐相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
2、JDK内置注解
@Override
注解 @Override 限定重写父类方法或实现接口方法, 该注解只能加在方法上。
@Deprecated
注解@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
@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();
}
}
自定义Annotation
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
练习
JDK中的元注解
(meta-annotation)
JDK 的元注解
是用于修饰其他注解的注解.
可以类比数据库中元数据的概念:
数据库表中有一列一列的数据,而每一列都有列名, 这个列名用来指明这一列是什么数据,这个列名就可以认为是元数据
JDK5.0提供了4个标准的元注解类型,分别是:
@Retention
@Target
@Documented
@Inherited
@Retention
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
@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
@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 修饰的注解将具有继承性。
如果某个类使用了被@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)
, 所以没有输出。
jdk 8 中注解的新特性
可重复注解、类型注解
(1)可重复注解 @Repeatable
@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值
(2)@Target新增2个ElementType类型成员
(2.1)ElementType.TYPE_PARANETER
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的地方。
(2.2)ELementType. TYPE_USE
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的属性到列的注解”;
1、定义“映射JavaBean到数据表的注解”
/**
* 类的注解,将类映射到表
*/
@Target(ElementType.TYPE) // Applies to classes only
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
// 类对应的表名称
public String name() default "";
}
2、定义 “映射JavaBean的实例变量到列的注解”
/**
* 列的限制属性
*/
@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;
}
}