Java 反射
JAVA反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
Class
Class 类十分特殊。它和一般类一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象。
import java.io.Serializable; public class DemoTest implements Serializable { private static final long serialVersionUID = 6186022545644402773L; public String name; public int age; public DemoTest() { super(); } public DemoTest(String name, int age) { super(); this.name = name; this.age = age; } public void hello(String param) { System.out.println("hello " + param); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class MainTest { public static void main(String[] args) { try { Class<?> c = Class.forName("com.....DemoTest"); // 类的名称 System.out.println(c.getName()); // 类的实例 DemoTest demoTest = (DemoTest) c.newInstance(); demoTest.setName("a"); System.out.println(demoTest.getName()); // 类实现的接口 Class<?>[] interfaces = c.getInterfaces(); for (Class<?> inter : interfaces) { System.out.println(inter.getName()); } // 获取有参构造函数 Constructor<?> con = c.getConstructor(String.class, int.class); DemoTest dt = (DemoTest) con.newInstance("abc", 12); System.out.println(dt.getName() + " " + dt.getAge()); // 获取类的成员变量 Field f2 = c.getField("age"); System.out.println(f2); // 获取指定对象上该字段表示的值 System.out.println(f2.get(dt)); // 获取指定的方法 Method m = c.getMethod("hello", String.class); // 反射调用方法 m.invoke(dt, "abc"); //获取所有属性 Field[] fields = c.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName()); } } catch (Exception e) { e.printStackTrace(); } } }
静态代理
一个典型的代理模式通常有三个角色:共同接口、真实对象、代理对象
共同接口
public interface Action { public void doSomething();
}
真实对象
public class RealObject implements Action{ public void doSomething() { System.out.println("do something"); } }
代理对象
public class Proxy implements Action { private Action realObject; public Proxy(Action realObject) { this.realObject = realObject; } public void doSomething() { System.out.println("proxy do"); realObject.doSomething(); } }
运行
Proxy proxy = new Proxy(new RealObject()); proxy.doSomething();
静态代理的优缺点
先看看代理模式的优点: 扩展原功能,不侵入原代码。
再看看这种代理模式的缺点:
假如有这样一个需求,有十个不同的RealObject,同时我们要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing
动态代理
动态代理的目的就是为了解决静态代理的缺点。
通过使用动态代理,我们可以通过在运行时,动态生成一个持有RealObject、并实现代理接口的Proxy,同时注入我们相同的扩展逻辑。哪怕你要代理的RealObject是不同的对象,甚至代理不同的方法,都可以动过动态代理,来扩展功能。
简单理解,动态代理就是我们上面提到的方案一,只不过这些proxy的创建都是自动的并且是在运行期生成的。
public class DynamicProxyHandler implements InvocationHandler { private Object realObject; public DynamicProxyHandler(Object realObject) { this.realObject = realObject; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理扩展逻辑 System.out.println("proxy do"); return method.invoke(realObject, args); } }
运行
public static void main(String[] args) { RealObject realObject = new RealObject(); Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject)); proxy.doSomething(); }
这个Proxy不是我们自己写的,而是java帮我们生成的
源码解析
这个Proxy是如何自动被生成的。入口就在newProxyInstance方法,核心代码如下:
private static final Class<?>[] constructorParams = { InvocationHandler.class }; public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{ Class<?> cl = getProxyClass0(loader, intfs); ... final Constructor<?> cons = cl.getConstructor(constructorParams); if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); }
整体流程就是:
1、生成代理类Proxy的Class对象。
2、如果Class作用域为私有,通过 setAccessible 支持访问
3、获取Proxy Class构造函数,创建Proxy代理实例。
生成Proxy的Class文件
生成Class对象的方法中,先是通过传进来的ClassLoader参数和Class[] 数组作为组成键,维护了一个对于Proxy的Class对象的缓存。这样需要相同Proxy的Class对象时,只需要创建一次。
第一次创建该Class文件时,为了线程安全,方法进行了大量的处理,最后会来到ProxyClassFactory的apply方法中,经过以下流程:
1、校验传入的接口是否由传入的ClassLoader加载的。
2、校验传入是否是接口的Class对象。
3、校验是否传入重复的接口。
4、拼装代理类包名和类名,生成.class 文件的字节码。
5、调用native方法,传入字节码,生成Class对象。
proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
第四步生成.class文件字节码的过程,主要分为两个阶段:
addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); } } methods.add(this.generateConstructor()); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); methods.add(pm.generateMethod()); } } methods.add(generateStaticInitializer());
第一个阶段的代码比较清晰,主要就是添加各种Method,比如toString()、equals,以及传入的代理接口中的方法。再添加一下构造方法以及静态初始化方法。这要构成了一个对象,存储生成Proxy的Class的一些信息。
到了这里,已经把要构造的Proxy的方法基本定义完成了,接下来就要生成这个.class文件了。
ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); dout.writeInt(0xCAFEBABE); ... dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); ... return bout.toByteArray();
看到这个CAFEBABE,就清楚第二阶段的内容了。
CAFEBABE是Class文件的魔数,关于Class文件这个咖啡宝贝的魔数,相信做Java的人都知道。没错,第二阶段就是生成字节码。按JVM规范,写入Class文件中包括权限控制、方法表、字段表等内容,生成符合规范的Class文件。最后返回对应的字节码。
字节码生成以后,通过调用native方法defineClass解析字节码,就生成了Proxy的Class对象。
Proxy的构造方法
Proxy的构造方法字节码生成部分:
MethodInfo minfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V",ACC_PUBLIC); DataOutputStream out = new DataOutputStream(minfo.code); code_aload(0, out); code_aload(1, out); out.writeByte(opc_invokespecial); out.writeShort(cp.getMethodRef(superclassName,"<init>", "(Ljava/lang/reflect/InvocationHandler;)V")); ...
关键在于,生成了一个参数为InvocationHandler的构造方法,code加载的是jvm方法区中的代码,然后通过invokespecial指令调用了父类构造方法。
查看生成的class
查看这个产生的Proxy到底是个什么样子。
注意ProxyGenerator中有这样一个逻辑:
if(saveGeneratedFiles) { ... FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class"); file.write(classFile); ... }
再看一下saveGeneratedFiles这个变量:
private final static boolean saveGeneratedFiles = java.security.AccessController.doPrivileged( new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue();
这是一个final类型的变量,通过GetBooleanAction方法读取系统变量,获取系统设置。默认这个值是false,稍微看一下System这个类的源码,发现有可以设置系统变量的Api,然后在程序的main 函数设置一下这个变量:
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
这个时候,再跑一遍程序,就可以看到生成的Proxy的Class文件了,直接双击利用 ide 反编译。
package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Action { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final void doSomething() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } ... static { try { ... m3 = Class.forName("Action").getMethod("doSomething", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
省略一些无关代码,可以看到两个重要的方法。
一个就是我们的代理方法doSomething、另一个就是构造方法。
这个$Proxy0 继承 Proxy并调用了父类的构造方法,回忆一下上文提到的invokeSpecial,怎么样,对上了吧。
看一下Proxy中这个构造方法:
protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; }
在看一下$Proxy0 的代理方法:
super.h.invoke(this, m3, (Object[])null);
再来回顾一下生成Proxy实例的过程:
private static final Class<?>[] constructorParams = { InvocationHandler.class }; ... final Constructor<?> cons = cl.getConstructor(constructorParams); ... return cons.newInstance(new Object[]{h});
其实newInstance生成Proxy实例时,通过$Proxy0的Class对象,选择了这个InvocationHandler为参数的构造方法,传入我们定义的InvocationHandler并生成了一个 Proxy0的实例!InvocationHandler 里有realObject的逻辑以及我们的扩展逻辑,当我们调用Proxy0的doSomething方法时,就会调用到我们InvocationHandler 里 实现的invoke方法。
总结