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 来动态代理目标类。

相关文章
|
2天前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
16天前
|
NoSQL Java 关系型数据库
Liunx部署java项目Tomcat、Redis、Mysql教程
本文详细介绍了如何在 Linux 服务器上安装和配置 Tomcat、MySQL 和 Redis,并部署 Java 项目。通过这些步骤,您可以搭建一个高效稳定的 Java 应用运行环境。希望本文能为您在实际操作中提供有价值的参考。
89 26
|
20小时前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。
|
22天前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
22天前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
29天前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
32 2
|
22天前
|
Java 数据库连接 编译器
Kotlin教程笔记(29) -Kotlin 兼容 Java 遇到的最大的“坑”
Kotlin教程笔记(29) -Kotlin 兼容 Java 遇到的最大的“坑”
39 0
|
1天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
3天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。