Java基础 | 反射

Java基础 | 反射

概述

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

反射能够提供的功能

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

Class 类

概念

程序经过编译命令,会生成一个或多个字节码文件(.class),接着对某个字节码文件进行解释运行,当于将某个字节码文件加载到内存中,此过程就称为类的加载。已经加载到内存中的类,我们就称为运行时类,此运行时类,就作为 Class 类的一个实例,(类存放在方法区,其对应的 Class 实例存放在堆)。也就是说,Class 类的实例就对应着一个运行时类。加载到内存中的运行时类,会缓存一定的时间,在此时间之内,我们可以通过不同的方式来获取此运行时类。同时,这个 Class 类的实例只存在一个,可以通过它获取其对应类的有关信息。

获取 Class 实例的几种方式

注意方式 3 和 4 会报 java.lang.ClassNotFoundException,需要处理

// 方式1 调用运行时类的 .class 属性
Class clazz1 = Person.class;
System.out.println(clazz1);

// 方式2 调用运行时类的对象的 getClass()
Person person = new Person();
Class clazz2 = person.getClass();
System.out.println(clazz2);

// 方式3 Class 的静态方法 forName(String classPath)
// 在JDBC开发中常用此方法加载数据库驱动:
// 要使用全类名来加载这个类,一般数据库驱动的配置信息会写在配置文件中。加载这个驱动前要先导入jar包
Class clazz3 = Class.forName("life.yzt2020.Person");
System.out.println(clazz3);

// 方式4 类加载器
ClassLoader classLoader = ClassDemo.class.getClassLoader();
Class clazz4 = classLoader.loadClass("life.yzt2020.Person");
System.out.println(clazz4);

System.out.println(clazz1==clazz2); // true
System.out.println(clazz1==clazz3); // true
System.out.println(clazz1==clazz4); // true

数组的元素类型和长度一致,就是同一个 Class对象

类的加载和 ClassLoader

当程序主动使用某个类时,如果该类还未加载到内存中,系统就会通过以下步骤对该类进行加载和初始化

类的加载

  • 类的加载:将 class 文件字节码内容加载到内存,并将这些静态数据转换成方法区的运行时数据结构,在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中数据的访问入口
  • 类缓存:标准的 JavaSE 类加载器可以按照要求查找类,如果某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过 JVM 的垃圾回收机制可以回收这些 Class 对象

类加载器的分类

类加载器分类

类编译、运行的过程

类编译运行过程

使用 Classloader 加载配置文件

配置文件的路径默认识别为当前 module 的 src 下

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class ClassLoaderTest {
    public static void main(String[] args) {
        // 获取 ClassLoader
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        // 存放配置的容器
        Properties pros = new Properties();
        // 输入流,从 ClassLoader 获得,传入文件
        InputStream is = classLoader.getResourceAsStream("jdbc.properties");
        // 加载配置文件内容
        try {
            pros.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 根据 key 获取相应内容
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user:" + user + ",password:" + password);
    }
}

获取运行时类的完整结构

// 获取运行时类的 Class 对象
Class<?> clazz = Person.class;


/* 属性相关 */
// 获取运行时类及其父类中声明为 public 的属性
Field[] fields = clazz.getFields();

// 获取运行时类中声明的所有属性(不包含父类中声明的属性)
Field[] declaredFields = clazz.getDeclaredFields();

Field f = fields[0];

// 获取属性的权限修饰符
int modifier = f.getModifiers();
System.out.println(Modifier.toString(modifier));

// 获取属性的类型
Class type = f.getType();

// 获取属性的变量名
String fName = f.getName();


/* 方法相关 */
// 获取运行时类及其所父类中声明为 public 的方法
Method[] methods = clazz.getMethods();

// 获取运行时类中声明的所有方法(不包含父类中声明的方法)
Method[] declaredMethods = clazz.getDeclaredMethods();

Method method = methods[0];
// 获取方法的注解、返回值
Annotation[] annotations = method.getAnnotations();

// 获取方法的返回值类型
Class<?> returnType = method.getReturnType(); 

// 获取方法的参数列表
Class<?>[] parametersType = method.getParameterTypes();

// 获取方法的异常类型
Class<?>[] exceptionsType = method.getExceptionTypes();

/*构造器相关*/
// 获取运行时类中声明为 public 的构造器(不包含父类的构造器)
Constructor[] constructors = clazz.getConstructors();

// 获取运行时类中声明的所有构造器(不包含父类的构造器)
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();

/*父类相关*/
// 获取运行时类的父类
Class<?> superClass = clazz.getSuperclass();

// 获取运行时类的带泛型的父类
Type genericSuperClass = clazz.getGenericSuperclass();

// 获取运行时类的带泛型的父类的泛型:JDBC部分会用得到
// 强转
ParameterizedType parameterizedType = (ParameterizedType) genericSuperClass;
// 获取
Type[] typeArguments = parameterizedType.getActualTypeArguments();
// 强转打印
System.out.println(((Class)typeArguments[0]).getName());


/*接口相关*/
// 获取运行时类所实现的接口(不包括父类实现的接口)
Class<?>[] interfaces = clazz.getInterfaces();

// 获取运行时类的父类所实现的接口
Class<?>[] superClassInterfaces = clazz.getSuperclass().getInterfaces();

// 获取运行时类所在的包
Package pack = clazz.getPackage();

// 获取运行时类的注解
Annotation[] annotations = clazz.getAnnotations();

调用运行时类的指定结构

调用指定构造器

// 注意是会报异常的
// getDeclaredConstructor(Class<?>... parameterTypes) 参数作用是 指明构造器的参数列表以返回对应的构造器
Constructor constructor = Person.class.getDeclaredConstructor(String.class);
// 确保此构造器的可访问性
constructor.setAccessible(true);
// 调用此构造器创建运行时类的对象(推荐用这种方法创建运行时类的对象)
Person p = (Person)constructor.newInstance("Tom");

调用指定属性

// 1. 获取当前运行时类的构造器,使用构造器创建该运行时类的一个对象
Constructor constructor = Person.class.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Person p = (Person)constructor.newInstance("Tom");

// 2. 获取指定变量名的属性,getDeclaredField(String fieldName) 方法可以获取到所有属性,包括 private 的
Field name = Person.class.getDeclaredField("name");
// 3. 确保该属性是可访问的
name.setAccessible(true);
// 4. set get 方法,注意需要把指定对象作为参数传入
name.set(p,"Jack");
System.out.println(name.get(p));

调用指定方法

// 1. 获取当前运行时类的构造器,使用构造器创建该运行时类的一个对象
Constructor constructor = Person.class.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Person p = (Person)constructor.newInstance("tom");

// 2. 获取指定方法名和参数列表的方法
// getDeclaredMethod(String name,Class<?>... parameterTypes) 方法的参数就是要调用方法名 + 参数列表
Method method = Person.class.getDeclaredMethod("eat",String.class);
// 3. 确保该方法是课访问的
method.setAccessible(true);
// 4. 通过 Method 对象的 invoke 方法调用运行时类的方法,invoke 是有返回值的
// Object invoke(Object obj,Object... args) 方法的参数是 调用者(运行时类的对象) + 要调用方法的实参
method.invoke(p,"apple");
// 可以接受返回值,调用的方法为 void,则返回null
Object returnVal = method.invoke(p,"orange");
// 如果调用的方法是静态的,则 invoke 传入该运行时类 或着 null 即可
method.invoke(Person.class,"apple");

代理模式

使用一个代理将对象包装起来, 然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上。

静态代理

通过实现 Runnable 接口的方式实现多线程,就是一个静态代理

Class MyThread implements Runnable{} //相当于被代理类
Class Thread implements Runnable{} //相当于代理类
public static void main(String[] args) {
    MyThread t = new MyThread(); // 被代理类对象
    Thread thread = new Thread(t); // 代理类对象,传入被代理类对象
    thread.start();//启动线程;通过被代理类对象调用代理类的 run()
}

静态代理的缺点

  • 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展
  • 每一个代理类只能为一个接口服务,程序开发中必然产生过多的代理

动态代理

动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象

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

public class ProxyTest {
    public static void main(String[] args) {    
        // 获取被代理类的对象
        PlayGame proxyInstance = (PlayGame) ProxyFactory.getProxyInstance(new Computer());
        // 通过被代理类的对象调用代理类的方法
        proxyInstance.playGame("LOL");
        System.out.println(proxyInstance.getScore());

        proxyInstance = (PlayGame) ProxyFactory.getProxyInstance(new PlayStation());
        proxyInstance.playGame("GTA5");
        System.out.println(proxyInstance.getScore());
    }
}

// 返回代理类的代理类工厂
class ProxyFactory {
    // 静态方法,返回代理类,返回值类型为 Object,入参是被代理类对象
    public static Object getProxyInstance(Object proxiedObj) {
        // java.lang.reflect.Proxy 类的 newProxyInstance() 方法
        // 入参:被代理类的类加载器,被代理类实现的接口,InvocationHandler接口的对象(向这个对象内传入被代理类对象)
        // 每一个代理类的调用处理程序都要实现 InvocationHandler 接口
        return Proxy.newProxyInstance(proxiedObj.getClass().getClassLoader(),
                proxiedObj.getClass().getInterfaces(),new MyInvocationHandler(proxiedObj));
    }
}

class MyInvocationHandler implements InvocationHandler {
    // 被代理类对象
    private Object proxiedObj;

    public MyInvocationHandler(){};

    // 给被代理类对象赋值
    public MyInvocationHandler(Object proxiedObj) {
        this.proxiedObj = proxiedObj;
    }

    // 每一个代理类的调用处理程序都要实现 InvocationHandler 接口中的 invoke 方法
    // 入参:代理类的对象 proxy,代理类对象调用的方法对象,调用的方法的参数列表
    // 当代理类对象调用被代理类的某个方法时,会自动调用这个方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 可以声明自己的操作
        System.out.println("before invoke");
        // 通过反射调用被代理类的方法
        Object returnVal = method.invoke(proxiedObj,args);
        // 可以声明自己的操作
        System.out.println("after invoke");
        return returnVal;
    }
}

// 被代理类要实现的接口
interface PlayGame {
    public abstract  void playGame(String gameName);
    public abstract  int getScore();
}

// 被代理类1
class Computer implements PlayGame {
    private int score;
    @Override
    public void playGame(String gameName) {
        System.out.println("使用电脑玩游戏:" + gameName);
        score = 100;
    }

    @Override
    public int getScore() {
        return score;
    }
}

// 被代理类2
class PlayStation implements PlayGame {
    private int score;
    @Override
    public void playGame(String gameName) {
        System.out.println("使用 PS 玩游戏:" + gameName);
        score = 99;
    }

    @Override
    public int getScore() {
        return score;
    }
}

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://yzt.cool/archives/java的反射机制