反射
反射机制的概述
Reflection(反射)
是被视为动态语言
的关键。反射机制允许程序在执行期借助于Reflection API
取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类后,在堆内存的方法区中就产生了一个Class
类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息,可以通过这个对象看到类的结构。
对反射的理解:
- 之所以需要反射,是因为在很多情况下,我们需要根据运行时具体的情况再创建对应情况的对象,而无法在编译时提前确定好创建的类的对象。比如,有注册和登录两个类,我们需要根据用户的选择再来创建对应的注册或者登录对象。
- 反射机制能够允许我们在编码时,对于要在运行时才能确定的要操作的类直接抽象为一个对应的
运行时类
对象,运行时类
就是指运行时加载到内存的一个类,也就是运行时我们能确定的要操作的类,根据这个抽象的运行时类
对象,就能对该类进行实例化、调用方法等操作。
仍以注册和登录为例,利用反射机制,在编码时虽然无法确定运行时到底要操作注册类还是登录类,但可以通过创建对应的运行时类
,对该运行时类
进行对应的操作,以实现在编码时就完成在运行时才能确定的操作,真正在运行时就会根据内存中加载的类确定具体的运行时类
,从而完成对应的操作。
反射之前:引入需要的“包类”名称 --> 通过new实例化 --> 取得实例化对象
反射:实例化对象 --> getClass()方法 --> 得到完整的“包类”名称
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时获取泛型信息;
- 在运行时调用任意一个对象的成员变量和方法;
- 在运行时处理注解;
- 生成动态代理。
反射相关的主要API:
java.lang.Class
:代表一个类;java.lang.reflect.Method
:代表类的方法;java.lang.reflect.Field
:代表类的成员变量;java.lang.reflect.Constructor
:代表类的构造器;
反射初体验
1 使用反射能够实现正常操作一个运行时类
2 使用反射能够实现操作运行时类的私有结构
Class类
1 Class的介绍
关于java.lang.Class
类的理解:
- 类的加载过程:
程序经过javac.exe
命令以后,会生成一个或多个字节码文件(.class结尾),再使用java.exe
命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,这个过程就称为类的加载。
加载到内存中的类,就称为运行时类,而运行时类就作为Class
的一个实例。 - 简单地说,
Class
的实例对应运行时类的对象。
2 获取Class实例的方式
加载到内存中的运行时类,会缓存一定的时间,在这段时间中,可以通过不同的方式来获取运行时类。
获取Class实例的方式有四种,前三种是重点:
- 调用运行时类的
class
属性: - 使用运行时类的对象的
getClass()
方法: - 调用
Class
的静态方法:public static Class<?> forName(String className) throws ClassNotFoundException
,其中参数className
是指完整的类名,即包名.类名
: - 使用类的加载器:classLoader
运行时类就与类一样,就是一个类,不会因为不同的获取方式而获取不同的运行时类,它们始终是内存中的同一个运行时类。
3 哪些类型可以有Class类
或者说,运行时类的范围:
另外,注意运行时类始终是指类
,因此对于数组,只要数组的元素类型和维度一样,比如上例,就是同一个Class
。
类的加载过程
类的加载器
1 类的加载器介绍
类的加载器的作用是把类装载到内存中。因此,可以通过装载到内存中的类来获取其对应的类的加载器。
- 自定义类,使用系统类加载器进行加载;
- 类的加载器,即
ClassLoader
对象有方法getParent()
来获取其上一层类的加载器对象; - 系统类加载器的上一层是扩展类加载器;
- 扩展类加载器的上一层是引导类加载器,但引导类加载器是无法获取的。
2 使用类的加载器来读取配置文件
读取配置文件有两种方式,第一种就是使用输入流,第二种就是使用类的加载器的getResourceAsStream()
方法。
这里需要注意的点是:
- 对于输入流,读取文件时的相对路径是当前
Module
下; - 对于类的加载器,读取配置文件时的相对路径是当前
Module
下的src
下。
使用反射创建运行时类的对象
使用反射创建运行时类的对象,是通过调用运行时类的newInstance()
方法来实现的,该方法内部调用运行时类的空参构造器以实现创建对应运行时类的空参对象。
此外,该方法成功调用对应运行时类的空参构造器以实现实例化的条件是,首先运行时类必须提供空参构造器,然后对应运行时类的空参构造器的访问权限必须足够,也就是能满足当前需要的访问权限,比如private
就不行,通常需要是public
。
public T newInstance() throws InstantiationException, IllegalAccessException
在javabean中要求提供一个public
的空参构造器,原因如下:
- 满足能够通过反射来创建对应运行时类的对象;
- 满足子类继承对应父类时,能够成功默认调用
super()
。
示例:
体会反射的动态性:
获取运行时类的结构
1 获取运行时类的属性
1)getFields()
public Field[] getFields() throws SecurityException
:返回对应运行时类及其父类中声明为public
访问权限的属性。
2)getDeclaredFields()
- public Field[] getDeclaredFields() throws SecurityException:返回对应运行时类中声明的所有属性,但不包括其父类属性。
3)获取属性的各个结构
属性的完整结构的形式为:权限修饰符 数据类型 属性变量名 属性值
,由于属性值又分为static
的,即属于类层面的,和非static
的,属于对象的,这里先不讨论属性值的获取。
主要用到java.lang.reflect.Field
的方法:
public int getModifiers()
:返回对应属性的代表权限修饰符的整数,可以再使用Modifier.toString(int mod)
方法,将结果转换为代表权限修饰符的字符串。public Class<?> getType()
:返回对应属性的数据类型对应的类。public String getName()
:返回对应属性的属性变量名。
2 获取运行时类的方法
1)getMethods()
public Method[] getMethods() throws SecurityException
:返回对应运行时类及其父类中声明为public
访问权限的方法。
2)getDeclaredMethods()
public Method[] getDeclaredMethods() throws SecurityException
:返回对应运行时类中声明的所有方法,但不包含其父类的方法。
3)获取方法的各个结构
方法的完整结构的形式为:
@注解
权限修饰符 返回值类型 方法名(参数列表) throws 异常
获取方法的各个结构,主要用到java.lang.reflect.Method
中的相关方法,还有的方法是该类的父类中的方法:
public Annotation[] getAnnotations()
:返回对应方法的注解。public int getModifiers()
:返回对应方法的权限修饰符。public Class<?> getReturnType()
:返回对应方法的返回值类型对应的类。public String getName()
:返回对应方法的方法名。public Class<?>[] getParameterTypes()
:返回对应方法的参数类型对应的类。public Class<?>[] getExceptionTypes()
:返回对应方法的异常对应的类。
3 获取运行时类的构造器
1)getConstructors()
public Constructor<?>[] getConstructors() throws SecurityException
:返回对应运行时类的声明为public
的构造器。
2)getDeclaredConstructors()
public Constructor<?>[] getDeclaredConstructors() throws SecurityException
:返回对应运行时类的声明的所有构造器。
3)获取构造器的各个结构:与属性和方法类似,通过调用构造器对象对应方法可以获取构造器对象的各个结构。
4 获取运行时类的父类
1)getSuperclass()
public Class<? super T> getSuperclass()
:返回对应运行时类的父类对应的类。从结果可以看到获取的是直接父类。
2)getGenericSuperclass()
public Type getGenericSuperclass()
:返回对应运行时类的带泛型的父类。也是指带泛型的直接父类。
3)获取运行时类带泛型的父类的泛型
获取的运行时类的带泛型的父类是Type
接口的,其子接口为ParameterizedType
,表示参数化类型的类型。可以理解为Type
就是指一个带泛型的父类,可能没有传入实际类型,而ParameterizedType
是指能传入实际类型的带泛型的类。
使用ParameterizedType
对象的getActualTypeArguments()
可以得到泛型对应的实际类型,有可能没有传入实际类型就会返回对应的泛型名。
另外,Type
接口的实现类就是Class
,因此,如果想要得到泛型类对应的泛型类型的Class
,可以强转。
5 获取运行时类实现的接口、所在包、注解
1)getInterfaces()
public Class<?>[] getInterfaces()
:返回对应运行时类实现的接口。(不包含父类实现的接口)
2)getPackage()
public Package getPackage()
:返回对应运行时类的包。
3)getAnnotations()
public Annotation[] getAnnotations()
:返回对应运行时类的注解。
调用运行时类的指定结构
1 调用指定的属性
1)获取指定的属性对象:指定属性要指定属性名
public Field getField(String name) throws NoSuchFieldException, SecurityException
:返回对应运行时类指定的public
的属性。public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException
:返回对应运行时类指定的属性。
2)设置指定的属性的值:传入设置的值
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException
3)获取指定的属性的值:
public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException
4)想要对获取的private
属性进行设置和获取其值,必须通过对应private
属性对象调用public void setAccessible(boolean flag) throws SecurityException
方法,参数为true
后,以保证当前属性可访问,才能实现设置和读值操作。
5)操作指定的静态属性值:就是在使用get()/set()
方法时,第一个参数要么传为运行时类,要么传为null
。
2 调用指定的方法
1)获取指定的方法对象:指定方法要指定方法名和方法形参类型对应的类
public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
2)调用指定的方法:传入实参
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
3)想要对获取的private
方法进行调用,必须通过对应private
方法对象调用public void setAccessible(boolean flag) throws SecurityException
方法,参数为true
后,以保证当前方法可访问,才能实现调用操作。
4)获取调用的方法的返回值:invoke()
方法的返回值即为对应调用的方法的返回值。
5)调用指定的静态方法:就是在使用invoke()
方法时,第一个参数要么传为运行时类,要么传为null
。
3 调用指定的构造器:指定构造器只需指定参数类型对应的类。
1)获取指定的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
2)用指定构造器实例化对象:传入实参
public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
3)想要对获取的private
构造器进行实例化,必须通过对应private
构造器对象调用public void setAccessible(boolean flag) throws SecurityException
方法,参数为true
后,以保证当前构造器可访问,才能实现实例化操作。
反射的应用:动态代理
1 代理设计模式的原理
使用一个代理将被代理对象包装起来,然后用该代理对象取代原始的被代理对象。任何对原始被代理对象的调用都要通过代理对象。代理对象决定是否以及何时将方法调用转到原始被代理对象上。
简单地理解,就是代理对象是被代理对象的一个外部代表,任何需要被代理对象做的事,都通过代理对象来间接地实现。因此,代理对象一定要与被代理对象一致,即能够去做被代理对象要做的事,只不过代理对象在做的过程种可能再去调用代理对象去做,从而才能实现通过代理对象来操作被代理对象。
要实现代理对象和被代理对象的一致,通常就是将代理对象和被代理对象的一致性封装为一个接口,这个接口中的抽象方法就是它们一致要能实现的事,然后让它们都实现这个接口,被代理对象在实现接口中的对应方法时就是实现其对应的操作,而被代理对象在实现接口中的对应方法时就是实现对代理对象做该方法的封装。
之前学习的静态代理,特征是代理类和对应的被代理类都是在编译期间确定下来,一方面,这不利于程序的扩展,因为程序扩展意味着有新的代理类和被代理类,需要在编译期间就确定好,另一方面,这会导致程序开发中产生过多的代理,因为每个代理类确定了对应的被代理类,不同的被代理类就对应不同的代理类。
动态代理就是实现能够通过一个代理类动态地完成所有被代理类的代理功能。
动态代理是指通过代理类来调用其他对象的方法,并且是在程序运行时根据需要动态地创建对应的被代理对象和代理对象。
动态代理的使用场景:
- 调试;
- 远程方法调用。
2 动态代理的实现
要实现动态代理,需要解决两个问题:
根据加载到内存中的被代理对象,动态地创建对应的代理对象:
- 在代理类中创建一个接收加载到内存中的被代理对象为参数的方法,该方法动态地创建对应对象的代理对象并返回;
由于不同对象有不同的需要被代理的功能,即要保持代理对象与被代理对象的一致性,因此要实现动态创建对应的代理对象,要用到方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
,该方法的功能就是创建指定类加载器加载的、实现指定接口的、根据指定InvocationHandler中实现的方法实现动态调用被代理对象对应的方法的代理对象,因此三个参数分别对应:- loader:指定该代理对象的类加载器,通常就是与被代理对象一致;
- interfaces:指定该代理对象要实现的接口,通常就是与被代理对象一致;
- h:该参数是一个接口对应的实现类对象,其实现的方法就是当调用代理对象时会去调用的方法,即该方法就是去实现如何调用被代理对象的操作。
通过代理对象调用对应方法时,动态地去调用被代理对象对应的方法:
通过实现
InvocationHandler
接口中的方法来实现:Object invoke(Object proxy, Method method, Object[] args) throws Throwable
:该方法是当通过代理对象调用要代理实现的方法时,就会自动调用该方法。该方法就是用来实现代理对象如何调用被代理对象对应的方法,其中的第一个参数代表的就是当前的代理对象,第二个参数代表的就是要实现的方法,通过invoke()
方法来调用该参数对应的方法,而invoke()
方法要传入两个参数,一个是调用哪个对象的该方法,一个是对应的要传入的参数,因此要调用被代理对象对应的方法时,就是传入对应的被代理对象和对应的参数,并且invoke()
方法的返回值就是对应的调用的方法的返回值。最后,第三个参数就是代表着要通过invoke()
方法调用第二个参数对应得方法时要传入得对应的参数,即method.invoke(被代理对象,args)
。
3 示例
1)静态代理
- 接口
- 被代理类
- 代理类
- 测试
2) 动态代理
- 接口
- 被代理类
- 动态代理类
- 测试