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

举报
厨师之乡 发表于 2024/04/22 07:55:23 2024/04/22
【摘要】 反射 什么是反射?反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。在 java 中,只要给定类的名字,那么就可以通过反射机制来获得类的所有属性和方法。 反射有什么作用:在运行时判断任意一个对象所属的类。在运行时判断任意一个类所具有的成员变量和方法。在运行时任意调用一个对象的方法。在运...

反射

什么是反射?

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。