前言
本文章旨在讲解一下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
| 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"); } ... }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@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); 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
| @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); String sname = (String)nameField.get(stu); 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
| @Test public void testConstructor() throws Exception{ Class clazz = Student.class; Constructor constructor01 = clazz.getConstructor(); Student stu01 = (Student) constructor01.newInstance(); System.out.println("获得了无参构造器:" + stu01); Constructor constructor02 = clazz.getConstructor(String.class,String.class,Integer.class); Student stu02 = (Student) constructor02.newInstance("胡近民", "男", 1000); System.out.println("获得了有参构造器:" + stu02); 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
| @Test public void testMethod() throws Exception{ Class clazz = Student.class; Method md01 = clazz.getMethod("live"); md01.invoke(new Student()); 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