0%

Java反射讲解与案例实战

前言

本文章旨在讲解一下java的反射机制和概念,以及利用反射原理制造一个简单的框架
框架内容:能够用一个getBean(String id)方法,创建任何的Bean对象)。

github源码传送门:github反射案例   

反射的概念

· 反射是框架设计的灵魂,反射的机制是将一个类的各个组成部分封装成其他的对象保存在Class类对象中。
· Java的类在计算机中会经历三个阶段:

  • 源代码阶段:我们编写的某个类经过javac指令编译后生成了.class字节码文件,这些文件存储在硬盘上
  • 类对象阶段:类加载器ClassLoader将字节码文件加载进内存中,由一个Class类对象保存这个类各个组成部分的所有信息:
    • 该类的成员变量封装成 Field[] 对象
    • 该类的构造方法封装成 Constructor[] 对象
    • 该类的成员方法封装成 Method[] 对象
  • Runtime运行时阶段:通过类对象创建该类的实例并使用

· 反射的优势:可以给代码解耦,提高程序的可扩展性。

获取字节码Class对象

·  由上一节可知,在运行阶段创建类的实例是需要通过Class对象的,要利用反射机制必然要使用这个对象,而获取这个对象有三种方式:

  • 使用 Class.forName(“全类名”)
  • 使用 类名.class
  • 使用 实例.getClass()
    ·  一个 .class 字节码文件被ClassLoader加载进内存之后,类的所有信息(Class对象)都存在方法区中,一个类的信息只有会一块内存存储,因此,无论利用哪种方法获取到某个类的Class对象,该对象都只指向一块内存。注:方法区是不同于堆的一块内存空间,一般不会被垃圾回收器回收。
    ·  以下是利用三种方法获取到class对象的代码,并判断了它们是否指向同一块内存。
  • 类定义如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//该区域为所定义的类:所在的包为com.memoforward
package com.memforward;

public class Student {
//成员变量
public String name;
public String gender;
private Integer age;
//构造函数
public Student(){
this.name = "江锦平";
this.gender = "男";
this.age = 999;
}
public Student(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//成员方法
public void live(){
System.out.println("长生不老+1+1+1....");
}
public void live(String num){
System.out.println("寿命延长:"+num+"s");
}
//toString
...
}
  • 测试代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用了testNG进行了测试

@Test
public void getClassTest() throws ClassNotFoundException {
//方法一
Class clazz1 = Class.forName("com.memforward.Student");
System.out.println("clazz1:" + clazz1);
//方法二
Class clazz2 = Student.class;
System.out.println("clazz2:" + clazz2);
//方法三
Class clazz3 = new Student().getClass();
System.out.println("clazz3:" + clazz3);
//判断是否指向同一个对象 ,都返回true
System.out.println(clazz1 == clazz2);
System.out.println(clazz2 == clazz3);
System.out.println(clazz1 == clazz3);
}

其输出为:
  

1
2
3
4
5
6
7
8
9
10
11
12
[TestNG] Running:
C:\Users\handsomestar\.IntelliJIdea2019.1\system\temp-testng-customsuite.xml
clazz1:class com.memforward.Student
clazz2:class com.memforward.Student
clazz3:class com.memforward.Student
true
true
true
===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

使用Class对象

·  Class对象中封装了一个类的成员变量、构造方法和成员方法。一般情况下,我们需要获得和使用的就是这三个部分的值,诚然一个Class对象中有很多的方法,但是我们最常用的只有以下几种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//获取成员变量
Field[] getFields()
Field[] getDeclaredFields()
Field getDeclaredField(String name)
Field getDeclaredField(String name)
//获取构造方法
Constructor<?>[] getConstructors()
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
//获取成员方法
Method[] getMethods()
Method[] getDeclaredMethods()
Method getMethod(String name, Class<?>... parameterTypes)
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
//获取类名
String getName()
//获取类加载器
ClassLoader getClassLoader()

· 看上面的方法可知,Class对象既可以得到该类的对应的组成部分的数组,也可以定向找到某项属性。声明了Declared的方法则可以得到该类的私有声明的信息。
· 该类的组成部分被封装在了Field、Constructor和Method对象,很显然这些对象有各自的用途:Field可以用来获取和设置某个实例成员变量的值;Constructor可以用来创建该类的实例对象;Method方法可以用来调用该类实例的方法。
以下为这三个对象的测试:

Field对象的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//Field对象的测试
@Test
public void testField() throws Exception {
Class clazz = Student.class;
System.out.println("-----------getFields------------");
Field[] fields01 = clazz.getFields();
for (Field field: fields01) System.out.println(field);
System.out.println("-----------getDeclaredFileds----");
Field[] fields02 = clazz.getDeclaredFields();
for (Field field:fields02) System.out.println(field);
System.out.println("--------------------------------");
Field nameField = clazz.getField("name");
Student stu = new Student();
System.out.println("原来的stu:" + stu);
//通过Filed获得实例的值
String sname = (String)nameField.get(stu);
//通过Field设置实例的值
nameField.set(stu, "胡近民");
//如果要获取私有的值
Field ageField = clazz.getDeclaredField("age");
//操作私有的值必须要开启权限,暴力反射
ageField.setAccessible(true);
ageField.set(stu,1000);
System.out.println("修改后的stu:" + stu);
}

Field测试的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[TestNG] Running:
C:\Users\handsomestar\.IntelliJIdea2019.1\system\temp-testng-customsuite.xml
-----------getFields------------
public java.lang.String com.memforward.Student.name
public java.lang.String com.memforward.Student.gender
-----------getDeclaredFileds---- //这里可以看到private属性的age也被获取到了
public java.lang.String com.memforward.Student.name
public java.lang.String com.memforward.Student.gender
private java.lang.Integer com.memforward.Student.age
--------------------------------
原来的stu:Student{name='江锦平', gender='男', age=999}
修改后的stu:Student{name='胡近民', gender='男', age=1000}
===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

Constructor对象的测试

小贴士:如果该类的构造是私有的,也是可以利用反射得到其构造器创建实例对象的,方法如Field,此测试就不写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Constructor对象的测试
@Test
public void testConstructor() throws Exception{
Class clazz = Student.class;
//获取Constructor对象
//1.1返回了无参构造
Constructor constructor01 = clazz.getConstructor();
Student stu01 = (Student) constructor01.newInstance();
System.out.println("获得了无参构造器:" + stu01);
//1.2 返回有参构造
Constructor constructor02 = clazz.getConstructor(String.class,String.class,Integer.class);
Student stu02 = (Student) constructor02.newInstance("胡近民", "男", 1000);
System.out.println("获得了有参构造器:" + stu02);
//无参构造器生成实例可以直接由Class对象获得
Student stu03 = (Student) clazz.newInstance();
System.out.println("Class对象直接生成的实例:" + stu03);
}

Constructor测试的输出

1
2
3
4
5
6
7
8
9
[TestNG] Running:
C:\Users\handsomestar\.IntelliJIdea2019.1\system\temp-testng-customsuite.xml
获得了无参构造器:Student{name='江锦平', gender='男', age=999}
获得了有参构造器:Student{name='胡近民', gender='男', age=1000}
Class对象直接生成的实例:Student{name='江锦平', gender='男', age=999}
===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

Method对象的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
//Method对象的测试
@Test
public void testMethod() throws Exception{
Class clazz = Student.class;
//获得方法
//1.1获得无参方法
Method md01 = clazz.getMethod("live");
//调用方法(需要有实例才可以执行该方法)
md01.invoke(new Student());
//1.2获得有参的方法
Method md02 = clazz.getMethod("live", String.class);
md02.invoke(new Student(),"1");
}

Method测试的输出

1
2
3
4
5
6
7
8
[TestNG] Running:
C:\Users\handsomestar\.IntelliJIdea2019.1\system\temp-testng-customsuite.xml
长生不老+1+1+1....
寿命延长:1s
===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

反射的案例

·  需求:实现一个“框架类”,该类可以创建任意类的对象,并能使用该类对象。
·  解决方案:此“框架类”通过加载配置文件,得到某个具体类的全类名,通过反射创建该类的实例即可。
以下是配置文件:

1
2
3
4
5
//beansConfig.xml
<beans>
<bean id="student" class="com.memforward.Student"/>
<bean id="person" class="com.memforward.Person"/>
</beans>

下面是新添加的Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.memforward;

public class Person {
private String name;
private Integer age;
public Person(){
this.name = "习";
this.age = 999;
}
public void doSomething(){
System.out.println(name + ":修身治国齐家平天下....");
}

public void saySomething(){
System.out.println(name + ":TW必将光复,HK属于CN!");
}
}

以下是框架ReflectDemo,利用Dom4j读取XML文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.memforward;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

public class ReflectDemo {
public static Document document = null;
public static void loadProperties(String config) {
SAXReader reader = new SAXReader();
try {
Document document = reader.read(new File("src/main/resources/beansConfig.xml"));
ReflectDemo.document = document;
} catch (DocumentException e) {
e.printStackTrace();
}
}

public Object getBean(String id){
Element bean = null;
String className = null;
Element root = document.getRootElement();
List<Element> beans = root.elements("bean");
//找到配置文件里的节点
for(Element e : beans){
if(e.attribute("id").getValue().equals(id)){
bean = e;
break;
}
}
if(bean == null) throw new RuntimeException("没有你指定的类:" + id);
className = bean.attribute("class").getValue();
try {
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

下面是测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testReflectDemo(){
ReflectDemo.loadProperties("beansConfig.xml");
ReflectDemo rd = new ReflectDemo();
Student stu = (Student) rd.getBean("student");
//调用方法
stu.live();
Person person = (Person) rd.getBean("person");
//调用方法
person.doSomething();
person.saySomething();
}

下面是测试代码的输出,可见这个框架再加载了配置文件后,可以很方便的创建你所配置的任意类的对象,并使用该对象的方法:

1
2
3
4
5
6
7
8
9
[TestNG] Running:
C:\Users\handsomestar\.IntelliJIdea2019.1\system\temp-testng-customsuite.xml
长生不老+1+1+1....
习:修身治国齐家平天下....
习:TW必将光复,HK属于CN!
===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

总结

·  以上就是整个反射的概念以及最常用的几个方法,可见反射其实也不是难,但是其思想着实是一大飞跃。几乎所有的代码我都贴在了博客上,如果有小伙伴不想复制粘贴,可以去我的github上下载源码(所用的IDE是Intellj idea):github反射案例

交流

请联系邮箱:chenxingyu@bupt.edu.cn