Java反射

Java反射

  • 引言
    • 反射的概念和作用
    • 为什么学习和使用Java反射
  • 反射基础知识
    • 类的加载和运行机制回顾
    • Class类和java.lang.reflect包的作用和重要性
    • 反射的优缺点
  • 获取和使用Class对象
    • 获取Class对象的四种方式
    • 不同方法的使用场景
  • 实例化对象
  • 获取和操作字段
    • 获取字段信息
    • 获取和设置字段值
    • 获取字段的修饰符和类型信息
    • 操作私有字段
  • 调用方法
    • 获取方法信息
    • 调用方法
    • 调用私有方法
  • 反射的应用
    • 动态代理
      • 动态代理的概念和应用场景
      • 创建动态代理对象
      • 代理方法的拦截和增强
      • 示例演示和示例代码

引言

反射的概念和作用

Java反射是指在运行时动态地获取类的信息并操作类的成员(字段、方法、构造函数等)。它允许程序在运行时检查和操作类、对象、方法和属性,而无需事先在代码中明确地指定它们的类型。

Java反射提供了一组类(如Class、Method、Field等),用于获取并操作类的成员。以下是Java反射的主要概念和作用:

  1. Class类:在Java反射中,Class类是所有类的元数据。它包含了类的属性、方法和构造函数的信息。通过Class类,可以获取类的名称、修饰符、父类、接口等信息。

  2. 获取Class对象:可以使用多种方式获取Class对象,包括使用类的.class语法、调用对象的getClass()方法、使用Class.forName()方法等。

  3. 实例化对象:使用反射可以实例化对象,即通过Class对象创建类的实例。可以调用Class对象的newInstance()方法或Constructor类的newInstance()方法来实现。

  4. 获取和操作字段:通过反射可以获取类的字段信息,包括字段的名称、类型、修饰符等。还可以设置和获取字段的值,即使字段是私有的也可以通过反射访问和修改它们。

  5. 调用方法:通过反射可以调用类的方法。可以获取方法的信息,包括方法的名称、参数类型、返回类型、修饰符等,并通过Method类的invoke()方法来动态调用方法。

  6. 构造函数操作:反射允许通过Class对象获取类的构造函数信息,并实例化对象。可以获取构造函数的参数类型、修饰符等,并通过Constructor类的newInstance()方法来创建对象。

  7. 动态代理:Java反射还可以实现动态代理,即在运行时生成代理对象来拦截对目标对象的访问,实现一些特定的处理逻辑。

Java反射的主要作用是在运行时动态地操作类和对象,使程序能够更灵活地处理类的成员。它广泛应用于很多框架和库中,例如ORM(对象关系映射)框架、依赖注入容器、单元测试等。反射也用于开发工具,如Java反编译器、代码生成器等。然而,由于反射涉及到动态生成和调用代码,使用不当可能会导致性能下降和安全隐患,因此在使用反射时需要谨慎权衡。

为什么学习和使用Java反射

  1. 动态性和灵活性:Java反射提供了在运行时动态地获取和操作类的信息的能力。通过反射,可以在程序运行时探查和操作类的字段、方法和构造函数等元素,而不需要在编译时确定具体的类型。这使得程序可以根据运行时条件进行灵活的逻辑和行为。

  2. 框架和库的扩展性:许多Java框架和库使用反射来实现插件化、扩展性和自定义功能。通过反射,可以动态地加载和使用扩展模块或插件,从而使应用程序具备更大的灵活性和可扩展性。

  3. 库和框架的开发:当你需要开发通用的库、框架或工具时,反射是一个强大的工具。通过反射,你可以设计更通用、灵活的代码,允许用户在运行时动态地配置和定制库的行为。

  4. 测试和调试:反射在测试和调试过程中也发挥重要作用。通过反射,可以获取和修改私有字段的值,调用私有方法,以及检查和设置对象的状态,这对于测试和调试复杂的代码逻辑非常有用。

  5. 框架和类库的学习与分析:反射也是学习和分析第三方框架、类库或未知代码的有力工具。通过反射,你可以深入了解框架或类库的内部结构,探索其提供的功能和接口,并进行扩展和定制。

尽管反射功能强大,但需要注意的是,过度使用反射可能导致性能下降、代码可读性降低和安全风险增加。因此,在使用反射时应权衡利弊,并遵循最佳实践和安全原则。

反射基础知识

类的加载和运行机制回顾

类的加载和运行机制是Java虚拟机在执行Java程序时的关键步骤。回顾类的加载和运行机制,可以涵盖以下内容:

  1. 类加载器(Class Loader):

    • 类加载器负责将类的字节码加载到内存中,并生成对应的Class对象。
    • Java中的类加载器分为启动类加载器、扩展类加载器、应用程序类加载器和自定义类加载器等。
  2. 类加载的过程:

    • 加载(Loading):查找并加载类的字节码文件。
    • 验证(Verification):验证字节码文件的正确性,确保它符合Java虚拟机的规范。
    • 准备(Preparation):为类的静态变量分配内存空间,并设置默认初始值。
    • 解析(Resolution):将类的符号引用转换为直接引用,解析符号引用的过程。
    • 初始化(Initialization):执行类的初始化操作,包括执行静态变量赋值和静态代码块的内容。
  3. 双亲委派模型(Delegation Model):

    • 类加载器采用双亲委派模型进行类加载。
    • 当一个类加载器需要加载某个类时,它首先将加载请求委托给父类加载器,直到达到顶层的启动类加载器。
    • 只有当父类加载器无法加载时,才由子类加载器尝试加载。
  4. 缓存机制:

    • 类加载器会缓存已加载的类,避免重复加载,提高加载性能。
  5. 类的唯一性:

    • 由不同的类加载器加载的同一个类会被视为不同的类,即使它们的字节码内容相同。

了解类的加载和运行机制对于理解Java程序的执行过程至关重要。它不仅是Java虚拟机的核心功能,还对Java的安全性、类的隔离性和动态性提供了基础支持。

Class类和java.lang.reflect包的作用和重要性

Class类和java.lang.reflect包是Java中用于支持反射机制的关键类和包。它们的作用和重要性如下:

  1. Class类的作用和重要性:

    • Class类是Java反射机制的核心类,用于表示在Java运行时环境中的类和接口。
    • 通过Class类,可以获取和操作类的信息,如字段、方法、构造函数、注解等。
    • Class类提供了一组方法来实例化对象、调用方法、获取和设置字段值等反射操作。
    • Class类也用于动态加载类,实现类的动态扩展和插件化功能。
    • Class类的实例由Java虚拟机在类加载过程中自动创建,每个类在内存中只有一个对应的Class实例。
    • Class类的重要方法包括newInstance()getMethod()getField()等。
  2. java.lang.reflect包的作用和重要性:

    • java.lang.reflect包包含了Java反射机制的核心类和接口,提供了进行反射操作的工具和方法。
    • 该包中的类和接口主要用于获取和操作类的信息,如字段、方法、构造函数、注解等。
    • 通过java.lang.reflect包,可以动态地创建对象、调用方法、获取和设置字段值等。
    • 该包中的类和接口包括Field、Method、Constructor、Parameter、Annotation等。
    • 这些类和接口提供了丰富的方法和属性,使得开发者能够在运行时灵活地操作类的结构和行为。

Class类和java.lang.reflect包为Java反射机制的实现提供了必要的支持,它们使得开发者可以在运行时动态地获取和操作类的信息,实现灵活性和扩展性。通过使用这些类和包,开发者能够在设计通用的库、框架和工具时实现更高的灵活性和可扩展性。同时,它们也为测试、调试和分析第三方代码提供了强大的工具。

反射的优缺点

反射机制在Java中具有许多优点,但也存在一些缺点。下面是反射机制的优缺点:

优点:

  1. 动态性和灵活性:反射机制允许在运行时动态地获取和操作类的信息,可以实现动态创建对象、调用方法和访问字段等操作,从而使程序具备更大的灵活性和可扩展性。

  2. 通用性和可重用性:通过反射,可以编写通用的代码,可以处理不同类型的对象和类,而无需提前知道具体的类型。这样可以提高代码的可重用性和通用性。

  3. 框架和库的扩展性:许多框架和库使用反射来实现插件化、扩展性和自定义功能。通过反射,可以在运行时动态地加载和使用扩展模块或插件,从而使应用程序具备更大的灵活性和可扩展性。

  4. 深入了解和分析类库:反射提供了一种方式来深入了解和分析第三方类库的内部结构,包括类的字段、方法和构造函数等。这有助于学习和使用类库,并能够在需要时进行扩展和定制。

缺点:

  1. 性能影响:相对于直接调用,使用反射机制会导致性能上的损失。反射调用比直接调用方法更慢,因为它涉及到动态的方法查找和调用过程,而且还需要进行访问权限的检查。

  2. 安全性问题:反射机制可以绕过访问控制,可以访问和修改私有的字段和方法,这可能会破坏封装性和引入潜在的安全风险。使用反射时需要谨慎处理访问权限的问题,以避免潜在的安全漏洞。

  3. 可读性降低:由于反射可以动态地访问和操作类的成员,代码的可读性可能会降低。使用反射时,某些类型的错误可能只在运行时才会暴露出来,而不会在编译时被捕获,增加了调试和维护的复杂性。

综上所述,反射机制在提供灵活性、通用性和可扩展性方面具有重要优势。然而,开发者应权衡反射的优缺点,并在使用反射时注意性能影响、安全性问题和可读性降低的风险。反射应谨慎使用,遵循最佳实践,以确保代码的可靠性和安全性。

获取和使用Class对象

获取Class对象的四种方式

通过类名.class属性,这种方法在编译时加载类,不会初始化类。

public class A {
    static {
        System.out.println("A类初始化");
    }
}
public class test {
    @Test
    // 类名.class 方式获取class
    public void test01() throws ClassNotFoundException {
        System.out.println("类.class");
        Class<A> aClass = A.class;
    }
}

Java反射_第1张图片

通过对象的getClass()方法,这种方法需要先创建对象,然后获取对象的Class实例。

public class test {
    @Test
    // 对象getClass方法获取class
    public void test02() throws ClassNotFoundException {
        System.out.println("对象getClass");
        Class<?> aClass1 = new A().getClass();
    }
}

Java反射_第2张图片

通过Class的静态方法forName(全类名),这种方法在运行时加载类,会初始化类。

public class test {
    @Test
    // 通过Class的静态方法forName(全类名)获取class
    public void test03() throws ClassNotFoundException {
        System.out.println("Class.forName");
        Class<?> aClass1 = Class.forName("com.hzy.reflect.A");
    }
}

Java反射_第3张图片

通过类加载器的loadClass(全类名)方法,这种方法在运行时加载类,不会初始化类。

public class test {
    @Test
    // 通过类加载器的loadClass(全类名)方法
    public void test04() throws ClassNotFoundException {
        System.out.println("类加载器");
        ClassLoader classLoader = A.class.getClassLoader();
        Class<?> aClass = classLoader.loadClass("com.hzy.reflect.A");
    }
}

Java反射_第4张图片

不同方法的使用场景

  • 类名.class属性适用于已知类名的情况,可以在编译时检查类是否存在,不会触发类的初始化。
  • 对象的getClass()方法适用于已有对象实例的情况,可以动态获取对象的实际类型,不会触发类的初始化。
  • Class的静态方法forName(全类名)适用于类名是动态传入的情况,可以在运行时加载类,会触发类的初始化。
  • 类加载器的loadClass(全类名)方法适用于需要自定义类加载器的情况,可以在运行时加载类,不会触发类的初始化。

实例化对象

public class A {
    public String name;
    private int age;

    static {
        System.out.println("A类初始化");
    }

    public A() {

    }

    public A(String name) {
        this.name = name;
    }
    private A(int age) {
        this.age= age;
    }
}

获取A的class对象

Class<A> aClass = A.class;

直接调用class对象的newInstance(),调用了空构造函数实例化对象

A a = aClass.newInstance();

调用class对象的getConstructor(),返回空构造器对象

Constructor<A> constructor = aClass.getConstructor();
A a = constructor.newInstance();

调用class对象的getConstructor(),返回带参构造器对象

Constructor<A> constructor = aClass.getConstructor(String.class);
A a = constructor1.newInstance("zs");

调用class对象的getDeclaredConstructor(),返回带参构造器对象

Constructor<A> declaredConstructor = aClass.getDeclaredConstructor(int.class);
// 反射访问一个类的私有成员,可以使用setAccessible(true)方法来覆盖Java语言的访问控制检查
declaredConstructor.setAccessible(true);
A a = declaredConstructor.newInstance(10);

getConstructor()getDeclaredConstructor()都是Java反射API中的方法,它们都可以用来获取类的构造器。它们之间的区别在于getConstructor()方法只返回类的public构造器,而getDeclaredConstructor()方法则返回类的所有构造器,包括public、protected、default (package) access和private构造器。

因此,如果只需要获取类的public构造器,可以使用getConstructor()方法。如果您需要获取类的所有构造器,不管它们的访问修饰符是什么,可以使用getDeclaredConstructor()方法。

getConstructors():可以获取全部public修饰的构造函数

getDeclaredConstructors():可以获取全部的构造函数

获取和操作字段

public class A {
    public String name;
    private int age;
    protected int protectedTest;
    public int publicTest;
}

获取字段信息

可以使用Class类的getField()getDeclaredField()方法来获取类的字段。这两个方法都接受一个字符串参数,表示要获取的字段的名称。

    @Test
    // 获取指定字段
    public void test02() throws Exception {
        Field name = aClass.getField("name");
        Field age = aClass.getDeclaredField("age");
        System.out.println(name);
        System.out.println(age);
    }

Java反射_第5张图片

也可以使用Class类的getFields()getDeclaredFields()方法来获取类的字段。这两个方法都获取一个字段集合。

    // 获取public 字段
    public void test01() {
        Class<A> aClass = A.class;
        Field[] fields = aClass.getFields();
        System.out.println("public 字段");
        for(var field : fields){
            System.out.println(field);
        }
    }
	// 全部字段
    public void test02() {
        Class<A> aClass = A.class;
        Field[] declaredFields = aClass.getDeclaredFields();
        System.out.println("所有字段");
        for(var field : declaredFields){
            System.out.println(field);
        }
    }

Java反射_第6张图片

获取和设置字段值

可以使用Field类的get()set()方法来分别获取和设置字段值。

get()方法接受一个对象作为参数,表示要获取哪个对象的字段值。如果该字段是静态的,则可以传入null作为参数。set()方法接受两个参数,第一个参数表示要设置哪个对象的字段值,第二个参数表示要设置的值。如果该字段是静态的,则第一个参数可以传入null

    // 获取字段值和操作字段值
    public void test03() throws Exception {
        Field name = aClass.getField("name");
        Field staticTest = aClass.getDeclaredField("staticTest");
        A a = new A();
        name.set(a,"张三");
        staticTest.set(null,12);

        System.out.println(name.get(a));
        System.out.println(staticTest.get(null));
    }

Java反射_第7张图片

获取字段的修饰符和类型信息

可以使用Field类的getModifiers()getType()方法来分别获取字段的修饰符和类型。

getModifiers()方法返回一个整数,表示字段的修饰符。可以使用java.lang.reflect.Modifier类中的方法来解析这个整数,以获取字段的修饰符信息。例如,可以使用Modifier.isPublic()方法来判断字段是否是public的。

getType()方法返回一个Class对象,表示字段的类型。

    public void test01() throws Exception{
        Field name = aClass.getField("name");
        int modifiers = name.getModifiers();
        Class<?> type = name.getType();
        System.out.println(modifiers);
        System.out.println(Modifier.isPublic(modifiers));
        System.out.println(type);
    }

Java反射_第8张图片

查看Modifier源码可以知道Modifier类中定义了一些常量,表示不同的修饰符。例如,Modifier.PUBLIC表示public修饰符,Modifier.PRIVATE表示private修饰符。这些常量的值是不同的2的幂
Java反射_第9张图片

操作私有字段

操作私有字段需要覆盖访问控制权限

    public void test04() throws Exception {
        Field age = aClass.getDeclaredField("age");
        A  a = new A();
        age.setAccessible(true);
        System.out.println(age.get(a));
    }

调用方法

public class A {
    public String printA(){
       return "a";
    }
    public void printA(String a){
        System.out.println("a:"+a);
    }
    private void printB(String b){
        System.out.println("b:"+b);
    }
   public static void printB(){
        System.out.println("bb");
    }
}

获取方法信息

可以使用反射来获取类的方法信息。可以使用Class类的getMethod()getDeclaredMethod()方法来获取类的方法,然后使用Method类的getName()getReturnType()getParameterTypes()getModifiers()方法来分别获取方法的名称、返回类型、参数类型和修饰符。

getMethod()方法接受一个字符串参数和一个可变长度的Class对象数组作为参数。字符串参数表示要获取的方法的名称,Class对象数组表示要获取的方法的参数类型。getMethod()方法只能获取类的public方法,包括其父类和接口中的public方法。如果要获取类的所有方法,不管它们的访问修饰符是什么,可以使用getDeclaredMethod()方法。

    public void test01() throws Exception{
        Method printA1 = aClass.getMethod("printA", String.class);
        System.out.println(printA1.getName());
        System.out.println(printA1.getReturnType());
        for (var ParameterType :printA1.getParameterTypes()){
            System.out.println(ParameterType);
        }
        System.out.println(printA1.getModifiers());
   }

Java反射_第10张图片

调用方法

使用Method类的invoke()方法来调用方法。

invoke()方法接受一个对象和一个可变长度的对象数组作为参数。第一个参数表示要调用哪个对象的方法,第二个参数表示要传递给方法的参数。如果该方法是静态的,则第一个参数可以传入null

    public void test02() throws Exception{
        Method printA1 = aClass.getMethod("printA", String.class);
        Method printA2 = aClass.getMethod("printA");
        A a = new A();
        printA1.invoke(a, "hahaha");
        System.out.println(printA2.invoke(a));
    }
    // 调用静态方法
    public void test03() throws Exception{
        Method printB = aClass.getMethod("printB");
        printB.invoke(null);
    }

Java反射_第11张图片
Java反射_第12张图片

调用私有方法

调用私有方法需要覆盖访问控制权限

    public void test04() throws Exception{
        Method printB = aClass.getDeclaredMethod("printB",String.class);
        A a = new A();
        printB.setAccessible(true);
        printB.invoke(a,"bbdd");
    }

不覆盖会报错

java.lang.IllegalAccessException: class com.hzy.reflect.MethodTest cannot access a member of class com.hzy.reflect.A with modifiers "private"

反射的应用

动态代理

动态代理的概念和应用场景

动态代理是一种设计模式,它允许在运行时动态地创建一个对象的代理,而无需手动编写代理类的代码。动态代理通常用于实现面向切面编程(AOP),可以在不修改原有代码的情况下,为对象添加额外的功能。

在Java中,可以使用java.lang.reflect.Proxy类来创建动态代理。Proxy类提供了一个静态方法newProxyInstance(),用于创建动态代理对象。该方法接受三个参数:类加载器、接口数组和调用处理器。类加载器用于定义代理类,接口数组表示代理类需要实现的接口,调用处理器用于处理对代理对象的方法调用。

动态代理的应用场景很多,例如:

  • 日志记录:可以在方法调用前后进行日志记录,以便于调试和追踪。
  • 性能监控:可以统计方法的执行时间,进行性能监控和优化。
  • 事务管理:可以在方法执行前后进行事务的开启、提交或回滚。
  • 权限控制:可以根据用户的权限动态判断是否执行方法。
  • 缓存操作:可以在方法执行前查询缓存,避免重复计算。

创建动态代理对象

创建动态代理对象主要有两种方式:基于接口的动态代理和基于类的动态代理。

  • 基于接口的动态代理:通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。
  • 基于类的动态代理:通过字节码生成库,如 CGLIB、Byte Buddy 等。

基于java.lang.reflect.Proxy 类来创建动态代理对象创建原理是:假设创建的代理对象名为 $Proxy0,那么:

  1. 根据传入的 interfaces 动态生成一个类,实现 interfaces 中的接口。
  2. 通过传入的 classloader 将刚生成的类加载到 JVM 中。即将 $Proxy0 类 load。
  3. 调用 $Proxy0 的构造函数创建 $Proxy0 的对象,并且用 interfaces 参数遍历其所有接口的方法,这些实现方法的实现本质上是通过反射调用被代理对象的方法。
  4. 将 $Proxy0 的实例返回给客户端。
  5. 当调用代理类的相应方法时,相当于调用 InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[]) 方法。

代理方法的拦截和增强

在创建动态代理对象时,需要实现 InvocationHandler 接口,并重写 invoke 方法。在 invoke 方法中,可以对代理的方法进行拦截和增强。例如,可以在方法调用前后进行日志输出、权限检查、事务处理等操作。

示例演示和示例代码

public class test {
    public static void main(String[] args) {
        bookService bookService = new bookServiceImpl();
        bookService proxy = ProxyUtil.getProxy(bookService);
        proxy.save();
        proxy.del("三国演义");
    }
}

class ProxyUtil<T>{
    public static <T> T getProxy(T obj){
        return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        StringBuffer sb = new StringBuffer();
                        if (args!=null){
                            for(var a: args){
                                sb.append(a).append("\t");
                            }
                        }
                        System.out.printf("Before method: 方法名:%s, 方法入参:%s\n", method.getName(), sb);
                        Object result = method.invoke(obj, args);
                        System.out.printf("After method: 方法出参:%s\n", result);
                        return result;
                    }
                });
    }
}
interface bookService{
    void save();
    boolean del(String name);
}
class bookServiceImpl implements bookService{

    @Override
    public void save() {
        System.out.println("书籍保存成功");
    }

    @Override
    public boolean del(String name) {
        System.out.println(name+"成功删除");
        return true;
    }
}

Java反射_第13张图片

你可能感兴趣的:(Java基础,java,学习,笔记)