什么是反射
反射就是借助 Reflection API 在运行期间,获取任何类的内部结构,并可以操作这些内部结构,包括但不限于属性、方法等。
个人的理解:简单点儿说就是通常情况下,我们得先 new 一个某个类的对象,然后通过对象去设置属性值,调用方法等。而除此方式外,我们还可以通过另一种方式去实现这些操作,即借助某些特定的 API,通过这些 API 去操作属性、方法的过程,就可以简单的理解为反射。
这些 API 包括:
- java.lang.Class: 代表一 个类
- java.lang.reflect.Method: 代表类的方法
- java.lang.reflect.Field: 代表类的成员变量
- java.lang.reflect.Constructor: 代表类的构造器
理解 Class 类并获取 Class 的实例
什么是 Class 类
注意和小写的 class 可不是一个东西,小写的 class 是一个关键字。Class 是一个类,它的一个实例就表示加载到内存中的一个类,也可以说一个 Class 实例就对应着一个 .class 字节码文件。我们要想操作某个类的属性或者方法,必须先获取这个类对应的 Class 类的一个实例,然后通过这个实例才能再去获取类中的内部结构,可以说 Class 类的实例就是反射的源头。
获取 Class 实例的四种方式
① 通过类的 class 属性获取
已知具体的类,就可以通过该类的 class 属性获取,该方法最为安全可靠,程序性能最高。
Class clazz = String.class;
② 通过 getClass() 方法获取
此方法的前提是先有一个类的对象,然后调用该对象的 getClass() 方法
Person p1 = new Person();
Class clazz = p1.getClass();
③ 通过 Class 类的静态方法获取
需要知道一个类的全路径类名
Class clazz = Class.forName(“java.lang.String”);
④ 通过 ClassLoader 获取
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);
Class 类的实例除可以代表一个运行时类外,还可以代表以下结构:
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
类的加载过程
-
加载
将 .class 字节码文件加载到内存中,将静态数据转换成方法区的运行时数据结构,并生成一个代表此类的 Class 对象,这个过程需要类加载器参与。 -
链接:将 Java 类的二进制代码合并到 JVM 的运行状态之中
验证:确保加载的类信息符合 JVM 规范,例如:以 cafe 开头,没有安全方面的问题
准备:为静态变量分配内存(在方法区中)并设置默认值
解析:虚拟机常量池的符号引用替换为直接引用 -
初始化
执行类构造器 () 方法的过程,此过程会为静态变量赋值(显示赋值或在静态代码块中赋值,谁在前面先执行谁)。
如果在初始化的时候,发现这个类的父类也没有进行初始化,则会先初始化其父类。
何时会发生类的初始化? -
类的主动引用(一定会发生类的初始化)
① 当虚拟机启动,先初始化main方法所在的类
② new 一个类的对象
③ 调用类的静态成员(除了final常量)和静态方法
④ 使用java.lang.reflect包的方法对类进行反射调用
⑤ 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类 -
类的被动引用(不会发生类的初始化)
① 当访问一个静态域时,只有真正声明这个域的类才会被初始化。也就是说当通过子类引用父类的静态变量,不会导致子类初始化
② 通过数组定义类引用,不会触发此类的初始化
③ 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
ClassLoader 的理解
ClassLoader 见名知意,就是用来加载类的。
不过一旦某个类被加载到类加载器中,它将会缓存一段时间。
1)ClassLoader 的分类
- Bootstap ClassLoader:引导类加载器。C++ 编写的,JVM 自带的加载器,用来加载 Java 的核心类库,该加载器无法获取
- Extension ClassLoader:扩展类加载器。用来加载 jre/lib/ext 目录下的 jar 包中的类
- System ClassLoader:系统类加载器。用来加载我们自己写的类
2)ClassLoader 的另一个作用
ClassLoader 还可以用来加载配置文件,且默认配置文件在 module 的 src 目录下。
Properties pros = new Properties();
// 此时的文件默认在当前的module下。
// 读取配置文件的方式一:
// FileInputStream fis = new FileInputStream("jdbc.properties");
// FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
// pros.load(fis);
// 读取配置文件的方式二:使用ClassLoader
// 配置文件默认识别为:当前 module 的 src 下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + ",password = " + password);
使用反射创建对象
1)调用空参构造器
Class<Person> clazz = Person.class;
Person obj = clazz.newInstance();
2)调用指定的构造器
//1. 根据全类名获取对应的Class 对象
String name = “com.java.Person";
Class clazz = null;
clazz = Class.forName(name);
//2. 调用指定参数结构的构造器,生成Constructor 的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3. 通过Constructor 的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);
使用反射获取运行时类的完整结构
我们可以通过反射获取类的 构造器、属性、方法、父类、父类的泛型、接口、异常、注解、所在的包等。
获取构造器
public Constructor<T>[] getConstructors()
:获取当前运行时类中声明为 public 的构造器
public Constructor<T>[] getDeclaredConstructors()
:获取当前运行时类中声明的所有构造器
通过反射获取类的构造器之后,还可以通过 Constructor 类获取构造器的修饰符、名称、参数的类型:
获取修饰符:public int getModifiers();
获取构造器的名称:public String getName();
获取参数的类型:public Class<?>[] getParameterTypes();
获取属性
public Field[] getFields()
:获取当前运行时类及其父类中声明为 public 访问权限的属性
public Field[] getDeclaredFields()
:获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
得到 Field 之后,还可以获取属性的修饰符、类型、名称:
以整数形式获取修饰符:public int getModifiers()
int modifier = f.getModifiers();
// 得到整数之后,转换成以字符串呈现
System.out.print(Modifier.toString(modifier) + "\t");
获取属性类型:public Class<?> getType()
获取属性名称:public String getName()
获取方法
public Method[] getMethods()
:获取当前运行时类及其所父类中声明为public权限的方法
public Method[] getDeclaredMethods()
:获取当前运行时类中声明的所方法(不包含父类中声明的方法)
/*
@Xxxx
权限修饰符 返回值类型 方法名(参数类型1 形参名1,...) throws XxxException{}
*/
@Test
public void test2(){
Class clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
//1.获取方法声明的注解
Annotation[] annos = m.getAnnotations();
for(Annotation a : annos){
System.out.println(a);
}
//2.权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + "\t");
//3.返回值类型
System.out.print(m.getReturnType().getName() + "\t");
//4.方法名
System.out.print(m.getName());
System.out.print("(");
//5.形参列表
Class[] parameterTypes = m.getParameterTypes();
if(!(parameterTypes == null && parameterTypes.length == 0)){
for(int i = 0;i < parameterTypes.length;i++){
if(i == parameterTypes.length - 1){
System.out.print(parameterTypes[i].getName() + " args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
}
}
System.out.print(")");
//6.抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();
if(exceptionTypes.length > 0){
System.out.print("throws ");
for(int i = 0;i < exceptionTypes.length;i++){
if(i == exceptionTypes.length - 1){
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ",");
}
}
System.out.println();
}
}
获取父类
public Class<? Super T> getSuperclass()
获取带
Type genericSuperclass = clazz.getGenericSuperclass();
获取父类的泛型
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
// 获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
// System.out.println(actualTypeArguments[0].getTypeName());
System.out.println(((Class)actualTypeArguments[0]).getName());
获取接口
public Class<?>[] getInterfaces()
获取注解
Annotation[] annotations = clazz.getAnnotations();
获取所在的包
Package getPackage()
使用反射获取运行时类的指定结构
1)调用指定的构造器
Class clazz = Person.class;
//private Person(String name)
/*
1.获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器是可访问的(如果构造器是私有的,则必须让构造器是可访问的)
constructor.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);
2)调用指定的属性
Class clazz = Person.class;
// 创建运行时类的对象
Person p = (Person) clazz.newInstance();
// 1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");
// 2.保证当前属性是可访问的
name.setAccessible(true);
// 3.设置指定对象的属性值
name.set(p,"Tom");
// 4.获取指定对象的属性值
System.out.println(name.get(p));
3)调用指定的方法
Class clazz = Person.class;
// 创建运行时类的对象
Person p = (Person) clazz.newInstance();
/*
1.获取指定的某个方法
getDeclaredMethod(String name, Class<?>... parameterTypes)
参数1 :指明获取的方法的名称
参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);
/*
3. 调用方法的invoke(Object obj, Object... args)
参数1:方法的调用者
参数2:给方法形参赋值的实参
invoke() 的返回值即为对应类中调用的方法的返回值。
*/
Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
System.out.println(returnValue);
System.out.println("*************如何调用静态方法*****************");
// private static void showDesc()
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
// 如果调用的运行时类中的方法没返回值,则此invoke()返回null
// Object returnVal = showDesc.invoke(null);
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal); //null
说明:
- 若原方法无参数,则 invoke() 的第二个参数可以不写(第一个参数必须要写)
- 若原方法无返回值,则 invoke() 返回 null
- 若是静态方法,则形参可以写 null 或 此类对应的 Class 的实例
反射的应用:动态代理
代理模式要求代理类与被代理类都实现共同的接口,这是前提。
动态代理与静态代理最大的不同就是,我们不需要再创建代理类,而可以通过 jdk 动态地为我们生成代理类的对象。我们只需要提供 接口、被代理类,以及多了一个 InvocationHandler 接口的实现类。
我们通过 InvocationHandler 中的 invoke() 方法,去调用被代理类的方法,还可以在此方法中进行一些其他的操作。原理就是:jdk 帮我们生成的代理类的字节码文件中,维护了一个 InvocationHandler 类型的引用,所以我们在生成代理类的对象时,要传入一个此接口的实现类对象。而调用代理类中的任何方法,都会转发到 InvocationHandler 类中的 invoke() 方法,所以我们执行代理类的方法时,实际执行的是 InvocationHandler 中的 invoke() 方法。
先定义接口
interface Human {
String getBelief();
void eat(String food);
}
创建被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
创建一个实现了 InvocationHandler 接口的实现类
class MyInvocationHandler implements InvocationHandler{
private Object obj;
public void bind(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(obj, args);
return invoke;
}
}
创建一个工厂,用于生成代理对象
class ProxyFactory{
public static Object createProxy(Object obj){
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
myInvocationHandler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),myInvocationHandler);
}
}
创建测试类
public class ProxyTest {
@Test
public void testProxy(){
SuperMan superMan = new SuperMan();
// 生成了一个代理类对象
Human proxy = (Human) ProxyFactory.createProxy(superMan);
// 执行代理类中的任何方法时,都会去执行 invoke() 方法
String belief = proxy.getBelief();
System.out.println(belief);
proxy.eat("四川麻辣烫");
}
}
看完上面的例子,不知道大家有没有疑问,反正我目前有两个疑问:
1. 调用代理类的方法的时候,怎么就去执行 MyInvocationHandler 类中 invoke 方法了呢?可参考下面的文章:
Java JDK 动态代理(AOP)使用及实现原理分析
2. invoke(Object proxy, Method method, Object[] args) 方法中的第一个参数 proxy 根本没有用到,那为什么还需要它呢?可参考下面的文章:
InvocationHandler中invoke方法中的第一个参数proxy的用途