Java反射机制主要提供了如下功能:java
不少框架都用到了反射机制,包括大名鼎鼎的Spring。所以,了解反射也能够说是为以后学习框架源码而打下坚实的基础。编程
即使编译时不知道类型和方法名称,也能使用反射。反射使用类对象提供的基本元数据,能从类对象中找出方法或字段的名称,而后获取表示方法或字段的对象。数组
在Java中,静态成员和普通数据类型不是对象,其余皆是。浏览器
那么问题来了,类是谁的对象?缓存
是java.lang.Class
的实例对象。安全
Class.forName(ClassName)//能够动态加载类——也就是运行时加载
(使用 Class::newInstance() 或另外一个构造方法)建立实例时也能让实例具备反射功能。若是有一个能反射的对象和一个 Method 对象,咱们就能在以前类型未知的对象上调用任何方法。架构
反射出来的对象信息是几乎未知的,因此反射也并非那么的好用。框架
不少,也许是多数 Java 框架都会适度使用反射。若是编写的架构足够灵活,在运行时以前都不知道要处理什么代码,那么一般都须要使用反射。例如,插入式架构、调试器、代码浏览器和 REPL 类环境每每都会在反射的基础上实现。ide
反射在测试中也有普遍应用,例如,JUnit 和 TestNG 库都用到了反射,并且建立模拟对象也要使用反射。若是你用过任何一个 Java 框架,即使没有意识到,也几乎能够肯定,你使用的是具备反射功能的代码。函数
常见的反射手段有JDK
反射和cglib
反射。
在本身的代码中使用反射 API 时必定要知道,获取到的对象几乎全部信息都未知,所以处理起来可能很麻烦。
只要知道动态加载的类的一些静态信息(例如,加载的类实现一个已知的接口),与这个类交互的过程就能大大简化,减轻反射操做的负担。
使用反射时有个常见的误区:试图建立能适用于全部场合的反射框架。正确的作法是,只处理当前领域当即就能解决的问题。
使用反射的第一步就是获取Class对象,Class对象里存储了不少关键信息——毕竟这是用来描述类的class。
咱们能够这样来获取Class信息:
Class<?> clz = Class.forName(obj.getClazz()); //经过class生成相应的实例 Object newObj = clz.newInstance
从Java1.5开始,Class类就支持泛型化了。好比:String.class就是Class<String>类型。
在反射最经常使用的API就是Method了。
类对象中包含该类中每一个方法的 Method 对象。这些 Method 对象在类加载以后惰性建立,因此在 IDE 的调试器中不会当即出现。
Method对象中保存的方法和元数据:
private Class<?> clazz; private int slot; // This is guaranteed to be interned by the VM in the 1.4 // reflection implementation private String name; private Class<?> returnType; private Class<?>[] parameterTypes; private Class<?>[] exceptionTypes private int modifiers; // Generics and annotations support private transient String signature; // Generic info repository; lazily initialized private transient MethodRepository genericInfo; private byte[] annotations; private byte[] parameterAnnotations; private byte[] annotationDefault; private volatile MethodAccessor methodAccessor;
咱们能够经过getMethod得到对象的方法:
Object rcvr = "str"; try { Class<?>[] argTypes = new Class[] { }; //其实这个参数没有也不要紧,由于hashCode方法不须要参数 Object[] args = null; Method hasMeth = rcvr.getClass().getMethod("hashCode", argTypes); Object ret = hasMeth.invoke(rcvr,args); System.out.println(ret); } catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException | InvocationTargetException x) { x.printStackTrace(); }
若是要调用非公开方法,必须使用 getDeclaredMethod() 方法才能获取非公开方法的引用,并且还要使用 setAccessible() 方法覆盖 Java 的访问控制子系统,而后才能执行:
public class MyCache { private void flush() { // 清除缓存…… } } Class<?> clz = MyCache.class; try { Object rcvr = clz.newInstance(); Class<?>[] argTypes = new Class[]{}; Object[] args = null; Method meth = clz.getDeclaredMethod("flush", argTypes); meth.setAccessible(true); meth.invoke(rcvr, args); } catch (IllegalArgumentException | NoSuchMethodException | InstantiationException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException | InvocationTargetException x) { x.printStackTrace(); }
Java 的反射 API 每每是处理动态加载代码的惟一方式,不过 API 中有些让人头疼的地方,处理起来稍微有点困难:
void 就是个明显的问题——虽然有 void.class,但没坚持用下去。Java 甚至不知道 void 是否是一种类型,并且反射 API 中的某些方法使用 null 代替 void。
这很难处理,并且容易出错,尤为是稍微有点冗长的数组句法,更容易出错。
Java反射的API中还提供了动态代理。动态代理是实现了一些接口的类(扩展 java.lang.reflect.Proxy 类)。这些类在运行时动态建立,并且会把全部调用都转交给 InvocationHandler 对象处理:
InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws javaThrowable { String name = method.getName(); System.out.println("Called as: "+ name); switch (name) { case "isOpen": return false; case "close": return null; } return null; } }; Channel c = (Channel) Proxy.newProxyInstance(Channel.class.getClassLoader(), new Class[] { Channel.class }, h); c.isOpen(); c.close();
代理能够用做测试的替身对象(尤为是测试使用模拟方式实现的对象)。
代理的另外一个做用是提供接口的部分实现,或者修饰或控制委托对象的某些方面:
public class RememberingList implements InvocationHandler { private final List<String> proxied = new ArrayList<>(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); switch (name) { case "clear": return null; case "remove": case "removeAll": return false; } return method.invoke(proxied, args); } } RememberingList hList = new RememberingList(); List<String> l = (List<String>) Proxy.newProxyInstance(List.class.getClassLoader(), new Class[] { List.class }, hList); l.add("cat"); l.add("bunny"); l.clear(); System.out.println(l);
Java7中提供了方法句柄,比起“传统”的反射机制。更为好用,并且性能更好。
以以前的反射hashCode为例
Object rcvr = "str"; try { MethodType mt = MethodType.methodType(int.class); MethodHandles.Lookup l = MethodHandles.lookup(); MethodHandle hashMeth = l.findVirtual(rcvr.getClass(), "hashCode", mt); int result; try { result = (int) hashMeth.invoke(rcvr); System.out.println(result); } catch (Throwable t) { t.printStackTrace(); } } catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); }
MethodType.methodType(int.class);
指定了方法的返回类型,其实不止如此。methodType还能够填入其函数参数经过MethodHandles.Lookup
能够得到当前执行方法的上下文对象,在这个对象上能够调用几个方法(方法名都以 find 开头),查找须要的方法,包括findVirtual()
、findConstructor()
` 和 findStatic()
findGetter()
和 findSetter()
方法,分别能够生成读取字段和更新字段的方法句柄。MethodHandles.Lookup.findVirtual()
得到了方法句柄(MethodHandle)通常来讲,invoke() 方法会调用 asType() 方法转换参数。转换的规则以下:
方法句柄提供的动态编程功能和反射同样,但处理方式更清晰明了。并且,方法句柄能在 JVM 的低层执行模型中很好地运转,所以,性能比反射好得多。