User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Java   ›   Java Notes   ›   正文
Java Notes

Java—反射机制

2020-04-27 15:12:26
868  0 1
参考目录 隐藏
1) 反射是什么
2) Class 对象详解
3) 如何获取到Class对象
4) 类的加载过程
5) Class 常用的API
6) 获取运行时类完整信息并使用
7) 反射获取运行时类构造方法并使用
8) 反射获取运行时类成员变量信息
9) 获取私有公有类成员信息
10) 获取父类成员属性信息
11) 设置或修改类成员变量值
12) 获取成员变量注解信息
13) 反射获取运行时类对对象方法信息并调用
14) 获取对象方法
15) 调用对象方法
16) 获取方法上的注解信息
17) 获取方法参数及参数注解信息
18) 获取方法返回参数和方法权限修饰符
19) 反射获取运行时类信息、接口信息、包信息
20) 获取运行时类的接口信息
21) 获取类所在包的信息
22) 获取类上注解信息
23) 反射获取运行时类的父类的泛型信息、接口泛型信息
24) 反射应用场景及实战案例
25) AOP + 自定义注解 修改前端传递的参数信息
26) Mybatis 拦截器实现自动填充创建人、修改人信息

阅读完需:约 36 分钟

反射是什么

JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

不过要想解剖一个类,就要先获取到该类的字节码文件对应的Class类型的对象.

“反射之所以被称为框架的灵魂“,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力,这种能力也就是我们常说的动态性,利用这种性质可以使编写的程序更灵活更通用。

反射机制可以用来:

  • 在运行时分析类的能力,如可以构造任意一个类,可以获取任意一个类的全部信息,
  • 在运行时检查对象,如在运行时判断任意一个对象所属的类
  • 实现泛型数组操作代码,因为在运行时可以获取泛型信息
  • 利用Method对象,如我们经常使用的动态代理,就是使用Method.invoke()来实现方法的调用。

反射是一种功能强大且复杂的机制,在开发Java工具或框架方面,反射更是不可缺少的一部分。

Class 对象详解

之前说到了,如果要分析一个类,就必须要获取到该类的字节码文件对应 Class 类型对象。

另外如果有听到类模板对象,这个说的其实就是Class对象,大家不要误会了。

如何获取到Class对象

得到Class的方式总共有四种:

  • 通过对象调用 getClass()方法来获取
  • 直接通过类名.class 的方式得到
  • 通过 Class对象的 forName() 静态方法来获取
  • Classloader,通过类加载器进行获取
 /**
  * @description:
  * @author: Ning Zaichun
  * @date: 2022年09月15日 22:49
  */
 public class ClassDemo01 {
 ​
     @Test
     public void test1() throws Exception {
         //1、通过对象调用 getClass() 方法来获取
         //  类型的对象,而我不知道你具体是什么类,用这种方法
         Student student = new Student();
         Class studentClass1 = student.getClass();
         System.out.println(studentClass1);
         // out: class com.nzc.Student
 ​
         //2、直接通过`类名.class` 的方式得到
         // 任何一个类都有一个隐含的静态成员变量 class
         // Class studentClass2 = Student.class;
         Class<?> studentClass2 = Student.class;
         System.out.println(studentClass2);
         // out: class com.nzc.Student
         System.out.println("studentClass1和studentClass2 是否相等==>" + (studentClass1 == studentClass2));
         // studentClass1和studentClass2 是否相等==>true
 ​
         //3、通过 Class 对象的 forName() 静态方法来获取,使用的最多
         //   但需要抛出或捕获 ClassNotFoundException 异常
         Class<?> studentClass3 = Class.forName("com.nzc.Student");
         System.out.println(studentClass3);
         // out: class com.nzc.Student
         System.out.println("studentClass1和studentClass3 是否相等==>" + (studentClass1 == studentClass3));
         //studentClass1和studentClass3 是否相等==>true
 ​
         //4、 使用类的加载器:ClassLoader 来获取Class对象
         ClassLoader classLoader = ClassDemo01.class.getClassLoader();
         Class studentClass4 = classLoader.loadClass("com.nzc.Student");
         System.out.println(studentClass4);
         System.out.println("studentClass1和studentClass4 是否相等==>" + (studentClass1 == studentClass4));
         //studentClass1和studentClass4 是否相等==>true
     }
 }

在这四种方式中,最常使用的是第三种方式,第一种都直接new对象啦,完全没有必要再使用反射了;第二种方式也已经明确了类的名称,相当于已经固定下来,失去了一种动态选择加载类的效果;而第三种方式,只要传入一个字符串,这个字符串可以是自己传入的,也可以是写在配置文件中的。

像在学 JDBC 连接的时候,大家肯定都使用过 Class对象的 forName() 静态方法来获取Class对象,再加载数据库连接对象,但可能那时候只是匆匆而过罢了。’

注意:不知道大家有没有观察,我把各种方式所获取到的class对象,都进行了一番比较,并且结果都为true,这是因为一个类在 JVM 中只会有一个 Class 实例,为了解释此点,我把类加载过程也简单的做了一个陈述。

类的加载过程

当我们需要使用某个类时,如果该类还未被加载到内存中,则会经历下面的过程对类进行初始化。

即类的加载 —> 链接 —> 初始化三个阶段。

在这里只着眼于类的加载过程

加载过程:

  1. 在我们进行编译后(javac.exe命令),会生成一个或多个字节码文件(就是项目中编译完会出现的 target 目录下的以.class结尾的文件)
  2. 接着当我们使用 java.exe 命令对某个字节码文件进行解释运行时。
  3. 加载过程
    • 就相当于将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构;
    • 并生成一个代表这个类的 java.lang.Class 对象,这个加载到内存中的类,我们称为运行时类,此运行时类,就是 Class的一个实例,所有需要访问和使用类数据只能通过这个 Class 对象。
    • 所谓Class对象,也称为类模板对象,其实就是 Java 类在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、 类字段、类方法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获 取 Java 类中的任意信息,能够对 Java 类的成员变量进行遍历,也能进行 Java 方法的调用。
    • 反射的机制即基于这一基础。如果 JVM 没有将 Java 类的声明信息存储起来,则 JVM 在运行期也无法进行反射
  4. 这个加载的过程还需要类加载器参与,关于类加载器的类型大家可以去了解了解,还有双亲委派机制等,此处我便不再多言

Class 常用的API

通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等

  • getName():获得类的完整名字。
  • getFields():获得类的public类型的属性。
  • getDeclaredFields():获得类的所有属性。包括private声明的和继承类
  • getMethods():获得类的public类型的方法。
  • getDeclaredMethods():获得类的所有方法。包括private声明的和继承类
  • getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
  • getConstructors():获得类的public类型的构造方法。
  • getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。
  • newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

另外就还有反射包下的几个常用的对象Constructor、Filed、Method等,分别表示类的构造器、字段属性、方法等等

获取运行时类完整信息并使用

所谓运行时类,就是程序运行时所创建出来的类,你直接理解为通过反射获取到的类也可。大体意思是如此。

在讲述这一小节时,先要理解Java中一切皆对象这句话。

我们平常编写一个类,我们会将它称为一个Java对象,但是在反射这里将此概念再次向上抽象了。

类的基本信息:构造方法、成员变量,方法,类上的注解,方法注解,成员变量注解等等,这些都是Java对象,也是证明了Java中一切皆对象这句话。

其中Constructor就表示构造方法的对象,他包含了构造方法的一切信息,

Field、Method等等都是如此。

不要太过于麻烦和重复书写,所有相关代码,都放在此处了,案例中的依赖全部基于此。

 public interface TestService {
 }
 @Target({ElementType.TYPE,ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MarkAnnotation {
 ​
     String value() default "";
 ​
 }
 public class Generic<T> {
 }
 public interface GenericInterface<T> {
 }
 // TYPE 表示可以标记在类上
 //  PARAMETER 表示可以标记在方法形式参数
 //  METHOD 方法
 //  FIELD 成员属性上
 @Target({ElementType.TYPE,ElementType.METHOD, ElementType.FIELD,ElementType.PARAMETER})
 @Retention(RetentionPolicy.RUNTIME) // 这里表明是运行时注解
 @Documented
 public @interface LikeAnnotation {
 ​
     String value() default "";
 ​
 ​
     String[] params() default {};
 }
 @Data
 @MarkAnnotation
 public class Person implements TestService{
     private String sex;
 ​
     public Integer age;
 ​
     private void mySex(String sex){
         System.out.println("我的性别"+sex);
     }
 ​
     public void myAge(Integer age){
         System.out.println("我的年龄"+age);
     }
 ​
 ​
 }
 @ToString(callSuper = true) // 增加这行是为了打印时将父类属性也打印出来,方便查看~
 @Data
 @LikeAnnotation(value = "123")
 public class Student extends Person implements Serializable {
     @LikeAnnotation
     private String username;
 ​
     @LikeAnnotation
     public String school;
 ​
     private Double score;
 ​
     public Student() {
     }
 ​
     private Student(String username) {
         this.username = username;
     }
 ​
     public Student(String username, String school) {
         this.username = username;
         this.school = school;
     }
 ​
     @LikeAnnotation
     public void hello() {
         System.out.println("世界,你好");
     }
 ​
     public void say( String username) {
         System.out.println("你好,我叫" + username);
     }
 ​
     private void myScore(Double score) {
         System.out.println("我的分数是一个私密东西," + score);
     }
 ​
     public void annotationTest(@LikeAnnotation  String username,@MarkAnnotation String str){
         System.out.println( "测试获取方法参数中的注解信息");
     }
 }

反射获取运行时类构造方法并使用

class获取构造方法的相关API

 // 获取所有的构造函数
 Constructor<?>[] getConstructors()
 ​
 // 获取 public或 private 修饰的狗赞函数,只要参数匹配即可
 Constructor<?>[] getDeclaredConstructors()
 ​
 // 获取所有 public 修饰的 构造函数
 Constructor<T> getConstructor(Class<?>... parameterTypes)
     
 //调用此方法,创建对应的运行时类的对象。
 public T newInstance(Object ... initargs)
复制代码

newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

要想此方法正常的创建运行时类的对象,要求:

  • 运行时类必须提供空参的构造器;
  • 空参的构造器的访问权限得够。通常,设置为 public。

为什么要 javabean 中要求提供一个 public 的空参构造器?

原因: 1、便于通过反射,创建运行时类的对象;2、便于子类继承此运行时类时,默认调用 super() 时,保证父类有此构 造器。

想要更详细的了解,建议去看生成的字节码文件,在那里能够给出你答案。

测试

 /**
  * @description:
  * @author: Ning Zaichun
  * @date: 2022年09月17日 1:17
  */
 public class ConstructorTest {
 ​
     /**
      * 获取公有、私有的构造方法 并调用创建对象
      */
     @Test
     public void test1() throws Exception {
         Class<?> aClass = Class.forName("com.nzc.Student");
 ​
         System.out.println("======获取全部public 修饰的构造方法=========");
         Constructor<?>[] constructors = aClass.getConstructors();
         for (Constructor<?> constructor : constructors) {
             System.out.println(constructor);
         }
         System.out.println("======获取 public、private 修饰的构造方法,只要参数匹配即可=========");
         /**
          * 这里的参数 Class<?>... parameterTypes 填写的是参数的类型,而并非某个准确的值信息
          */
         Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
         System.out.println(constructor);
         // 因为此构造函数是 private 修饰,如果不设置暴力反射,则没有权限访问。
         // 这里setAccessible(true) 是设置暴力反射,如果不设置,则会报错,
         constructor.setAccessible(true);
         // 这里调用的有参构造,所以在调用 newInstance 方法时,也需要填写参数
         Object o1 = constructor.newInstance("宁在春");
         constructor.setAccessible(false);
         System.out.println("o1===>"+o1);
 ​
         /**
          * 如果需要获取有参构造,只需要填写对应的参数类型即可,
          * 获取无参构造,填null或不填都可。
          */
         Constructor<?> constructor1 = aClass.getConstructor();
         Constructor<?> constructor2 = aClass.getConstructor(String.class,String.class);
         System.out.println("无参构造==>"+constructor1);
         System.out.println("有参构造==>"+constructor2);
         Object o2 = constructor1.newInstance();
         Object o3 = constructor2.newInstance("宁在春2","xxxx社会");
         System.out.println("o2===>"+o2);
         System.out.println("o3===>"+o3);
     }
 }

既然能够获取到构造方法,那么也就是可以使用的,用Constructor.newInstance()方法来调用构造方法即可,在下列的打印信息中,也可以看出来确实如此,如果明确要获取为Student对象的话,进行强转即可。

 ======获取全部public 修饰的构造方法=========
 public com.nzc.Student(java.lang.String,java.lang.String)
 public com.nzc.Student()
 ======获取 public、private 修饰的构造方法,只要参数匹配即可=========
 private com.nzc.Student(java.lang.String)
 o1===>Student(username=宁在春, school=null, age=null)
 无参构造==>public com.nzc.Student()
 有参构造==>public com.nzc.Student(java.lang.String,java.lang.String)
 o2===>Student(username=null, school=null, age=null)
 o3===>Student(username=宁在春2, school=xxxx社会, age=null)
复制代码

反射获取运行时类成员变量信息

class对象中获取类成员信息使用到的API

 Field[] getFields();
  
 Field[] getDeclaredFields();
 ​
 Field getDeclaredField(String name);
 ​
 public native Class<? super T> getSuperclass();

这里的 Field 类对象,其实就是表示类对象中的成员属性,不过还有多了很多其他在反射时需要用到的属性和API罢了

获取私有公有类成员信息

为了有更好的对比,我先编写了一段不使用反射时的正常操作。

 /**
      * 不使用反射的进行操作
      */
 @Test
 public void test1() {
     Student student = new Student();
     student.setUsername("username");
     student.setSchool("xxxx社会");
     student.setScore(100.0);
     // 永远三岁的小伙子 哈哈 ?
     student.setAge(3);
     student.setSex("男");
 ​
     System.out.println("student信息===>" + student);
     // out:student信息===>Student(super=Person(sex=男, age=3), username=username, school=xxxx社会, score=100.0)
 }

现在我再使用反射来实现上述操作

在实现之前,还是先来看看如何获取公有、私有的成员变量吧

 @Test
 public void test2() throws Exception {
     Class<?> stuClass = Class.forName("com.nzc.Student");
 ​
     System.out.println("========获取所有 public 修饰的成员属性(包括父类属性)=====");
     Field[] fields = stuClass.getFields();
     for (Field field : fields) {
         System.out.println(field);
     }
     //public java.lang.String com.nzc.Student.school
     //public java.lang.Integer com.nzc.Person.age
 ​
     System.out.println("========获取所有(public、private、protected等修饰的)属性成员(不包括父类成员属性)=====");
     Field[] fields1 = stuClass.getDeclaredFields();
     for (Field field : fields1) {
         System.out.println(field);
     }
     //private java.lang.String com.nzc.Student.username
     //public java.lang.String com.nzc.Student.school
     //private java.lang.Double com.nzc.Student.score
 ​
     System.out.println("========通过反射,获取对象指定的属性=====");
     Field username = stuClass.getDeclaredField("username");
     System.out.println("username===>" + username);
 }

这无法获取到父类的成员变量信息,父类的信息,我们该如何获取呢

获取父类成员属性信息

其实一样的,我们也是要获取到父类的Class对象,在Class API中有一个getSuperClass()方法可以获取到父类的class对象,其他的操作都是一样的

 @Test
 public void test5() throws ClassNotFoundException {
     Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("========获取所有属性成员(包括父类成员属性)=====");
     Class clazz = stuClass;
     List<Field> fieldList = new ArrayList<>();
     while (clazz != null) {
         fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
         clazz = clazz.getSuperclass();
     }
     Field[] fields2 = new Field[fieldList.size()];
     fieldList.toArray(fields2);
     for (Field field : fields2) {
         System.out.println(field);
     }
     //private java.lang.String com.nzc.Student.username
     //public java.lang.String com.nzc.Student.school
     //private java.lang.Double com.nzc.Student.score
     //private java.lang.String com.nzc.Person.sex
     //public java.lang.Integer com.nzc.Person.age
 }

到这里我们已经知道如何获取到类的成员变量信息了,就可以来看看如何给它们设置值,或者通过反射的方式来获取到值了。

设置或修改类成员变量值

 @Test
 public void test3() throws Exception {
     Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("获取到 filed 后,设置值或修改值");
     Constructor<?> constructor = stuClass.getConstructor();
     Object o = constructor.newInstance();
 ​
     //操作pubilc 属性
     Field school = stuClass.getDeclaredField("school");
     school.set(o,"xxxx社会");
     System.out.println(o);
     //Student(super=Person(sex=null, age=null), username=null, school=xxxx社会, score=null)
     // 另外既然可以设置,那么自然也是可以获取的
     System.out.println(school.get(o));
     //xxxx社会
 ​
     // 操作 private 修饰的成员属性
     Field name = stuClass.getDeclaredField("username");
     // setAccessible(true) 因为是获取private 修饰的成员变量
     // 不设置暴力反射 是无法获取到的,会直接报 IllegalAccessException 异常
     name.setAccessible(true);
     name.set(o,"Ningzaichun");
     name.setAccessible(false);
     System.out.println(o);
     //Student(super=Person(sex=null, age=null), username=Ningzaichun, school=xxxx社会, score=null)
 }

其实看到这里对于反射已经有个大概的印象了,并且这都是比较平常且实用的一些方法。

获取成员变量注解信息

 @Test
 public void test4() throws Exception {
     Class<?> stuClass = Class.forName("com.nzc.Student");
     Field school = stuClass.getDeclaredField("school");
 ​
     LikeAnnotation annotation = school.getAnnotation(LikeAnnotation.class);
     System.out.println(annotation);
     //@com.nzc.LikeAnnotation(params=[], value=)
 ​
     Annotation[] annotations = school.getAnnotations();
     for (Annotation annotation1 : annotations) {
         System.out.println(annotation1);
     }
     //@com.nzc.LikeAnnotation(params=[], value=)
 ​
 ​
     LikeAnnotation declaredAnnotation = school.getDeclaredAnnotation(LikeAnnotation.class);
     System.out.println(declaredAnnotation);
     //@com.nzc.LikeAnnotation(params=[], value=)
 ​
     Annotation[] declaredAnnotations = school.getDeclaredAnnotations();
     for (Annotation declaredAnnotation1 : declaredAnnotations) {
         System.out.println(declaredAnnotation1);
     }
     //@com.nzc.LikeAnnotation(params=[], value=)
 }

反射获取运行时类对对象方法信息并调用

Class对象中使用的相关API

 Method[] getMethods();
 ​
 Method getMethod(String name, Class<?>... parameterTypes);
 ​
 Method[] getDeclaredMethods();
 ​
 Method getDeclaredMethod(String name, Class<?>... parameterTypes);
 ​
 // 获取方法返回值类型
 Class<?> getReturnType();
 ​
 // obj – 调用底层方法的对象 
 // args – 用于方法调用的参数 
 // return 使用参数args在obj上调度此对象表示的方法的结果
 Object invoke(Object obj, Object... args)

获取对象方法

获取公有、私有方法

 @Test
 public void test1() throws Exception {
     Class<?> stuClass = Class.forName("com.nzc.Student");
 ​
     System.out.println("========获取所有public修饰的方法======");
     Method[] methods = stuClass.getMethods();
     for (Method method : methods) {
         System.out.println(method);
     }
     //public boolean com.nzc.Student.equals(java.lang.Object)
     //public java.lang.String com.nzc.Student.toString()
     //public int com.nzc.Student.hashCode()
     //public void com.nzc.Student.hello()
     //public void com.nzc.Student.say(java.lang.String)
     //public java.lang.Double com.nzc.Student.getScore()
     //public void com.nzc.Student.setScore(java.lang.Double)
     //public void com.nzc.Student.setSchool(java.lang.String) .... 还有一些没写出来了
 ​
     System.out.println("========获取对象【指定的public修饰的方法】======");
     // 第一个参数为方法名,此处是获取public修饰的无参方法
     Method hello = stuClass.getMethod("hello");
     System.out.println("hello===>"+hello);
     // 带参方法,第二个参数填写【参数类型】
     Method say = stuClass.getMethod("say", String.class);
     System.out.println("say===>"+say);
     //hello===>public void com.nzc.Student.hello()
     //say===>public void com.nzc.Student.say(java.lang.String)
 ​
 ​
     // 参数为 方法名,此处是获取 private 修饰的无参方法
     // stuClass.getDeclaredMethod("");
     Method myScore = stuClass.getDeclaredMethod("myScore", Double.class);
     System.out.println("myScore==>"+myScore);
     //myScore==>private void com.nzc.Student.myScore(java.lang.Double)
 ​
     System.out.println("=======获取所有的方法=======");
     Method[] declaredMethods = stuClass.getDeclaredMethods();
     for (Method declaredMethod : declaredMethods) {
         System.out.println(declaredMethod);
     }
     //public void com.nzc.Student.say(java.lang.String)
     //private void com.nzc.Student.myScore(java.lang.Double)
     //public java.lang.Double com.nzc.Student.getScore()
     //public void com.nzc.Student.setScore(java.lang.Double)
     //protected boolean com.nzc.Student.canEqual(java.lang.Object)
 }

调用对象方法

还记得经常能看到的一个invoke()方法吗,这里就是调用method.invoke()方法来实现方法的调用。

 @Test
 public void test2() throws Exception {
     Class<?> stuClass = Class.forName("com.nzc.Student");
     Constructor<?> constructor = stuClass.getConstructor();
     Object o = constructor.newInstance();
 ​
     Method myScore = stuClass.getDeclaredMethod("myScore", Double.class);
     myScore.setAccessible(true);
     // 调用方法
     myScore.invoke(o, 99.0);
     // 相当于 Student student= new Student();
     // student.myScore(99.0);
     myScore.setAccessible(false);
 }

Method它本身就记录了方法的一切信息

获取方法上的注解信息

 @Test
 public void test2() throws Exception {
     Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("==== 获取成员变量上指定的注解信息===");
 ​
     Field username = stuClass.getDeclaredField("username");
     System.out.println(username);
     //private java.lang.String com.nzc.Student.username
     Annotation annotation = username.getAnnotation(LikeAnnotation.class);
     System.out.println(annotation);
     //@com.nzc.LikeAnnotation(params=[], value=)
 ​
     Method hello = stuClass.getDeclaredMethod("hello");
     LikeAnnotation annotation1 = hello.getAnnotation(LikeAnnotation.class);
     System.out.println(hello+"===="+annotation1);
     // public void com.nzc.Student.hello()====@com.nzc.LikeAnnotation(params=[], value=)
 }

获取方法参数及参数注解信息

不过在写项目时,有可能还会要获取方法参数上的注解,那该如何获取方法参数呢?又该如何获取方法参数的注解信息呢?

 @Test
 public void test3() throws Exception {
     Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("==== 获取方法参数中的注解信息===");
 ​
     Method annotationTest = stuClass.getDeclaredMethod("annotationTest",String.class,String.class);
 ​
     // 获取方法的返回值类型
     Class<?> returnType = annotationTest.getReturnType();
     // 获取权限修饰符
         System.out.println(Modifier.toString(annotationTest.getModifiers()) );
     // 获取到全部的方法参数
     Parameter[] parameters = annotationTest.getParameters();
     for (Parameter parameter : parameters) {
         Annotation annotation = parameter.getAnnotation(LikeAnnotation.class);
         if(annotation!=null){
             // 参数类型
             Class<?> type = parameter.getType();
             // 参数名称
             String name = parameter.getName();
             System.out.println("参数类型"+type+"  参数名称==>"+name+" 参数上的注解信息"+annotation);
             //参数类型class java.lang.String  参数名称==>arg0 参数上的注解信息@com.nzc.LikeAnnotation(params=[], value=)
         }
     }
     // 获取参数上全部的注解信息
     Annotation[][] parameterAnnotations = annotationTest.getParameterAnnotations();
     for (int i = 0; i < parameterAnnotations.length; i++) {
         for (int i1 = 0; i1 < parameterAnnotations[i].length; i1++) {
             System.out.println(parameterAnnotations[i][i1]);
         }
     }
     // @com.nzc.LikeAnnotation(params=[], value=)
     //@com.nzc.MarkAnnotation(value=)
     int parameterCount = annotationTest.getParameterCount();
     System.out.println("获取参数个数==>"+parameterCount);
 }

获取方法返回参数和方法权限修饰符

 @Test
 public void test33() throws Exception {
     Class<?> stuClass = Class.forName("com.nzc.Student");
 ​
     Method annotationTest = stuClass.getDeclaredMethod("annotationTest",String.class,String.class);
     // 获取方法的返回值类型
     Class<?> returnType = annotationTest.getReturnType();
     System.out.println(returnType);
     //void
     // 获取权限修饰符
     System.out.println(Modifier.toString(annotationTest.getModifiers()) );
     //public
 }

反射获取运行时类信息、接口信息、包信息

获取运行时类的接口信息

 /**
      *  获取运行时类实现的接口
      */
 @Test
 public void test5() {
     Class clazz = Student.class;
     Class[] interfaces = clazz.getInterfaces();
     for (Class c : interfaces) {
         System.out.println(c);
     }
     System.out.println("====================");
     // 获取运行时类的父类实现的接口
     Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
     for (Class c : interfaces1) {
         System.out.println(c);
     }
 }

获取类所在包的信息

 /**
      * 获取运行时类所在的包
      */
 @Test
 public void test6() {
     Class clazz = Person.class;
     Package pack = clazz.getPackage();
     System.out.println(pack);
     // out:package com.nzc
 }

获取类上注解信息

 @Test
 public void test1() throws Exception {
     Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("==== 获取类上指定的注解信息===");
 ​
     LikeAnnotation annotation = stuClass.getAnnotation(LikeAnnotation.class);
     System.out.println(annotation);
     //@com.nzc.LikeAnnotation(params=[], value=123)
     System.out.println("获取注解上的值信息==>"+annotation.value());
     //获取注解上的值信息==>123
     //annotation.params(); 注解有几个属性,就可以获取几个属性
 ​
     System.out.println("====  获取类上全部注解信息===");
 ​
     Annotation[] annotations = stuClass.getAnnotations();
     for (Annotation annotation1 : annotations) {
         System.out.println(annotation1);
         //@com.nzc.LikeAnnotation(params=[], value=123)
     }
 ​
     LikeAnnotation declaredAnnotation = stuClass.getDeclaredAnnotation(LikeAnnotation.class);
     System.out.println("declaredAnnotation==>"+declaredAnnotation);
     //declaredAnnotation==>@com.nzc.LikeAnnotation(params=[], value=123)
     Annotation[] declaredAnnotations = stuClass.getDeclaredAnnotations();
 ​
 }

注意:lombok 相关的注解是无法获取到的,因为 lombok 注解为编译时注解,并非是运行时注解,在编译完成后,lombok 注解并不会保留于class文件中,因此是无法通过反射获取到的。

@Data 也标明了它的存在级别为源码级别,而运行时存在注解@Retention为@Retention(RetentionPolicy.RUNTIME) 。

反射获取运行时类的父类的泛型信息、接口泛型信息

 @Test
 public void test8(){
     Class clazz = Person.class;
 ​
     // 获取泛型父类信息
     Type genericSuperclass = clazz.getGenericSuperclass();
     System.out.println(genericSuperclass);
     // com.nzc.Generic<java.lang.String>
     // 获取泛型接口信息
     Type[] genericInterfaces = clazz.getGenericInterfaces();
 ​
     for (Type genericInterface : genericInterfaces) {
         System.out.println(genericInterface);
         //interface com.nzc.TestService
         //com.nzc.GenericInterface<java.lang.String>
     }
 }

反射应用场景及实战案例

一句话说它的应用场景就是:确定不下来到底使用哪个类的时候,比如你要开发一个通用工具类,为了达到通用性,传入的参数对象,一般都是无法限制的,这个时候就是要用到反射啦~。

反射的特征:动态性

AOP + 自定义注解 修改前端传递的参数信息

需求如下:我现在引入了一个第三方 jar 包,里面有一个 MyBatis-Plus 查询构造器,其中构造 LIKE条件查询的条件是当前端传过来的参数带有逗号时,拼接为LIKE查询条件。

关键代码:

标识在对象的某个成员属性上

 @Target({ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface LikeAnnotation {
 ​
     String value() default "";
 }
 

标识在Controller层上,以此来判断那些请求是需要被切入的。

 @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MarkAnnotation {
 ​
     String value() default "";
 ​
 }

一个非常简单的Javabean

 @Data
 public class Login {
 ​
     @LikeAnnotation(value = "username")
     private String username;
 ​
     private String password;
 ​
     public Login() {
     }
 ​
     public Login(String username, String password) {
         this.username = username;
         this.password = password;
     }
 }
 

Controller 类

 @Slf4j
 @RestController
 public class HelloController {
 ​
     @MarkAnnotation
     @GetMapping("/search")
     public Login getLikeLogin(Login login){
         System.out.println(login);
         return login;
     }
 ​
 }

重点重点,切面类 LikeAnnotationAspect,处理逻辑全部在此处

 @Aspect
 @Component
 @Slf4j
 public class LikeAnnotationAspect {
 ​
     // 标记了 @MarkAnnotation 注解的才切入 降低性能消耗
     @Pointcut("@annotation(com.nzc.annotation.MarkAnnotation)")
     public void pointCut() {
 ​
     }
 ​
     // 获取当前类及父类所有的成员属性
     private static Field[] getAllFields(Object object) {
         Class<?> clazz = object.getClass();
         List<Field> fieldList = new ArrayList<>();
         while (clazz != null) {
             fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
             clazz = clazz.getSuperclass();
         }
         Field[] fields = new Field[fieldList.size()];
         fieldList.toArray(fields);
         return fields;
     }
 ​
     @Around("pointCut()")
     public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
         long start = System.currentTimeMillis();
         Object[] args = pjp.getArgs();
         // 获取到第一个参数
         Object parameter = args[0];
 ​
         log.info("parameter==>{}", parameter);
         if (parameter == null) {
             return pjp.proceed();
         }
 ​
         Field[] fields = getAllFields(parameter);
 ​
         for (Field field : fields) {
             log.debug("------field.name------" + field.getName());
             if (field.getAnnotation(LikeAnnotation.class) != null) {
                 try {
                     field.setAccessible(true);
                     Object username = field.get(parameter);
                     field.setAccessible(false);
                     if (username != null && !username.equals("")) {
                         field.setAccessible(true);
                         field.set(parameter, "," + username + ",");
                         field.setAccessible(false);
                     }
                 } catch (Exception e) {
                 }
             }
         }
         // 调用方法
         Object result = pjp.proceed();
         long end = System.currentTimeMillis();
         log.debug("修改耗时==>" + (end - start) + "ms");
         return result;
     }
 ​
 }

Mybatis 拦截器实现自动填充创建人、修改人信息

看似好像写业务的我们,没有怎么接触Java反射,但实际上可能处处都隐含着反射。

使用过 Mybatis-Plus 的朋友,应该知道,可以设置自动填充数据(创建时间、更新时间、创建人、更新人等),不过那个是实现MetaObjectHandler接口进行处理的。

但是今天的话,我用Mybatis 原生的拦截器来进行一番实现,实现每次更新、添加时自动填充创建人、更新人等,表里没时间字段,就没演示时间了,但实现原理都一致。

 import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.binding.MapperMethod.ParamMap;
 import org.apache.ibatis.executor.Executor;
 import org.apache.ibatis.mapping.MappedStatement;
 import org.apache.ibatis.mapping.SqlCommandType;
 import org.apache.ibatis.plugin.*;
 import org.springframework.stereotype.Component;
 ​
 import java.lang.reflect.Field;
 import java.util.*;
 ​
 /**
  * mybatis拦截器,自动注入创建人、创建时间、修改人、修改时间
  * @author Ning Zaichun
  */
 @Slf4j
 @Component
 @Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
 public class MybatisInterceptor implements Interceptor {
     
     public static Field[] getAllFields(Object object) {
         Class<?> clazz = object.getClass();
         List<Field> fieldList = new ArrayList<>();
         while (clazz != null) {
             fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
             clazz = clazz.getSuperclass();
         }
         Field[] fields = new Field[fieldList.size()];
         fieldList.toArray(fields);
         return fields;
     }
 ​
     @Override
     public Object intercept(Invocation invocation) throws Throwable {
         MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
         String sqlId = mappedStatement.getId();
         log.info("------sqlId------" + sqlId);
 //2022-09-17 17:16:50.714  INFO 14592 --- [nio-8080-exec-1] com.nzc.tree.commons.MybatisInterceptor  : ------sqlId------com.nzc.tree.mapper.CategoryMapper.updateById
         SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
         Object parameter = invocation.getArgs()[1];
         log.info("------sqlCommandType------" + sqlCommandType);
 //2022-09-17 17:16:50.714  INFO 14592 --- [nio-8080-exec-1] com.nzc.tree.commons.MybatisInterceptor  : ------sqlCommandType------UPDATE
 ​
         if (parameter == null) {
             return invocation.proceed();
         }
         if (SqlCommandType.INSERT == sqlCommandType) {
             Field[] fields = getAllFields(parameter);
             for (Field field : fields) {
                 log.info("------field.name------" + field.getName());
                 try {
                     if ("createBy".equals(field.getName())) {
                         field.setAccessible(true);
                         Object local_createBy = field.get(parameter);
                         field.setAccessible(false);
                         if (local_createBy == null || local_createBy.equals("")) {
                                 field.setAccessible(true);
                                 field.set(parameter, "nzc-create");
                                 field.setAccessible(false);
                             }
                     }
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         }
         if (SqlCommandType.UPDATE == sqlCommandType) {
             Field[] fields = null;
             if (parameter instanceof ParamMap) {
                 ParamMap<?> p = (ParamMap<?>) parameter;
                 if (p.containsKey("et")) {
                     parameter = p.get("et");
                 } else {
                     parameter = p.get("param1");
                 }
                 if (parameter == null) {
                     return invocation.proceed();
                 }
 ​
                 fields = getAllFields(parameter);
             } else {
                 fields = getAllFields(parameter);
             }
 ​
             for (Field field : fields) {
                 log.info("------field.name------" + field.getName());
                 try {
                     if ("updateBy".equals(field.getName())) {
                             field.setAccessible(true);
                             field.set(parameter,"nzc-update");
                             field.setAccessible(false);
                     }
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         }
         return invocation.proceed();
     }
 ​
     @Override
     public Object plugin(Object target) {
         return Plugin.wrap(target, this);
     }
 ​
     @Override
     public void setProperties(Properties properties) {
         // TODO Auto-generated method stub
     }
 }

正如文中所说,所谓Class对象,也称为类模板对象,其实就是 Java 类在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、 类字段、类方法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获取 Java 类中的任意信息,能够对 Java 类的成员变量进行遍历,也能进行 Java 方法的调用,获取到类的任意信息。

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 593718浏览

随机文章
Kotlin-协程—协程进阶(三十二)
4年前
Spring—IOC容器(构建)
3年前
Kotlin-类型初步—智能类型转换(十)
4年前
MyBatis懒加载
5年前
Java—并发编程(二)synchronized关键字
4年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 593718 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付