最近在补Java基础,看到了类型信息这块知识,记起某些Android api和一些库里都有用到反射或动态代理的知识,特此探究一番。
一、反射
首先百度到的定义:JAVA反射是在运行状态中,对于任意一个类,都能获取这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
看定义能够知道反射就是在程序运行的时候,通过获得某个未知类型对应的Class引用,来获得类型的所有信息,并可以动态地执行构造这种类型的对象,调用方法,访问域等操作。这里的未知类型是指不在本程序空间中定义的类,即在程序编译后才从网络或从其他地方加载进来的类,因此利用反射机制可以完成一段通用的代码以动态适应各种情况。
复习一下获得某类型的Class引用的方法:
1、获得了某个具体的对象引用如object,可以通过Class<?> c = object.getClass();2、获知某类的完整包名+类名,可以Class<?> c = Class.forName("path");
3、通过类字面常量,如某类名“Test”,Class<?> c = Test.class.
(关于Class引用,java程序在运行的时候并不是一次把所有的类都加载进来,当第一次使用某类时才会把类型对应的.Class文件加载进来,同时创建一个Class对象,其保存了这种类型的所有信息。)
一个模拟运行时使用反射的例子:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;
public class Unknownclass {
private static String mClassName;
public static void main(String[] args) throws ClassNotFoundException{
Scanner in = new Scanner(System.in);
mClassName = in.nextLine();
Class c = Class.forName(mClassName); //mClassName 模拟动态获取的类
Method[] methods = c.getDeclaredMethods();
System.out.println("类"+mClassName+"的所有方法:");
for(Method method : methods){
System.out.println(method+"");
}
Constructor[] constructors = c.getConstructors();
System.out.println("类"+mClassName+"的所有构造方法:");
for(Constructor construct : constructors){
System.out.println(construct+"");
}
}
public void method1(String s1){
}
public void method2(){
}
}
打印结果:
Unknownclass //输入
类Unknownclass的所有方法:
public static void Unknownclass.main(java.lang.String[]) throws java.lang.ClassNotFoundException
public void Unknownclass.method2()
public void Unknownclass.method1(java.lang.String)
类Unknownclass的所有构造方法:
public Unknownclass()
获取到class引用后可以动态地newInstance出该类型的对象,并通过invoke方法调用其成员方法等等。
其实花费很多功夫,最后完成的操作可能也就是构造一个对象,访问下域调用下方法,利用反射机制无疑降低了程序的性能,增加了程序的复杂度,但前面也说了,它可以加载、探知、使用编译期间完全未知的classe,使程序可以有一定的动态性和通用性,所以用得好的话很犀利,用不好就弄巧成拙了。
总结:反射机制用于在程序运行时获得任意对象的类型的所有信息,构造任意类型的对象并可调用其方法访问其域。动态代理就是基于反射机制实现的。
二、动态代理
首先复习一下代理的知识,代理是介于组合与继承之间的做法,使用一个代理类,内部保持一个实际使用的对象的引用,代理类实现所有和内部对象一样的方法(如实现相同的接口),并在方法中仅仅调用内部对象的对应方法,即代理做中间人的角色。
通过调用静态Proxy.newProxyInstance()创建动态代理,参数需要:一个类加载器、一个希望该代理实现的接口列表(不能是类或抽象类,这也导致动态代理仅适用于接口的类型)、以及InvocationHandler()接口的一个实现(调用处理器),需要知道对动态代理的所有方法调用都会重定位到调用处理器中。
通过一个例子来理解:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy {
public static void main(String[] args){
AnInterface proxy = (AnInterface)Proxy.newProxyInstance(AnInterface.class.getClassLoader(),
new Class[]{AnInterface.class}, new DynamicProxyHandler(new ActualObject()));
//构造动态代理对象并转换为某接口类型以调用接口声明的方法
proxy.method1("haha");
proxy.method2();
}
}
interface AnInterface{
void method1(String s1);
void method2();
}
class ActualObject implements AnInterface{
public void method1(String s1) {
System.out.println("调用了method2, 参数: " + s1);
}
public void method2() {
System.out.println("调用了method2");
}
}
class DynamicProxyHandler implements InvocationHandler{
private Object proxied; //实际使用的对象
public DynamicProxyHandler(Object proxied){
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("准备调用方法: " + method + " 参数:" + args);
return method.invoke(proxied, args); //动态调用某方法
}
}
打印结果:
准备调用方法: public abstract void AnInterface.method1(java.lang.String) 参数:[Ljava.lang.Object;@75b84c92
调用了method2, 参数: haha
准备调用方法: public abstract void AnInterface.method2() 参数:null
调用了method2
分析可知,proxy作为一个代理,相当于保持了一个对实际对象的引用,同样通过代理这个中间人来操作实际对象。
那么这是怎么实现的呢?从Proxy.newInstance()方法起看看:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h); //检查传入的调用处理器是否为空
final Class<?>[] intfs = interfaces.clone();
......
Class<?> cl = getProxyClass0(loader, intfs);
......
final Constructor<?> cons = cl.getConstructor(constructorParams); //利用反射机制
return cons.newInstance(new Object[]{h}); //返回传入调用处理器构造的某个接口类型的对象,并向上转为Object
......
}
然后回到调用Proxy.newInstance()方法的地方:
AnInterface proxy = (AnInterface)Proxy.newProxyInstance(AnInterface.class.getClassLoader(),
new Class[]{AnInterface.class}, new DynamicProxyHandler(new ActualObject()));
返回的Object对象向下转型成对应的接口类型,因为返回对象原本就是AnInterface类型的,所以没有任何问题,直接调用接口声明的方法,就能将方法调用重定位至调用处理器的Invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("准备调用方法: " + method + " 参数:" + args);
return method.invoke(proxied, args); //动态调用某方法
}
传入代理引用、调用的方法信息跟参数列表作为参数,而方法体的实现则是让实际对象proxied根据传入的参数,调用相应的方法。
关于反射和动态代理其他更深层的剖析和应用等等等等,有空再研究~