Java反射详解,学以致用,实战案例(AOP修改参数、Mybatis拦截器实现自动填充)1

简介: Java反射详解,学以致用,实战案例(AOP修改参数、Mybatis拦截器实现自动填充)

作为Java开发者,你认为反射这个知识点重要程度,在你心里是什么样的呢?

以前我也只觉得反射非常重要,但总归是听这个文章说,听那个朋友说,学是学了,但却没怎么应用。

当我正式进入到社会当 cv 仔的时候,需要考虑的问题多了,慢慢思考问题了,就觉得反射是个力大无穷的东西,更会感觉反射是个无所不能的东西,如各种各样的框架的底层,各种各样的拦截器的实现,反射都是其中少不了的一部分~

如果平时着重于开发业务的话,那么确实可能会较少使用到反射机制,但并非是说反射它不重要,反射它是Java 框架的基础勒,可以说木有反射,Java的动态性是会受限的~

文章大致思路:

image.png

全文共 7500 字左右,案例均可运行,阅读时间大约需要20分钟左右,如有问题,请留言或发送邮件(nzc_wyh@163.com)。

编写的过程中,即使书写完已阅读过,但难免可能会出现遗漏,如有发现问题请及时联系修正,非常感谢你的阅读,希望我们都能成为技术道路上的朋友。

一、反射是什么?

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

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

稍后就会讲到~

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

反射机制可以用来:

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

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

二、Class 对象详解

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

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

2.1、如何获取到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 实例,为了解释此点,我把类加载过程也简单的做了一个陈述。

2.2、类的加载过程

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

image.png

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

在这里我只着眼于类的加载过程了,想要了解更为详细的,就需要大家去找找资料看看啦~

加载过程

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、这个加载的过程还需要类加载器参与,关于类加载器的类型大家可以去了解了解,还有双亲委派机制等,此处我便不再多言

2.3、为了更便于记忆的图

image.png

(图片说明:为更好的描述JVM中只有一个Class对象,而画下此图,希望通过这张简图,让你记忆更为深刻)

2.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( "测试获取方法参数中的注解信息");
     }
 }

Java反射详解,学以致用,实战案例(AOP修改参数、Mybatis拦截器实现自动填充)2:https://developer.aliyun.com/article/1394601

目录
相关文章
|
3天前
|
存储 Java
java用base64编码案例
Java Base64编码示例:导入`java.util.Base64`,设置字符串`originalString`,使用`Base64.getEncoder().encodeToString()`编码并存储到`encodedString`,打印编码后字符串。解码用`Base64.getDecoder().decode()`。
10 0
|
2天前
|
Java
【专栏】如何在 Java 8 中使用 Streams?结合多种案例剖析学习!
【4月更文挑战第28天】Java 8 的 Streams 提供了一种处理数据集合的新方式,增强了代码的可读性和可维护性。本文介绍了 Streams 的基本概念,如从数据源创建 Stream,以及中间和终端操作。通过过滤、映射、归并、排序、分组等案例,展示了 Streams 的使用,包括并行 Streams 提高效率。学习 Streams 可以提升代码质量和效率,文章鼓励读者在实际开发中探索更多 Streams 功能。
|
3天前
|
设计模式 算法 Java
Java 设计模式:探索策略模式的概念和实战应用
【4月更文挑战第27天】策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在 Java 中,策略模式通过定义一系列的算法,并将每一个算法封装起来,并使它们可以互换,这样算法的变化不会影响到使用算法的客户。
9 1
|
3天前
|
Java
Java 事件驱动编程:概念、优势与实战示例
【4月更文挑战第27天】事件驱动编程是一种编程范式,其中程序的执行流程由外部事件的发生而触发或驱动。
9 0
|
3天前
|
Java 程序员
Java 异步编程:概念、优势与实战示例
【4月更文挑战第27天】在现代软件开发中,异步编程是一种重要的编程范式,特别适用于处理长时间运行的任务,如网络通信、文件操作等。
11 0
|
3天前
|
Java 数据处理 API
Java 函数式编程:概念、优势与实战示例
【4月更文挑战第27天】函数式编程(Functional Programming,简称 FP)是一种编程范式,它将计算视为数学函数的求值并避免使用程序状态以及可变数据。
9 1
|
3天前
|
Java Shell API
Java 模块化编程:概念、优势与实战指南
【4月更文挑战第27天】Java 模块化编程是 Java 9 中引入的一项重大特性,通过 Java Platform Module System (JPMS) 实现。模块化旨在解决 Java 应用的封装性、可维护性和性能问题
9 0
|
3天前
|
Java Apache
java读取excel数据案例
Java代码示例使用Apache POI库读取Excel(example.xlsx)数据。创建FileInputStream和XSSFWorkbook对象,获取Sheet,遍历行和列,根据单元格类型(STRING, NUMERIC, BOOLEAN)打印值。需引入Apache POI库并确保替换文件路径。
6 1
|
5天前
|
监控 Java 调度
Java多线程实战-从零手搓一个简易线程池(四)线程池生命周期状态流转实现
Java多线程实战-从零手搓一个简易线程池(四)线程池生命周期状态流转实现
|
5天前
|
设计模式 Java
Java多线程实战-从零手搓一个简易线程池(三)线程工厂,核心线程与非核心线程逻辑实现
Java多线程实战-从零手搓一个简易线程池(三)线程工厂,核心线程与非核心线程逻辑实现