java注解与反射详解
一、注解篇
1.1、注解的基本概念
-
注解:一种代码级别的说明,它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次;它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释.
要将注解(annotation)和注释(commnet)分开,注释是给人看的,机器不会执行,而注解主要是是给机器“看的”;
比如多线程中重写run()方法,会有一个
@Override
注解,该注解一是给人说明这个方式是一个重写方法,而是给机器做检查(如果你重写的方法名等不正确会报错!)注解和反射是许多java框架的底层,因此必须学好!
-
作用:
- 编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
- 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
- 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
总结:注解就是java代码的一种,可以认为是一种类型,并且主要是给机器看的
-
注解格式
@<注解名>[(参数)] # @Override # @SuppressWarning("all")
1.2、java内置注解
1.2.1、作用在代码的注解
①、@Override
-
作用: 检查该方法是否是重写方法;如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误.
-
代码演示:
package kuang.annotation.lesson1; /** * 测试Override的案例 */ public class TestOverride { @Override public String tostring() { // 重写的toString方法,这里将S改为小写会报错,因为Object中没有该方法 return "TestOverride{}"; } public static void main(String[] args) { } }
-
效果展示:
上方代码将
toString
方法的S
改为了小写,@Overside
检查后发现父类(Object类)中没有该方法,会发生编译错误
②、@Deprecated
-
作用:标记过时方法,标明该方法在该版本JDK过时或有更好的替代;如果使用该方法,会报编译警告,但不影响运行。
-
代码演示:
package kuang.annotation.lesson1; /** * 测试Deprecated的演示 */ public class TestDeprecated extends Thread { /** * 参数since,表示已注解的API元素已被弃用的版本 * 参数forRemoval,元素表示注解的API素在将来的版本中是否被删除 * 这两个参数是java9之后新增的,平时可以不用 */ @Deprecated(since = "9",forRemoval = true) // 使用废弃注解标明该方法已经不推荐使用 public static void test() { System.out.println("废弃注解测试"); } public static void main(String[] args) { test(); // 废弃方法在同一个类中,会直接标明该方法 new Thread("小明").stop(); // 通过继承等方式调用被弃用方法,会有个横线划掉方法 } }
-
效果展示
③、@SuppressWarnings
-
作用:指示编译器去忽略注解中声明的警告,可以使用参数标明镇压警告的对象,可以是构造器、方法、类等
-
代码演示:
package kuang.annotation.lesson1; /** * 测试镇压警告注解的demo */ @SuppressWarnings("all") // 使用镇压警告后,idea不会提示警告代码,并且代码在编译时,jvm也会包容警告 public class TestSuppressWarning { public static void main(String[] args) { // 未使用的变量发生警告 int a; int c; int[] array = new int[1024]; System.out.println(1 == 1.2); // 警告 } }
-
效果展示:
idea没有提示警告代码
1.2.2、元注解
元注解(meta-annotation),就是负责注解其他注解的注解,套娃滴干活
-
元注解
@Target
:用来标明注解的使用范围@Retention
:指定其所修饰的注解的保留策略,保留策略有三个:SOURCE(源码级)、CLASS(字节码级)、RUNTIME(运行时级),最用范围依次变大@Document
:该注解是一个标记注解,用于指示一个注解将被文档化@Inherited
:该注解使父类的注解能被其子类继承@Repeatable
:Java 8 开始支持,标识某注解可以在同一个声明上使用多次、@FunctionalInterface
:Java 8 开始支持,标识一个匿名函数或函数式接口@SafeVarargs
: Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
-
代码演示
package kuang.annotation.lesson1; import java.lang.annotation.*; public class TestMetaAnnotation { public static void main(String[] args) { } @Target(ElementType.METHOD) // 注解应用于方法上 @Retention(value = RetentionPolicy.RUNTIME) // 表示该注解一直到运行时有效 @Documented // 表示将注解生成在javaDoc中,不加则在javaDoc中不生成 @Inherited // 子类可以继承父类的注解 @interface myAnnotation { // 我定义的注解 } }
1.3、自定义注解
-
规范
- 使用关键字
@interface <注解名>
定义注解 - 注解体中可以包含配置参数,配置参数的形式为
<基本类型> 配置参数名()
,必须加上括号,类似一个方法 - 配置参数可以有多个,且名称自定义,可以使用
default
关键字给配置参数加上默认值 - 带配置参数的注解被引用时,参数部分必须以键值对形式写出来,有默认值的可以不写(也可以写,就是覆盖掉默认值),没有默认值的配置参数必须写上值
- 单个配置参数可以使用默认参数名
value
,该名称在注解被时引用时可以省略
- 使用关键字
-
代码演示
注解定义部分
package kuang.annotation.lesson1; import java.lang.annotation.*; /** * 注解定义: * 1.@interface关键字来定义注解 * 2.注解体中可以包含配置参数 * 3.配置参数需要有类型和配置参数名,参数类型都是基本数据类型,参数名类似方法名,需要用() * 4.自定义注解一般需要使用元注解,如@Target等来修饰,但可有可无 */ @Target(ElementType.METHOD) // 表示注解的使用范围是方法,如果不加该注解则自定义注解可以应用在任何地方 @Retention(RetentionPolicy.RUNTIME) // 不写该注解默认保留策略为CLASS级别 public @interface MyAnnotation { String name() default "张三"; // 配置参数名为name,类型为String int age() default 0; String[] school() default {"北大","清华"}; // default可以给配置参数带上默认值 int id(); // 不带默认值,在该注解被引用时,必须显式写上 } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @interface MyAnno2 { // 单个配置参数,可以只用默认的参数名->value,默认参数名在注解引用时可以不写出 // 见SuppressWarning注解,它只含有一个单个的配置参数,且使用默认的配置参数名value,在注解被引用时,可以省略默认参数名 String value(); }
引用部分
package kuang.annotation.lesson1; /** * 测试自定义注解的demo */ public class TestMyAnnotation { // 自定义配置名称的注解,在写入参数时必须使用键值对形式 // 这里配置参数name有默认值,不写的话,这里也不会报错 // 但不带默认值必须显式写出来,即这里的id @MyAnnotation(age = 18,school = {"家里蹲"},id = 2022) public void test01(String name, int age, String[] school, int id) { System.out.println("自定义注解的测试"); } @MyAnno2("hello") // 默认参数名value的注解在被引用时,可以省略参数名 public void test02() { } }
二、反射篇
2.1、反射的基本概念
2.1.1、引入
-
动态语言:在运行时代码可以根据某些条件改变自身结构,比如新的方法、对象、代码的引入,已有的代码可以在结构上发生变化;常见的动态语言比如:JS、PHP、Python等,可以发现他们都是解释型语言,这类语言最大的特点就是在运行时才确定某些代码的性质,比如数据类型等,并且运行时可以改变自身结构,比如PHP和JS可以使用执行函数eval()来执行一些非代码的字符串等
-
静态语言:运行时结构不可改变的语言,比如C/C++、java;常见的类C语言都是静态语言,这类语言最大的特点就是运行前必须规定代码的性质,比如必须规定一个变量的数据类型,否则连编译都无法通过,并且已一个类在运行时无法获取其内部信息
反射机制:让程序可以访问、检测和修改它本身状态或行为;java反射机制是指在Java程序运行状态中,对于任何一个类,都可以获得这个类的所有属性和方法;对于给定的一个对象,都能够调用它的任意一个属性和方法。这种动态获取类的内容以及动态调用对象的方法称为反射机制;
-
反射机制的核心:能够分析类的能力是java反射机制的核心,我们可以在运行时获得程序或者程序集中每个类型的成员已经成员的信息
2.1.2、应用
java反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
- 获取泛型信息。
- 获取注解信息。
2.1.3 韩顺平反射补充
2.1.3.1 反射机制原理图
解释:某类的源码通过javac编译成jvm字节码,我们通常在使用时通过new来将类实例化为对象,并通过对象调用成员方法等;在实例化为对象前,该类首先要被类加载器ClassLoader加载至堆区,并在堆区创建该类的Class类对象,该Class类对象包含被加载类的所有信息,且无论被加载类实例化出多少对象,它有且只有一个Class类对象;实例化出的被加载类对象记录着自己属于哪一个Class类对象;反射机制即是通过Class类对象来直接创建对象、操作属性等
2.1.3.2 反射简单应用举例
-
需求
-
源码实现
-
配置文件
re.properties
classfullpath = com.hspedu.Cat method = hi
-
Cat类
package com.hspedu; public class Cat { private String name = "tomcat"; public void hi() { System.out.println("hi, i am " + name); } }
-
ReflectionQuestion类
package com.hspedu.reflection.question; import com.hspedu.Cat; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Properties; /** * 韩java 01-02 */ public class ReflectionQuestion { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { // 1、传统方法,new出类的实例化对象 Cat cat = new Cat(); cat.hi(); System.out.println("_________________________"); // 2、通过I/O流的Properties类读出配置文件 Properties properties = new Properties(); properties.load(Files.newInputStream(Paths.get("src\re.properties"))); // 获取配置文件键值对的值 String classpath = properties.get("classfullpath").toString(); String method = properties.get("method").toString(); System.out.println(classpath); System.out.println(method); System.out.println("_____________________________"); // 3、反射机制 Class<?> cls = Class.forName(classpath); // 加载Cat类并范围其唯一的Class类对象 Object o = cls.newInstance(); // 通过Class类对象cls,得到加载类的实例对象 //System.out.println(o.getClass()); // 测试o真正的类型是否为Cat类 Method method1 = cls.getMethod(method);// 通过cls返回加载类的method的方法对象,即将Cat的hi方法通过对象形式返回并调用 method1.invoke(o); // 通过方法对象援引加载类的实例对象来调用方法 } }
当Cat类中新增了一个方法cry,我们仅需通过修改配置文件的method值即可,而不用修改源码
-
修改的Cat类
package com.hspedu; public class Cat { private String name = "tomcat"; public void hi() { System.out.println("hi, i am " + name); } // 新增方法 public void cry() { System.out.println("tomcat is cry"); } }
-
修改的配置文件
classfullpath = com.hspedu.Cat method = cry // x
ReflectionQuestion类的修改
- 通过new的传统方法
// 1、传统方法,new出类的实例化对象 Cat cat = new Cat(); // cat.hi(); --> 修改源码为 cat.cry(); System.out.println("_________________________");
- 而反射机制不需要修改源码,仅需修改配置文件即可,符合ocp开闭原则(对修改封闭,对拓展开放)
-
2.2、反射的简单使用
2.2.1、获得反射对象
-
Java反射运行程序再执行期间借助
refelction API
获得任何类的内部信息,直接操作任意对象的内部属性及其方法 -
反射对象获取方式:
获取方法 解释 Class.forName(<全类名>) 编译阶段,一般用配置文件中已知全类名信息 <类名>.class 加载阶段,一般用于参数传递 <实例化对象>.getClass() 运行阶段,已经有加载类的实例化对象 classLoader = <实例化对象>.getClass().getClassLoader()
classLoader.loadClass(cls)通过类加载器获取 <基本数据类型>.class 基本数据类型获取Class对象 <包装类>.TYPE 基本数据类型的包装类获取Class对象 -
加载完类后,会在内存中产生一个Class的对象,并且
一个类只有一个Class对象
,该Class对象包含整个类的完整结构,我们可以通过这个Class对象得到类的所有信息;这个Class对象就像一面镜子,通过镜子看到了类的结构,所以形象的称之为反射 -
代码演示
package kuang.Reflection.lesson2; /** * 通过反射获得类的Class对象de体验 */ public class TestObjectToClass { public static void main(String[] args) throws ClassNotFoundException { // 通过反射获得类的class对象 Class<?> c1 = Class.forName("kuang.Reflection.lesson2.TestObjectToClass"); System.out.println(c1); // 一个类在内存中只有一个Class对象 // 一个类被加载后类的整个结构都会被封装在Class对象中 Class<?> c2 = Class.forName("kuang.Reflection.lesson2.TestObjectToClass"); Class<?> c3 = Class.forName("kuang.Reflection.lesson2.TestObjectToClass"); Class<?> c4 = Class.forName("kuang.Reflection.lesson2.TestObjectToClass"); // 由hashcode相等可得,一个类在内存中只有一个Class对象 System.out.println(c2.hashCode()); System.out.println(c3.hashCode()); System.out.println(c4.hashCode()); } } /** * 实体类,pojo */ class Person { String name; int age; int id; public Person() {} public Person(String name, int age, int id) { this.name = name; this.age = age; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Person{" + "name="" + name + """ + ", age=" + age + ", id=" + id + "}"; } }
-
输出结果
2.2.2 韩顺平反射相关类补充(P4)
-
相关类及其作用
-
应用
package com.hspedu.reflection; import com.hspedu.Cat; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * 02韩顺平反射相关类,p4 */ public class Reflection_P4 { public static void main(String[] args) throws Exception { // 01,反射相关类之Class类,表示某个加载类在内存中的Class对象,该Class对象有且只有一个 Class<?> aClass = Class.forName("com.hspedu.Cat"); // 和上方作用相同,都是返回对应的Class对象,不过下面这个是通过加载类的实例化对象逆向得到的 Cat cat = new Cat(); Class<? extends Cat> aClass1 = cat.getClass(); // 通过加载类的Class对象实例化出被加载类的对象 Object o = aClass.newInstance(); Cat cat1 = aClass1.newInstance(); // 这里直接指定为Cat类是因为它本身就是通过Cat的实例化对象逆向产生的 // 02,反射相关类之Method,创建某个加载类的成员方法的方法对象 Method hi = aClass.getMethod("hi"); // 得到Cat类的hi成员方法的方法对象 // 该方法对象援引加载类的实例化对象即可执行该方法,这也是反射之精妙所在 // 普通方式:实例化对象.成员方法 // 反射方式:成员方法对象.方法(实例化对象) hi.invoke(o); // 03,反射相关类之Field,创建加载类的成员属性的对象 Field ageField = aClass.getField("age"); // 该方式不能得到私有属性 System.out.println("age == " + ageField.get(o)); // 通过反射方式打印字段数据 // 04,反射相关类之Constructor,创建加载类的构造器对象 Constructor<?> constructor1 = aClass.getConstructor(); // 返回无参构造器 System.out.println("无参构造器:"+ constructor1); // 创建有参构造器对象,要注意getConstructor方法的参数是Class类型,通过int.class即可得到int的Class类对象 Constructor<?> constructor2 = aClass.getConstructor(int.class); System.out.println("有参构造器:" + constructor2); } }
-
输出截图
2.2.3 反射机制优化
-
反射机制优点:可以动态的创建和使用对象,是所有框架的底层
-
反射机制缺点:反射机制的执行手段基本是解释执行,且对加载类的字段、方法、构造器有可访问性检测(访问检查)
-
优化手段:在使用反射机制调用加载类的字段、方法、构造器时手动关闭访问检查
setAccessible(); // 传入参数为true时关闭访问检查 // 使用方法举例 Object o = aClass.newInstance(); Method hi = aClass.getMethod("hi"); hi.setAccessible(true); // 传入参数true ···
2.3 认识Class类
2.3.1 Class类分析
-
Class类也是类,因此也继承Object类
-
Class不能通过new创建,它是由系统创建在堆区中的
某加载类的字节码二进制数据首先会通过类加载器CLassLoader的
loadClass()
方法生成该类的Class类对象(类加载机制),然后再实例化出对象;并且类加载过程在同一程序中只会进行依次,下一次实例化对象会直接使用已经在堆区生成的唯一个Class对象 -
某个类的Class对象在内存中有且只有一个,并且类加载只进行一次
-
每个类的实例化对象记录着自己由哪个Class对象所生成
-
通过Class类对象可以得到一个类的完成结构
-
类的字节码二进制数据存放在方法区(类的元数据),类的Class类对象存放在堆区
代码印证第三条
package com.hspedu.reflection; import com.hspedu.Cat; /** * han _p6,反射机制之Class类 */ public class Reflection_P6 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Cat cat = new Cat(); // 通过new实例化对象,Class对象已经生成在堆区 /*public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); }*/ Cat cat1 = new Cat(); // 打断点调试,不会再执行loadClass来生成Class对象 // 通过反射机制创建类的实例化对象 Class<?> aClass1 = Class.forName("com.hspedu.Cat"); Class<?> aClass2 = Class.forName("com.hspedu.Cat"); // 哈希值相同,说明Class类对象相同,说明只有一个Class类对象 System.out.println(aClass1.hashCode()); System.out.println(aClass2.hashCode()); } }
2.3.2 Class常用方法
方法名 | 作用 |
---|---|
Class.forName(<加载类的全类名>) | 获得加载类的Class类对象 Class<?> cls = Class.forName(classFullPath); |
cls.getClass() | 获取运行类型 |
cls.getPackage() | 获取包信息 |
cls.getName() | 获取全类名 |
cls.newInstance()》 cls.getDeclaredConstructor().newInstance() |
通过反射创建对象(该方法再java9之后过时,添加下面的方式) |
cls.getFields() | 获取所有字段信息,返回字段集数组 |
package com.hspedu.reflection._Class;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@SuppressWarnings("all")
public class Class01_p7 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
String classFullPath = "com.hspedu.Car";
// 获取Car类的Class类对象
Class<?> cls = Class.forName(classFullPath);
// 显示cls是哪个加载类的Class对象
System.out.println(cls);
// 获得运行类型
System.out.println(cls.getClass());
// 获取包名
System.out.println(cls.getPackage().getName());
// 获取加载类的全类名
System.out.println(cls.getName());
// 通过反射创建对象
Object o = cls.getDeclaredConstructor().newInstance();
// 通过反射获取所有字段信息,获取所有方法等方式类似
Field[] fields = cls.getFields();
for(Field f : fields) {
System.out.println(f.getName() + "==" + f.get(o));
}
}
}
2.4 动态和静态加载
-
区别:静态加载在程序编译阶段就将程序内所引用的资源全部加载进来,如果资源不存在则编译不通过;动态加载则是在程序运行阶段时,当程序中所引用的资源不存在时才会去加载,即使所引用资源不存在也可以通过编译阶段。
-
通过反射机制可以实现动态加载,即用时加载
-
代码示例:
package com.hspedu.reflection._Class; import java.lang.reflect.Method; import java.util.Scanner; /** * 静态加载和动态加载——p10 */ public class ClassLoaderTest { public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); System.out.println("请输入对应数字"); int num = scanner.nextInt(); switch (num) { case 1: // 通过反射实现动态加载,即使person不存在也可以通过编译 Class<?> person = Class.forName("Person"); Object o = person.newInstance(); Method hi = person.getMethod("hi"); hi.invoke(o); break; case 2: // 静态加载,当Car类不存在,编译不通过 // Car car = new Car(); // 通过javac命令编译,去掉注释编译不通过,因为是静态加载 System.out.println("2"); default: System.out.println("do nothing"); } } }
上述代码中,Person类不存在,但可以通过编译,说明反射是动态加载解释执行
2.5 反射爆破操作
即通过反射机制突破所访问对象的限定符,主要是针对私有属性无法直接获取的问题
2.5.1 属性
-
获取属性对象的两种方式:
// 方式1: cls.getField() // 只能获取public修饰的 cls.getDeclaredFiled() // 可以获取所有属性,包括私有的
-
私有属性爆破方式
<属性对象>.setAccessible(true)
-
变量赋值方式
<属性对象>.set(o,<值>) // 如果是静态属性,o可以写为null
-
案例代码
Student类
package com.hspedu; public class Student { public String name; private int age; public static int num; @Override public String toString() { return "Person{" + "name="" + name + """ + ", age=" + age + "}"; } }
测试类
package com.hspedu.reflection; import java.lang.reflect.Field; /** * 通过反射爆破属性_p17 */ public class ReflectAccessProperty { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException { Class<?> stuClass = Class.forName("com.hspedu.Student"); Object o = stuClass.newInstance(); // 通过反射获取public属性,并设置值 Field name = stuClass.getField("name"); name.set(o,"张三"); // 通过反射获取私有属性,并设置值 Field age = stuClass.getDeclaredField("age"); age.setAccessible(true); // 私有属性爆破 age.set(o,18); // 通过反射获取static变量,并设置值 Field num = stuClass.getField("num"); num.set(null,1433223); System.out.println(o); } }
2.5.2 方法
-
获取方法对象的方式
cls.getMethod("<方法名>",[<XXX>.class···]) // 获取公有方法,xxx.class即参数类型的class对象,无参则不写 cls.getDeclaredMethod("<方法名>",<XXX>.class) // 获取所有方法
-
爆破:
m.setAccessible(true)
-
调用方法
m.invoke(o,<实参列表>); // 针对不同方法有不同返回值 // 静态方法o可以写为null
-
案例代码
Student类
package com.hspedu; public class Student { public String name; private int age; public static int num; public String setNameAndPrint(String name) { this.name = name; return "名字是" + name; } private String setAgeAndPrint(int age) { this.age = age; return "年龄是" + age; } public static void hi(String person) { System.out.println("你好" + person); } @Override public String toString() { return "Person{" + "name="" + name + """ + ", age=" + age + "}"; } }
测试类
package com.hspedu.reflection; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectAccessMethod { public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { Class<?> stuClass = Class.forName("com.hspedu.Student"); Object o = stuClass.newInstance(); // 获取公有方法 Method name = stuClass.getMethod("setNameAndPrint", String.class); // 执行方法 Object returnName = name.invoke(o, "张三"); // 获取私有方法 Method age = stuClass.getDeclaredMethod("setAgeAndPrint", int.class); // 爆破私有限定符 age.setAccessible(true); // 执行方法 Object returnAge = age.invoke(o, 18); // 获取静态方法 Method hi = stuClass.getMethod("hi",String.class); // 执行方法 hi.invoke(null,"玛卡巴卡"); System.out.println(returnName); System.out.println(returnAge); } }
2.6 反射获取注解信息
-
注解依附于不同的Target,因此要根据注解位置的不同,创建不同的Class类对象
例如某注解是TYPE上的注解,依附于某个类,就要先创建该类的Class对象;依附于某字段属性上,就要在该类的Class对象的基础上创建Field对象
-
通过反射获取注解信息是许多框架的底层操作,也是学习myBatis等的知识点
-
案例代码
package com.hspedu.ReflectToAnno; import java.lang.annotation.Annotation; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.util.Arrays; /** * 反射获取注解信息测试 */ public class Test { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { Class<?> c = Class.forName("com.hspedu.ReflectToAnno.Person"); // 获取指定注解的value值 TableName table = c.getAnnotation(TableName.class); String v = table.value(); System.out.println("tableName is " + v); // 获得指定位置上的注解,这里获取Person类属性上的 Field name = c.getDeclaredField("name"); FieldInfo fieldInfo = name.getAnnotation(FieldInfo.class); System.out.println(fieldInfo.columnName()); System.out.println(fieldInfo.type()); System.out.println(fieldInfo.length()); // 获取注解上的注解信息 Class<?> aClass = Class.forName("com.hspedu.ReflectToAnno.FieldInfo"); Annotation[] annotations = aClass.getAnnotations(); for (Annotation a : annotations) { System.out.println(a); } Target target = aClass.getAnnotation(Target.class); System.out.println(Arrays.toString(target.value())); } }