再次认识java反射

一、概述

在认识java反射之前我们先来认识一下什么是动态语言与静态语言。

动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以 被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。

主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

静态语言

与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。 Java的动态性让编程的时候更加灵活!

像C、C++这类型语言一旦编译完成之后,运行时加载到内存后,程序内部结构无法被改变,因此被称为静态语言。但是java由于有反射的功能,可以通过反射让程序在运行过程中,动态的加载类到内存,从而改变了程序结构,因此java在一定程度上具备了动态语言的特性。

反射

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于ReflectionAPI取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

反射提供的功能

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时判断任意一个类所具有的成员变量和方法
  4. 在运行时获取泛型信息
  5. 在运行时调用任意一个对象的成员变量和方法
  6. 在运行时处理注解
  7. 生成动态代理

反射工作原理

步骤一:源文件转class文件

在Java编程中,一旦我们完成了一个Java项目的编写,每个Java源文件都会经过编译器的处理,生成相应的.class文件。这是反射机制得以发挥作用的第一步。

步骤二:加载到jvm

当我们的Java程序开始运行时,ClassLoader会介入并负责将编译好的.class文件加载到JVM中。这一步是反射机制得以顺利工作的关键。

当一个类被成功加载到JVM后,系统会在内存中自动为其创建一个Class对象。接下来,我们可以通过这个Class对象来获取该类的各种信息,包括其属性、方法和构造函数。这一过程可以通过以下方式实现:
在日常编程中,我们通常使用new关键字来创建对象。实际上,这个过程是通过Class对象来完成的。需要注意的是,这个Class文件是在编译阶段就已经生成的,也就是说,程序在运行时,JVM是按照预先定义好的规则来执行和创建对象的。
在使用new关键字创建对象时,我们必须明确指定类名,这种实现方式被称为硬编码,缺乏灵活性。然而,通过Java的反射机制,我们只需传入类名参数,就能动态生成对象。这种灵活性极大地降低了程序各部分之间的耦合性,使得程序更加易于扩展和维护。因此,许多Java开发框架都充分利用了反射机制的优势。

使用反射

Class类是Java反射机制的核心,它代表了Java程序中的类和接口。每个类在JVM中都有一个唯一的Class对象,通过Class对象可以获取类的元数据(如构造函数、字段、方法等),并动态地操作这些元数据。

Class类的获取方式

1、通过对象的getClass()方法

package org.example;

public class Main {
    public static void main(String[] args) {
        String str = "Hello, Java";
        Class<?> clazz = str.getClass();
        System.out.println(clazz.getName()); // 输出:java.lang.String
    }
}

 2、通过类名.class

package org.example;

public class Main {
    public static void main(String[] args) {
        Class<?> clazz = String.class;
        System.out.println(clazz.getName()); // 输出:java.lang.String
    }
}

3、通过Class.forName()方法

Class.forName()方法可以通过类的全限定名(包括包名)动态加载类,并返回对应的Class对象。

package org.example;

public class Main {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("java.lang.String");
            System.out.println(clazz.getName()); // 输出:java.lang.String
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Class类的作用

Class类提供了丰富的API,用于获取类的元数据,并动态地创建对象、调用方法和访问字段。

1、获取类的信息


package org.example;
import java.lang.reflect.Modifier;

public class Main {
    public static void main(String[] args) {
        Class<?> clazz = String.class;
        // 获取类名
        System.out.println("Class Name: " + clazz.getName()); // 输出:java.lang.String
        // 获取简单类名
        System.out.println("Simple Name: " + clazz.getSimpleName()); // 输出:String
        // 获取修饰符
        int modifiers = clazz.getModifiers();
        System.out.println("Modifiers: " + Modifier.toString(modifiers)); // 输出:public final
        // 获取父类
        Class<?> superClass = clazz.getSuperclass();
        System.out.println("Superclass: " + superClass.getName()); // 输出:java.lang.Object
        // 获取实现的接口
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> iface : interfaces) {
            System.out.println("Interface: " + iface.getName());
        }
    }
}

运行结果

 2、创建类的实例

通过Class对象,可以使用反射创建类的实例。常用的方法是newInstance(),但需要注意的是,newInstance()方法已经被标记为过时,推荐使用getDeclaredConstructor().newInstance()

package org.example;

public class Main {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("java.util.ArrayList");
            // 创建实例
            Object instance = clazz.getDeclaredConstructor().newInstance();
            System.out.println(instance.getClass().getName()); // 输出:java.util.ArrayList
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3、获取类的构造方法、字段和方法

package org.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) {
        Class<?> clazz = String.class;

        // 获取所有构造方法
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("Constructor: " + constructor);
        }

        // 获取所有字段(包括私有字段)
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("Field: " + field.getName());
        }

        // 获取所有方法(包括私有方法)
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println("Method: " + method.getName());
        }
    }
}

运行结果

"C:\Program Files\Java\jdk1.8.0_241\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2023.2\lib\idea_rt.jar=5619:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2023.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_241\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\rt.jar;D:\project\IDEAjava\testPro\reflectTest\target\classes;C:\Users\liyuanhong\.m2\repository\org\bouncycastle\bcprov-jdk15on\1.70\bcprov-jdk15on-1.70.jar;C:\Users\liyuanhong\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.13.1\jackson-core-2.13.1.jar;C:\Users\liyuanhong\.m2\repository\org\apache\commons\commons-lang3\3.9\commons-lang3-3.9.jar;C:\Users\liyuanhong\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.13.3\jackson-databind-2.13.3.jar;C:\Users\liyuanhong\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.13.3\jackson-annotations-2.13.3.jar;C:\Users\liyuanhong\.m2\repository\com\alibaba\fastjson\1.2.76\fastjson-1.2.76.jar" org.example.Main
Constructor: public java.lang.String(byte[],int,int)
Constructor: public java.lang.String(byte[],java.nio.charset.Charset)
Constructor: public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
Constructor: public java.lang.String(byte[],int,int,java.nio.charset.Charset)
Constructor: public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
Constructor: java.lang.String(char[],boolean)
Constructor: public java.lang.String(java.lang.StringBuilder)
Constructor: public java.lang.String(java.lang.StringBuffer)
Constructor: public java.lang.String(byte[])
Constructor: public java.lang.String(int[],int,int)
Constructor: public java.lang.String()
Constructor: public java.lang.String(char[])
Constructor: public java.lang.String(java.lang.String)
Constructor: public java.lang.String(char[],int,int)
Constructor: public java.lang.String(byte[],int)
Constructor: public java.lang.String(byte[],int,int,int)
Field: value
Field: hash
Field: serialVersionUID
Field: serialPersistentFields
Field: CASE_INSENSITIVE_ORDER
Method: equals
Method: toString
Method: hashCode
Method: compareTo
Method: compareTo
Method: indexOf
Method: indexOf
Method: indexOf
Method: indexOf
Method: indexOf
Method: indexOf
Method: valueOf
Method: valueOf
Method: valueOf
Method: valueOf
Method: valueOf
Method: valueOf
Method: valueOf
Method: valueOf
Method: valueOf
Method: charAt
Method: checkBounds
Method: codePointAt
Method: codePointBefore
Method: codePointCount
Method: compareToIgnoreCase
Method: concat
Method: contains
Method: contentEquals
Method: contentEquals
Method: copyValueOf
Method: copyValueOf
Method: endsWith
Method: equalsIgnoreCase
Method: format
Method: format
Method: getBytes
Method: getBytes
Method: getBytes
Method: getBytes
Method: getChars
Method: getChars
Method: indexOfSupplementary
Method: intern
Method: isEmpty
Method: join
Method: join
Method: lastIndexOf
Method: lastIndexOf
Method: lastIndexOf
Method: lastIndexOf
Method: lastIndexOf
Method: lastIndexOf
Method: lastIndexOfSupplementary
Method: length
Method: matches
Method: nonSyncContentEquals
Method: offsetByCodePoints
Method: regionMatches
Method: regionMatches
Method: replace
Method: replace
Method: replaceAll
Method: replaceFirst
Method: split
Method: split
Method: startsWith
Method: startsWith
Method: subSequence
Method: substring
Method: substring
Method: toCharArray
Method: toLowerCase
Method: toLowerCase
Method: toUpperCase
Method: toUpperCase
Method: trim

 反射与注解

注解(Annotation)是Java中的一种元数据机制,反射可以读取注解信息,并根据注解动态地执行操作。

1、通过反射读取注解信息

首先,定义一个注解

package org.example.anno;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}

然后,使用注解

package org.example.anno;

@MyAnnotation("Class Annotation")
public class AnnotationExample {
    @MyAnnotation("Method Annotation")
    public void myMethod() {
        System.out.println("Method called");
    }
}

通过反射读取注解信息

import java.lang.reflect.Method;

public class AnnotationReader {
    public static void main(String[] args) throws Exception {
        // 获取类的注解
        Class<?> clazz = AnnotationExample.class;
        MyAnnotation classAnnotation = clazz.getAnnotation(MyAnnotation.class);
        System.out.println("Class Annotation: " + classAnnotation.value());

        // 获取方法的注解
        Method method = clazz.getMethod("myMethod");
        MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class);
        System.out.println("Method Annotation: " + methodAnnotation.value());
    }
}

运行结果

 2、自定义注解与反射结合

自定义注解可以与反射结合,实现一些高级功能。例如,假设我们定义了一个注解@Log,用于在方法调用时记录日志:

package org.example.anno;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Log {

在方法上使用该注解:

package org.example.anno;

public class LogExample {
    @Log
    public void myMethod() {
        System.out.println("Method called");
    }
}

通过反射读取注解并记录日志:

package org.example.anno;

import java.lang.reflect.Method;

public class LogProcessor {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = LogExample.class;
        Method method = clazz.getMethod("myMethod");

        // 检查方法是否包含@Log注解
        if (method.isAnnotationPresent(Log.class)) {
            System.out.println("Logging before method call");
            method.invoke(clazz.getDeclaredConstructor().newInstance());
            System.out.println("Logging after method call");
        }
    }
}

运行结果

反射机制的高级应用

 1、动态代理与反射

动态代理是反射机制的经典应用之一,它允许在运行时动态生成代理类,从而在不修改原始类的情况下,为对象添加额外的功能。

动态代理的原理

动态代理的核心是java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。Proxy类用于生成代理对象,而InvocationHandler接口定义了代理对象的行为。

动态代理的工作流程如下:

  1. 定义接口:代理类和目标类需要实现相同的接口。
  2. 实现InvocationHandler接口:在invoke方法中定义代理类的行为。
  3. 使用Proxy.newProxyInstance生成代理对象:通过反射动态生成代理对象
使用反射实现动态代理

首先定义一个接口

package org.example.dyProxy;

public interface Calculator {
    int add(int a, int b);
}

然后实现目标类

package org.example.dyProxy;

public class SimpleCalculator implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

实现InvocationHandler接口

package org.example.dyProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class CalculatorProxy implements InvocationHandler {
    private Object target; // 目标对象

    public CalculatorProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 记录方法调用前的日志
        System.out.println("Calling method: " + method.getName() + " with args: " + args[0] + ", " + args[1]);
        // 调用目标方法
        Object result = method.invoke(target, args);
        // 记录方法调用后的日志
        System.out.println("Method result: " + result);
        return result;
    }
}

生成代理对象

package org.example.dyProxy;

import java.lang.reflect.Proxy;

public class DynamicProxyExample {
    public static void main(String[] args) {
        // 创建目标对象
        Calculator target = new SimpleCalculator();

        // 创建代理对象
        Calculator proxy = (Calculator) Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 类加载器
                target.getClass().getInterfaces(),  // 目标对象的接口
                new CalculatorProxy(target)         // 代理处理器
        );

        // 调用代理对象的方法
        proxy.add(10, 20);
    }
}

 在这个例子中,通过反射生成了一个代理对象,并在调用目标方法前后添加了日志功能。

posted @ 2025-03-10 00:20  远洪  阅读(91)  评论(0)    收藏  举报