Java反射讲解与案例实战

Java反射讲解与案例实战

       本文章旨在讲解一下java的反射机制和概念,以及利用反射原理制造一个简单的框架(该框架的目的是:能够用一个getBean(String id)方法,创建任何的Bean对象)。
github源码传送门:github反射案例

1. 反射的概念

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

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

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

2. 获取字节码Class对象

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

  • 使用 Class.forName(“全类名”)
  • 使用 类名.class
  • 使用 实例.getClass()
    ·  一个 .class 字节码文件被ClassLoader加载进内存之后,类的所有信息(Class对象)都存在方法区中,一个类的信息只有会一块内存存储,因此,无论利用哪种方法获取到某个类的Class对象,该对象都只指向一块内存。注:方法区是不同于堆的一块内存空间,一般不会被垃圾回收器回收。
    ·  以下是利用三种方法获取到class对象的代码,并判断了它们是否指向同一块内存。
  • 类定义如下
//该区域为所定义的类:所在的包为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
    ...
}

  • 测试代码如下
// 使用了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);
    }

其输出为:

[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
===============================================

3. 使用Class对象

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

//获取成员变量
 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方法可以用来调用该类实例的方法。
以下为这三个对象的测试:

3.1 Field对象的测试

//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测试的输出:

[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
===============================================

3.2 Constructor对象的测试

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

//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测试的输出

[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
===============================================

3.3 Method对象的测试

//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测试的输出

[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
===============================================

反射的案例

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

//beansConfig.xml
<beans>
    <bean id="student" class="com.memforward.Student"/>
    <bean id="person" class="com.memforward.Person"/>
</beans>

下面是新添加的Person类:

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文件

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;
    }
}

下面是测试代码:

    @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();
    }

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

[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反射案例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值