Java基础教程(13)-Java中的反射和动态代理

简介: 【4月更文挑战第13天】Java反射机制允许程序在运行时获取类的信息并调用其方法。Class类是基础,提供获取类属性和方法的能力。通过Class对象,可以操作实例字段和方法,如getField、getDeclaredField等。动态代理是Java提供的创建接口实例的机制,其中JDK动态代理需目标类实现接口,而Cglib则可代理未实现接口的类。动态代理涉及Proxy和InvocationHandler接口。

反射

什么是反射?

反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。
反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。

在 java 中,只要给定类的名字,那么就可以通过反射机制来获得类的所有属性和方法。

反射有什么作用:

  • 在运行时判断任意一个对象所属的类。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时任意调用一个对象的方法。
  • 在运行时构造任意一个类的对象

Class 类

Java 的 Class 类是 java 反射机制的基础,通过 Class 类我们可以获得关于一个类的相关信息。

Java.lang.Class 是一个比较特殊的类,它用于封装被装入到 JVM 中的类(包括类和接口)的信息。当一个类或接口被装入的 JVM 时便会产生一个与之关联的 java.lang. Class 对象,可以通过这个Class 对象对被装入类的详细信息进行访问。

虚拟机为每种类型管理一个独一无二的 Class 对象。也就是说,每个类(型)都有一个 Class 对象。运行程序时,Java 虚拟机(JVM)首先检查是否所要加载的类对应的Class 对象是否已经加载。如果没有加载,JVM 就会根据类名查找.class 文件,并将其Class 对象载入。

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载

以 String 类为例,当JVM加载 String 类时,它首先读取String.class 文件到内存,然后,为 String 类创建一个 Class 实例并关联起来; 这个 Class 实例是JVM内部创建的, Class 类的构造方法
是 private ,只有JVM能创建 Class 实例,我们自己的Java程序是无法创建 Class 实例的;

这种通过 Class 实例获取 class 信息的方法称为反射

获取一个 class 的 Class 实例有三个方法:

  • 方法一:直接通过一个 class 的静态变量 class 获取
  • 通过实例变量提供的 getClass() 方法获取;
  • 如果知道一个 class 的完整类名,可以通过静态方法 Class.forName() 获取;

操作实例的字段和方法

Class 类提供了以下几个方法来获取字段:

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

Field 对象包含了一个字段的所有信息:

  • getName() :返回字段名称
  • getType() :返回字段类型,也是一个 Class 实例 ;
  • getModifiers() :返回字段的修饰符,它是一个 int ,不同的bit表示不同的含义。
package com.demo;

import java.lang.reflect.Field;

public class ReflectionDemo {
   

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException {
   
        Box box = new Box();
        Class<Box> boxClass = Box.class;
        Class box_class = box.getClass();
        box_class = Class.forName("com.demo.Box");

        Field[] fields = box_class.getDeclaredFields();
        for (Field f : fields) {
   
            System.out.println(f.getName());
            System.out.println(f.getType());
            System.out.println(f.getModifiers());
            f.setAccessible(true); // 调用 Field.setAccessible(true) 的意思是,别管这个字段是不是 public ,一律允许访问
            System.out.println(f.get(box)); //,用 Field.get(Object) 获取指定实例的指定字段的值
            f.set(box,111); // 设置字段值

        }
    }
}

Class 类提供了以下几个方法来获取 Method :

  • Method getMethod(name, Class…) :获取某个 public 的 Method (包括父类)
  • Method getDeclaredMethod(name, Class…) :获取当前类的某个 Method (不包括父类)
  • Method[] getMethods() :获取所有 public 的 Method (包括父类)
  • Method[] getDeclaredMethods() :获取当前类的所有 Method (不包括父类)

一个 Method 对象包含一个方法的所有信息:

  • getName() :返回方法名称,例如: "getScore" ;
  • getReturnType() :返回方法返回值类型,也是一个Class实例,例如: String.class ;
  • getParameterTypes() :返回方法的参数类型,是一个Class数组,例如: {String.class,int.class} ;
  • getModifiers() :返回方法的修饰符,它是一个 int ,不同的bit表示不同的含义。
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
   
        Box box = new Box();
        Class<Box> boxClass = Box.class;
        Class box_class = box.getClass();
        box_class = Class.forName("com.demo.Box");

        Method[] methods = boxClass.getDeclaredMethods();
        for (Method method: methods) {
   
            System.out.println(method.getName());
            System.out.println(method.getReturnType());
            System.out.println(method.getParameterTypes());
        }
        Method m = boxClass.getMethod("getWidth",Integer.class);
        m.setAccessible(true); // 调用非public方法,我们通过 Method.setAccessible(true) 允许其调用
        int re = (int)m.invoke(box_class,3); //调用非静态方法;
        Method m_static = boxClass.getMethod("getWidth",Integer.class,Integer.class);
        // 调用静态方法时,由于无需指定实例对象,所以 invoke 方法传入的第一个参数永远为 null 。
        int result = (int) m.invoke(null,1,2);


    }

其他方法

如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法;

调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。

通过Class实例获取Constructor的方法如下:

  • getConstructor(Class…) :获取某个 public 的 Constructor
  • getDeclaredConstructor(Class…) :获取某个 Constructor
  • getConstructors() :获取所有 public 的 Constructor
  • getDeclaredConstructors() :获取所有 Constructor

Constructor 总是当前类定义的构造方法,和父类无关

通过 Class 对象可以获取继承关系:

  • Class getSuperclass() :获取父类类型;
  • Class[] getInterfaces() :获取当前类实现的所有接口。
  • 通过 Class 对象的 isAssignableFrom() 方法可以判断一个向上转型是否可以实现

动态代理

什么是动态代理

JDK提供的动态创建接口对象的方式,就叫动态代理。
Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创
建某个 interface 的实例。

在运行期动态创建一个 interface 实例的方法如下:

  • 定义一个 InvocationHandler 实例,它负责实现接口的方法调用;
  • 通过 Proxy.newProxyInstance() 创建 interface 实例,它需要3个参数:

    使用的 ClassLoader ,通常就是接口类的 ClassLoader ;
    需要实现的接口数组,至少需要传入一个接口进去;
    用来处理接口方法调用的 InvocationHandler 实例。

  • 将返回的 Object 强制转型为接口。

反射是动态代理的一种实现方式

Java 中,实现动态代理有两种方式:

  • 1、JDK 动态代理:java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口提供了生成动态代理类的能力。
  • 2、Cglib 动态代理:Cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

区别:

JDK 的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用 CGLIB实现。

Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 Java 类与实现 Java
接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP 和 dynaop,为他们提供方
法的 interception(拦截)

Java 实现动态代理主要涉及哪几个类

  • java.lang.reflect.Proxy: 这是生成代理类的主类,通过 Proxy 类生成的代理类都继承了 Proxy 类,即 DynamicProxyClass extends Proxy。
  • java.lang.reflect.InvocationHandler: 这里称他为"调用处理器",他是一个接口,我们动态生成的代理类需要完成的具体内容需要自己定义一个类,而这个类必须实现InvocationHandler 接口

Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理。JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。

相关文章
|
1天前
|
搜索推荐 Java 测试技术
《手把手教你》系列技巧篇(五十)-java+ selenium自动化测试-字符串操作-上篇(详解教程)
【5月更文挑战第14天】本文介绍了自动化测试中如何从字符串中提取特定信息,主要讲解了两种方法:正则表达式和字符串切片操作。文章提供了一个测试场景,即在搜索引擎中搜索“北京宏哥”并比较百度和必应的搜索结果数量。通过字符串切片函数`split()`,可以从搜索结果的描述中提取出数字。代码示例展示了如何使用Java实现这个功能,包括在百度和必应的搜索页面获取结果数量,并进行比较。文章最后还简单提到了其他字符串截取的方法,如`substring()`和`StringUtils`类中的方法。
12 2
|
2天前
|
JavaScript 前端开发 Java
《手把手教你》系列技巧篇(四十九)-java+ selenium自动化测试-隐藏元素定位与操作(详解教程)
【5月更文挑战第13天】本文主要讨论了在Selenium自动化测试中如何处理前端隐藏元素的问题。隐藏元素通常是通过`type="hidden"`或`style="display: none;"`属性实现的,它们在页面上不可见,但仍然存在于HTML代码中。Selenium可以定位到这些隐藏元素,但无法直接进行点击、输入等操作,会报错“ElementNotInteractableException”。
24 3
|
3天前
|
JavaScript 前端开发 测试技术
《手把手教你》系列技巧篇(四十八)-java+ selenium自动化测试-判断元素是否可操作(详解教程)
【5月更文挑战第12天】本文介绍了WebDriver中用于判断元素状态的三个方法:`isEnabled()`、`isSelected()`和`isDisplayed()`。`isSelected()`检查元素是否被选中,通常用于勾选框。`isDisplayed()`则用来判断元素是否在页面上可见。`isEnabled()`方法确定元素是否可操作,例如是否能点击或输入内容。
14 1
|
4天前
|
存储 JavaScript Java
《手把手教你》系列技巧篇(四十七)-java+ selenium自动化测试-判断元素是否显示(详解教程)
【5月更文挑战第11天】WebDriver 的 `isDisplayed()` 方法用于检查页面元素是否可见,如果元素存在于DOM中且可视,返回`true`,否则返回`false`。在自动化测试中,这个方法常用于验证元素是否真正显示在页面上。示例代码展示了如何使用 `isDisplayed()` 判断百度登录页面的特定错误提示文字是否出现。
16 1
|
5天前
|
Java
Java中int[]与Integer[]相互转化的方法,java基础知识面试重点总结
Java中int[]与Integer[]相互转化的方法,java基础知识面试重点总结
|
5天前
|
安全 Java API
JAVA-不安全的反射--RCE
JAVA不安全的反射造成的RCE小案例
|
5天前
|
JavaScript Java 测试技术
《手把手教你》系列技巧篇(四十六)-java+ selenium自动化测试-web页面定位toast-下篇(详解教程)
【5月更文挑战第10天】本文介绍了使用Java和Selenium进行Web自动化测试的实践,以安居客网站为例。最后,提到了在浏览器开发者工具中调试和观察页面元素的方法。
18 2
SpringJDK动态代理实现,2024Java面试真题精选干货整理
SpringJDK动态代理实现,2024Java面试真题精选干货整理
|
1天前
|
Java 容器
Java并发编程:深入理解线程池
【5月更文挑战第21天】 在多核处理器的普及下,并发编程成为了提高程序性能的重要手段。Java提供了丰富的并发工具,其中线程池是管理线程资源、提高系统响应速度和吞吐量的关键技术。本文将深入探讨线程池的核心原理、关键参数及其调优策略,并通过实例展示如何高效地使用线程池以优化Java应用的性能。
|
1天前
|
监控 算法 Java
Java并发编程:深入理解线程池
【5月更文挑战第21天】 在现代软件开发中,尤其是Java应用中,并发编程是一个不可忽视的重要领域。合理利用多线程可以显著提高程序的性能和响应速度。本文将深入探讨Java中的线程池机制,包括其工作原理、优势以及如何正确使用线程池来优化应用程序性能。通过分析线程池的核心参数配置,我们将了解如何根据不同的应用场景调整线程池策略,以期达到最佳的并发处理效果。