一.什么是反射
反射,一种计算机处理方式。是程序可以访问、检测和修改它本身状态或行为的一种能力。java反射使得我们可以在程序运行时动态加载一个类,动态获取类的基本信息和定义的方法,构造函数,域等。除了检阅类信息外,还可以动态创建类的实例,执行类实例的方法,获取类实例的域值。反射使java这种静态语言有了动态的特性
二.反射机制的作用
• 在运行时判断任意一个对象所属的类;
• 在运行时构造任意一个类的对象;
• 在运行时判断任意一个类所具有的成员变量和方法;
• 在运行时调用任意一个对象的方法;
• 生成动态代理。
三.反射机制的优点与缺点
静态编译:在编译时确定类型,绑定对象,即通过。
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。
反射机制的优点:可以实现动态创建对象和编译,体现出很大的灵活性。通过反射机制我们可以获得类的各种内容,进行了反编译。对于JAVA这种先编译再运行的语言来说,反射机制可以使代码更加灵活,更加容易实现面向对象。
反射机制的缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它 满足我们的要求。这类操作总是慢于只直接执行相同的操作。
四.利用反射机制能获得什么信息
获取类3中方式
获得构造函数的方法
Constructor getConstructor(Class[] params)//根据指定参数获得public构造器
Constructor[] getConstructors()//获得public的所有构造器
Constructor getDeclaredConstructor(Class[] params)//根据指定参数获得public和非public的构造器
Constructor[] getDeclaredConstructors()//获得public的所有构造器
获得类方法的方法
Method getMethod(String name, Class[] params),根据方法名,参数类型获得方法
Method[] getMethods()//获得所有的public方法
Method getDeclaredMethod(String name, Class[] params)//根据方法名和参数类型,获得public和非public的方法
Method[] getDeclaredMethods()//获得所以的public和非public方法
获得类中属性的方法
Field getField(String name)//根据变量名得到相应的public变量
Field[] getFields()//获得类中所以public的方法
Field getDeclaredField(String name)//根据方法名获得public和非public变量
Field[] getDeclaredFields()//获得类中所有的public和非public方法
运行时调用methods
首先准备一个Class[]做为参数类型,然后调用getMethod(),获得特定的Method object。接下来准备一个Object[]放置变量,然后调用上述所得之特定Method object的invoke()。
调用私有方法,需要抑制访问控制检查
method.setAccessible(true)会赋值给override
利用反射实现工厂模式
根据完整类名返回类的对象
传入类名
配置文件结合反射实现工厂模式(模拟IOC)
读取properties文件内容
动态代理
获取类的构造函数(代码)
在获取一个类的class后,我们可以通过反射获取一个类的所有构造函数,class类内部封装了如下方法用于提取构造函数:
上面的代码片段主要就是读取配置,检查缓存中是否有有效数据,如果有,直接从缓存中返回,如果没有,调用native的方法从jvm中请求数据,然后设置到缓存。比较重要的是reflectionData()这个调用。这个方法主要是用于延迟创建并缓存ReflectionData对象,注意是对象,里面并没有保存反射数据,这些数据只有在第一次执行相应的反射操作后才会被填充。下面是这个方法的实现代码:
SoftReference声明reflectionData是一个软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存,不过也可以通-XX:SoftRefLRUPolicyMSPerMB参数控制回收的时机
下面看一下newReflectionData()的实现:
上面的几个代码片段是反射获取一个类的构造函数的主要方法调用。主要流程就是先从class内部的reflectionData缓存中读取数据,如果没有缓存数据,那么就从jvm中去请求数据,然后设置到缓存中,供下次使用。
ReflectionData
为了提高反射的性能,缓存显然是必须的。class类内部有一个useCaches静态变量来标记是否使用缓存,这个值可以通过外部配置项sun.reflect.noCaches进行开关。class类内部提供了一个ReflectionData内部类用来存放反射数据的缓存
执行构造函数
在通过反射获取到一个类的构造函数我们,一般我们会试图通过构造函数去实例化声明这个构造函数的类,如下:
MyClass myClass = (MyClass) constructor.newInstance();
下面来看看这个newInstance()到底发生了什么:
上面代码主要就是做权限检查,如果权限通过,则通过执行acquireConstructorAccessor()获取一个ConstructorAccessor来真正执行newInstance,真正工作的是ReflectionFactory.newConstructorAccessor(),这个方法的主要工作就是为一个Constructor动态生成针对ConstructorAccessor接口的实现ConstructorAccessorImpl,下面来看一下这个方法实现:
由上面代码可见,在生成ConstructorAccessorImpl的操作上,一共提供多种版本的实现:直接抛出异常的实现,native执行的实现,asm生成代码的实现。native的实现比较简单,asm 版本的主要就是字节码操作了
Method获取
调用Class类的getDeclaredMethod可以获取指定方法名和参数的方法对象Method。
getDeclaredMethod
其中privateGetDeclaredMethods方法从缓存或JVM中获取该Class中申明的方法列表,searchMethods方法将从返回的方法列表里找到一个匹配名称和参数的方法对象。
searchMethods
如果找到一个匹配的Method,则重新copy一份返回,即Method.copy()方法
所次每次调用getDeclaredMethod方法返回的Method对象其实都是一个新的对象,且新对象的root属性都指向原来的Method对象,如果需要频繁调用,最好把Method对象缓存起来。
privateGetDeclaredMethods
从缓存或JVM中获取该Class中申明的方法列表,实现如下:
Method调用
获取到指定的方法对象Method之后,就可以调用它的invoke方法了,invoke实现如下:
应该注意到:这里的MethodAccessor对象是invoke方法实现的关键,一开始methodAccessor为空,需要调用acquireMethodAccessor生成一个新的MethodAccessor对象,MethodAccessor本身就是一个接口,实现如下:
在acquireMethodAccessor方法中,会通过ReflectionFactory类的newMethodAccessor创建一个实现了MethodAccessor接口的对象,实现如下:
在ReflectionFactory类中,有2个重要的字段:noInflation(默认false)和inflationThreshold(默认15),在checkInitted方法中可以通过-Dsun.reflect.inflationThreshold=xxx和-Dsun.reflect.noInflation=true对这两个字段重新设置,而且只会设置一次;
如果noInflation为false,方法newMethodAccessor都会返回DelegatingMethodAccessorImpl对象,
DelegatingMethodAccessorImpl的类实现
其实,DelegatingMethodAccessorImpl对象就是一个代理对象,负责调用被代理对象delegate的invoke方法,其中delegate参数目前是NativeMethodAccessorImpl对象,所以最终Method的invoke方法调用的是NativeMethodAccessorImpl对象invoke方法,实现如下:
这里用到了ReflectionFactory类中的inflationThreshold,当delegate调用了15次invoke方法之后,如果继续调用就通过MethodAccessorGenerator类的generateMethod方法生成MethodAccessorImpl对象,并设置为delegate对象,这样下次执行Method.invoke时,就调用新建的MethodAccessor对象的invoke()方法了。
这里需要注意的是:
generateMethod方法在生成MethodAccessorImpl对象时,会在内存中生成对应的字节码,并调用ClassDefiner.defineClass创建对应的class对象,实现如下:
在ClassDefiner.defineClass方法实现中,每被调用一次都会生成一个DelegatingClassLoader类加载器对象
这里每次都生成新的类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载,从其设计来看本身就不希望这些类一直存在内存里的,在需要的时候有就行了。
Beantuils
Beanutils工具在使用时几乎只用到以下几个方法,其中一个方法通常情况下都是使用匿名内部类。
BeanUtils.setProperty(bean, name, value);其中bean是指你将要设置的对象,name指的是将要设置的属性(写成”属性名”),value(从配置文件中读取到到的字符串值)
BeanUtils.copyProperties(bean, name, value),和上面的方法是完全一样的。使用哪个都可以
BeanUtils.populate(bean,Map),其中Map中的key必须与目标对象中的属性名相同,否则不能实现拷贝。
BeanUtils.copyProperties(newObject,oldObject),实现对象的拷贝