反射那些事儿——Java动态装载和反射技术

2023-05-22,,

一直以来反射都是只闻其声,却无法将之使用,近日尽心下来学习下,发现了很多精妙之处。

Java动态装载和反射技术

一、类的动态装载

1.Java代码编译和执行的整个过程包含了以下三个重要的机制:

  ● Java源码编译机制:得到Class字节码
  ● 类加载机制:包括类的装载,连接,初始化三个阶段
  ● 类执行机制

2.类加载机制分为装载,连接,初始化三个阶段

(1)装载阶段:类装载器(ClassLoader) 把编译形成的class文件载入内存,创建类相关的java.lang.Class对象,这个Class对象封装了我们要使用的类的类型信息。

ClassLoader分四层,分别为Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader,他们不是类继承的父子关系,是逻辑上的上下级关系。

Bootstrap ClassLoader是启动类加载器,它是用C++编写的,从%jre%/lib目录中加载类,或者运行时用-Xbootclasspath指定目录来加载。

Extension ClassLoader是扩展类加载器,从%jre%/lib/ext目录加载类,或者运行时用-Djava.ext.dirs制定目录来加载。

App ClassLoader,系统类加载器,它会从系统环境变量配置的classpath来查找路径,环境变量里的.表示当前目录,是通过运行时-classpath或-Djava.class.path指定的目录来加载类。

ClassLoader使用了双亲委托模式进行类加载。下图是ClassLoader的加载类流程图,以加载一个类的过程类示例说明整个ClassLoader的过程。

(2)连接阶段:又可以分为三个子步骤:验证、准备和解析。(纯抄,暂时也不去理解了)

验证就是要确保java类型数据格式 的正确性,并适于JVM使用。

准备阶段,JVM为静态变量分配内存空间,并设置默认值,注意,这里是设置默认值,比如说int型的变量会被赋予默认值0 。在这个阶段,JVM可能还会为一些数据结构分配内存,目的 是提高运行程序的性能,比如说方法表。

解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用。这个阶段可以被推迟到初始化之后,当程序运行的过程中真正使用某个符号引用的时候 再去解析它

(3)初始化阶段:

类会在首次被“主动使用”时执行初始化,为类(静态)变量赋予正确的初始值。在Java代码中,一个正确的初始值是通过类变量初始化语句或者静态初始化块给出的。而我们这里所说的主动使用 包括:

a.        创建类的实例

b.        调用类的静态方法

c.        使用类的非常量静态字段

d.        调用Java API中的某些反射方法

e.        初始化某个类的子类

f.        含有main()方法的类启动时

初始化一个类包括两个步骤:

1、  如果类存在直接父类的话,且直接父类还没有被初始化,则先初始化其直接父类

2、  如果类存在一个初始化方法,就执行此方法

注:初始化接口并不需要初始化它的父接口。

问题

我们加载一个类的时候,很多时候可以使用Class.forname,也可以使用ClassLoader.loadClass,这两者有什么区别呢?

Class.forname的两个重载方法

1 public static Class<?> forName(String name, boolean initialize,
2 ClassLoader loader)
3 throws ClassNotFoundException
4
5
6 public static Class<?> forName(String className)
7 throws ClassNotFoundException

ClassLoader.loadClass的两个重载方法

1 protected synchronized Class<?> loadClass(String name, boolean resolve)
2 throws ClassNotFoundException
3
4 public Class<?> loadClass(String name) throws ClassNotFoundException

二者的区别(结论):

1.Class.forName返回的Class对象可以决定是否初始化,默认会初始化。而ClassLoader.loadClass返回的类型绝对不会初始化,最多只会做连接操作。 
2.Class.forName可以决定由哪个classLoader来请求这个类型,默认使用当前类的ClassLoader。而ClassLoader.loadClass是用确定的当前classLoader去请求。

二、反射

我们知道利用类加载机制可以动态加载Class,这样我们就可以通过class.forName或者ClassLoader.loadClass获得任何一个Class的Class<?>对象。

利用这个Class<?>对象,我们可以将这个Class反射出来。

下面的例子,使用getSystemClassLoader,Load出String的Class<?>,在DumpClassInfo类中,把String类中的inferface,superClass,filed,method等都获取出来。

1         ClassLoader loader =  ClassLoader.getSystemClassLoader();
2
3 Class<?> cls = loader.loadClass("java.lang.String");
4
5 DumpClassInfo.dump(cls, System.out);
  1 package com.sgpro._01_classloader;
2
3 import java.io.IOException;
4 import java.io.OutputStream;
5 import java.lang.annotation.Annotation;
6 import java.lang.reflect.Constructor;
7 import java.lang.reflect.Field;
8 import java.lang.reflect.Method;
9 import java.lang.reflect.Modifier;
10
11 public class DumpClassInfo {
12
13 private static String dumppAnnotation(Annotation[] ann) {
14 StringBuffer ans = new StringBuffer();
15 if (ann != null) {
16 for (Annotation a : ann) {
17 if (ans.length() != 0) {
18 ans.append(",");
19 }
20 ans.append(a.toString());
21 }
22 }
23 return ans.length() == 0? "" : "/**\r\n" + ans.toString() + "*/\r\n";
24 }
25
26 public static void dump(Class<?> cls, OutputStream os) {
27
28 StringBuffer iStr = new StringBuffer(); //interface
29 StringBuffer fStr = new StringBuffer(); //Field
30 StringBuffer cStr = new StringBuffer(); //Constructor
31 StringBuffer mStr = new StringBuffer(); //Method
32
33 StringBuffer paramTypes = new StringBuffer(); //param
34 StringBuffer exceptionTypes = new StringBuffer(); //exception
35
36 /**
37 * 所有声明的属性
38 */
39 for (Field f : cls.getDeclaredFields()) {
40 fStr.append(
41 String.format("%s\t%s %s %s;\r\n",
42 dumppAnnotation(f.getDeclaredAnnotations())//注释
43 , Modifier.toString(f.getModifiers()) //修饰符 public private protected
44 , f.getType().getSimpleName() //类型 integer String 等
45 // , f.getGenericType() == null? "" : "<" + f.getGenericType() + ">"
46 , f.getName())); //字段名
47 }
48
49 /**
50 * 所有的构造方法
51 */
52 for (Constructor<?> c : cls.getConstructors()) {
53 paramTypes.setLength(0);
54 exceptionTypes.setLength(0);
55
56 for (Class<?> p: c.getParameterTypes()) {
57 if (paramTypes.length() != 0) {
58 paramTypes.append(", ");
59 }
60 paramTypes.append(p.getSimpleName());
61 }
62
63 for (Class<?> e: c.getExceptionTypes()) {
64 if (exceptionTypes.length() != 0) {
65 exceptionTypes.append(", ");
66 }
67 exceptionTypes.append(e.getSimpleName());
68 }
69
70 cStr.append(
71 String.format("%s\t%s %s (%s ) %s %s \r\n"
72 , dumppAnnotation(c.getDeclaredAnnotations())
73 , Modifier.toString(c.getModifiers())
74 , c.getDeclaringClass().getSimpleName()
75 , paramTypes
76 , exceptionTypes.length() > 0? "throws" : ""
77 , exceptionTypes));
78
79 }
80
81 /**
82 * 所有声明的方法
83 */
84 for (Method m : cls.getDeclaredMethods()) {
85 paramTypes.setLength(0);
86 exceptionTypes.setLength(0);
87
88 for (Class<?> c: m.getParameterTypes()) {
89 if (paramTypes.length() != 0) {
90 paramTypes.append(", ");
91 }
92 paramTypes.append(c.getSimpleName());
93 }
94
95 for (Class<?> c: m.getExceptionTypes()) {
96 if (exceptionTypes.length() != 0) {
97 exceptionTypes.append(", ");
98 }
99 exceptionTypes.append(c.getSimpleName());
100 }
101
102 mStr.append(
103 String.format("%s,\t%s %s %s (%s ) %s %s \r\n"
104 , dumppAnnotation(m.getDeclaredAnnotations())
105 , Modifier.toString(m.getModifiers())
106 , m.getReturnType().getSimpleName()
107 , m.getName()
108 , paramTypes
109 , exceptionTypes.length() > 0? "throws" : ""
110 , exceptionTypes));
111
112 }
113
114 /**
115 * interface获取
116 */
117 for (Class<?> intf : cls.getInterfaces()) {
118 if (iStr.length() != 0 ) {
119 iStr.append(", ");
120 }
121 iStr.append(intf.getSimpleName());
122 }
123
124
125 /**
126 * superClass
127 */
128 Class<?> supcls = cls.getSuperclass();
129
130
131 String dc = String.format("%s %s %s %s %s {\r\n%s\r\n%s\r\n%s\r\n}\r\n"
132 , dumppAnnotation(cls.getDeclaredAnnotations())
133 , Modifier.toString(cls.getModifiers())
134 , cls.getSimpleName()
135 , supcls == Object.class? "" : "extends " + supcls.getSimpleName()
136 , iStr.length() == 0? "" : " implements " + iStr.toString()
137 , fStr.length() == 0? "" : fStr.toString()
138 , cStr.toString()
139 , mStr.length() == 0? "" : mStr.toString()
140 );
141
142 try {
143 os.write(dc.getBytes());
144 } catch (IOException e) {
145 // TODO Auto-generated catch block
146 e.printStackTrace();
147 }
148 }
149
150 }
151

从以上例子可以得知,利用发射技术,我们可以没有看到源码,仅有Class的情况下,获取所有此Class的信息。

上面我们是使用的getSystemClassLoader,下面我们使用一个自定义的InternetClassLoader,此ClassLoader实现利用http从网上去加载一个Class

 1 package com.sgpro._01_classloader;
2
3 import java.io.BufferedInputStream;
4 import java.io.ByteArrayOutputStream;
5 import java.io.IOException;
6 import java.net.HttpURLConnection;
7 import java.net.URL;
8
9 public class InternetClassLoader extends ClassLoader {
10
11 public String getDomain() {
12 return domain;
13 }
14
15 public void setDomain(String domain) {
16 this.domain = domain;
17 }
18 public InternetClassLoader(String domain) {
19 super();
20 this.domain = domain;
21 }
22 private String domain = "http://192.168.30.170/JavaClassPool/";
23 public InternetClassLoader() {
24 // TODO Auto-generated constructor stub
25 }
26
27 public InternetClassLoader(ClassLoader parent) {
28 super(parent);
29 // TODO Auto-generated constructor stub
30 }
31
32 public byte [] downloadClass(String name) {
33 byte [] data = null ;
34
35 if (name == null ) {
36 throw new IllegalArgumentException("参数有误!类名为空");
37 }
38
39 try {
40 BufferedInputStream bis = null;
41 HttpURLConnection httpUrl = null;
42 URL url = null;
43 byte[] buf = new byte[4096];
44
45 // 建立链接
46
47 String[] packagePath = name.split("\\.");
48 String clsName = packagePath.length > 0? packagePath[packagePath.length - 1] : name;
49
50 url = new URL(domain + clsName + ".class");
51 httpUrl = (HttpURLConnection) url.openConnection();
52 httpUrl.setRequestProperty("Accept", "*/*");
53 // 连接指定的资源
54 httpUrl.connect();
55 // 获取网络输入流
56 bis = new BufferedInputStream(httpUrl.getInputStream());
57 ByteArrayOutputStream bos = new ByteArrayOutputStream();
58
59 int size = 0;
60 while ((size = bis.read(buf)) != -1) {
61 bos.write(buf, 0, size);
62 }
63
64 bos.close();
65 bis.close();
66 httpUrl.disconnect();
67
68 return bos.toByteArray();
69 } catch (IOException e) {
70 e.printStackTrace();
71 }
72
73 return data;
74 }
75 @Override
76 protected Class<?> findClass(String name) throws ClassNotFoundException {
77 Class<?> ret = null;
78 byte[] data = downloadClass(name);
79
80 try {
81 if (data == null) {
82 throw new Exception("字节码无法获取");
83 }
84 ret = defineClass(name, data, 0, data.length);
85 } catch (Exception ex) {
86 System.err.println("找不到类" + name + "详细:" + ex.getMessage());
87 throw new ClassNotFoundException("无法加载远程类", ex);
88 }
89
90 return ret;
91 }
92
93 }
1         loader =   new InternetClassLoader();
2 cls = loader.loadClass("_05_Generic.Country");
3
4 DumpClassInfo.dump(cls, System.out);

我们可以看到,自定义一个ClassLoader需要继承ClassLoader类以及实现findClass的方法。InternetClassLoader中的findClass放,我们按照提供的类名去http服务器中请求获得.class文件,再通过defineClass方法()最终获得Class<?>类。

然后,我们可以使用得到Class<?>类,进行实例化,并执行对象的方法

1         o = cls.newInstance();
2
3 o.getClass().getMethod("setName", String.class).invoke(o, "中国");
4 o.getClass().getMethod("getName").invoke(o);
5
6 System.out.println(o.toString());

三、反射的应用

四、内省机制和javabean

参考资料:

sgpro的java学习例子

http://daizuan.iteye.com/blog/1097105  Class.forName与ClassLoader

反射那些事儿——Java动态装载和反射技术的相关教程结束。

《反射那些事儿——Java动态装载和反射技术.doc》

下载本文的Word格式文档,以方便收藏与打印。