## 概述
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在**运行时**借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
### 反射能够提供的功能
* 在运行时判断任意一个对象所属的类
* 在运行时构造任意一个类的对象
* 在运行时判断任意一个类所具有的成员变量和方法
* 在运行时获取泛型信息
* 在运行时调用任意一个对象的成员变量和方法
* 在运行时处理注解
* 生成动态代理
## Class 类
### 概念
程序经过编译命令,会生成一个或多个字节码文件(.class),接着对某个字节码文件进行解释运行,当于将某个字节码文件加载到内存中,此过程就称为类的加载。已经加载到内存中的类,我们就称为运行时类,此运行时类,就作为 Class 类的一个实例,(类存放在方法区,其对应的 Class 实例存放在堆)。也就是说,Class 类的实例就对应着一个**运行时类**。加载到内存中的运行时类,会缓存一定的时间,在此时间之内,我们可以通过不同的方式来获取此运行时类。同时,这个 Class 类的实例只存在一个,可以通过它获取其对应类的有关信息。
### 获取 Class 实例的几种方式
注意方式 3 和 4 会报 java.lang.ClassNotFoundException,需要处理
```java
// 方式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 下
```java
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);
}
}
```
## 获取运行时类的完整结构
```java
// 获取运行时类的 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();
```
## 调用运行时类的指定结构
### 调用指定构造器
```java
// 注意是会报异常的
// getDeclaredConstructor(Class>... parameterTypes) 参数作用是 指明构造器的参数列表以返回对应的构造器
Constructor constructor = Person.class.getDeclaredConstructor(String.class);
// 确保此构造器的可访问性
constructor.setAccessible(true);
// 调用此构造器创建运行时类的对象(推荐用这种方法创建运行时类的对象)
Person p = (Person)constructor.newInstance("Tom");
```
### 调用指定属性
```java
// 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));
```
### 调用指定方法
```java
// 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 接口的方式实现多线程,就是一个静态代理
```java
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()
}
```
静态代理的缺点
* 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展
* 每一个代理类只能为一个接口服务,程序开发中必然产生过多的代理
### 动态代理
动态代理是指客户通过代理类来调用其它对象的方法,并且是在**程序运行时**根据需要**动态创建目标类的代理对象**
```java
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;
}
}
```

Java基础 | 反射