浅析Java Reflection Facility(反射机制)

前言:这里补一篇Java反射的blog,感觉这在Android中和泛型一样经常还是会被用到的。在java web的JDBC技术中也是用的比较多的。

一、概念

1 . 什么是反射

Java让我们在运行时识别对象和类的信息,主要有2种方式:一种是传统的RTTI(运行时类型识别),它假定我们在编译时已经知道了所有的类型信息;另一种是反射机制,它允许我们在运行时发现和使用类的信息,即允许java在运行环境中动态获取类的信息以及动态调用对象的方法。Java反射是Java被视为动态(或准动态)语言的一个关键性质。
反射的机制是在JDK1.5之后引入的,从应用的角度看就是程序在运行时通过class.forname的方式加载\获取相关的类后,再通过method的invoke方法来调用相关的方法,又或者通过设置属性的方式来操作类的局部变量。

2. 反射的应用场景

反射在一些开源框架里用的非常之多,Spring,Struts,Hibnerate,MyBatics,JDBC都有它的影子,还有Android NDK开发时C/C++获取Java层的方法等。反射很灵活,能够使得写的代码,变的大幅精简,常被广泛地应用于那些需要在运行时检测或修改程序行为的程序中。

二、浅析反射原理

1. JVM中分层的内存结构

这里写图片描述
这里是盗的图,关于JVM的原理,自己去查查资料吧,这里只是提提java的反射与JVM是密切相关的。毕竟谈JVM又得是一个长篇大论,不是本篇blog的重点。
看图说话,简单地概述就是:以ClassLoader作为了整个图的入口,主要是考虑到JVM本身第一步就是通过ClassLoader来将所有需要的内容载入到虚拟机中的;可以看到执行引擎(Execution Engine)就是最需要关注的内容,是整个java程序运行的核心部分,至于其余的本地方法区JVM已经自己和系统做好的交互,我们不用太过于关注。放大执行引擎来看,就看到了传说中的最经典的JVM内存的5部分模型。
对于JVM,简单的来说就是:
(1). 每个类都会产生一个对应的Class对象,也就是保存在.class文件;
(2). 所有类都是在对其第一次使用时,动态加载到JVM的,类加载器首先会检查这个类的Class对象是否已被加载过,如果尚未加载,默认的类加载器就会根据类名查找对应的.class文件。当程序创建一个对类的静态成员的引用时,就会加载这个类;
(3). Class对象仅在需要的时候才会加载,static初始化是在类加载时进行的。
为了使用类而做的准备工作一般有以下3个步骤:
(1). 加载:由类加载器完成,找到对应的字节码;
(2). 创建一个Class对象链接:验证类中的字节码,为静态域分配空间初始化;
(3). 如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块。

2. 反射与JVM的关系

前面已经知道在类的加载过程中仅有两种方式,一种是通过new来创建,第二是通过forname来获取到对象(其中第一种方式包含了第二种方式,只是new的时候,除了forname外JVM还做了实例化等一系列工作),而这些对象的来源都是通过ClassLoader读取进入到内存中的。而这些class最基本的信息,也就是.class(字节码文件)中所存储的所有的接口、方法、属性之类的信息都会存储在方法区中,一直不会变化。反射其实就是通过从方法区中读取出了class所有的相关信息后,再显示的调用了实例化的方法(也就是构造器来实例化类)后,模拟了整个类的创建、引用和调用的过程,只是这整个过程更为底层一些(相比直接new多了好几个步骤)。但是总的来说,其实我们正常的使用对象上并没有很大的区别(当然关于private这个权限的处理上,反射做了特殊的处理来绕过了权限的认证)。

三、java 反射的使用

1. 提供的Reflection API

其相关API在包java.lang.reflect中。
Class — 代表类
Field — 代表属性(成员变量)
Method — 代表方法
Constructor — 代表构造方法
这里写图片描述

2. Java反射机制提供的功能

(1). 在运行时判断任意一个对象所属的类;
(2). 在运行时构造任意一个类的对象;
(3). 在运行时构造任意一个类的对象;
(4). 在运行时判断任意一个类所具有的成员变量和方法;
(5). 在运行时调用任一个对象的方法;
(6). 在运行时创建新类对象;
基本过程:首先基本都是要获取类的Class对象,再通过Class对象获取其他的对象。

3. Demo

这里的Demo只是给了几种简单的使用,对于其余的使用方法,直接查查API即可,比如说如何获取构造器等。
假设自定义了一个如下的Person类

public class Person {

    private String id;
    protected String name;
    public int age;

    public Person() {
    }
    public Person(String id, String name, int age) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    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;
    }

    public void show() {
        System.out.println("暂无具体信息");
    }
    public void showDetail(String sex) {
        System.out.println("性别:" + sex);
    }
    public void showDetail(String sex, String nation) {
        System.out.println("性别:" + sex + ", 国籍:" + nation);
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return "Person [id = " + id + ", name = " + name + ", age = " + age + "]";
    }
}

(1). Demo1-获取Class实例

/**
 * 利用反射获取对象的属性、方法等
 * @author herdyouth
 *
 */
public class TestReflection1 {
    public static void main(String[] args) {
        noReflection();
        System.out.println("=======反射之后========");
        testReflection1();
        testReflection2();
    }

    /*不使用反射*/
    public static void noReflection() {
        // 1 获取对象
        Person person = new Person();
        // 2 获取对象的属性
        person.setId("12150203");
        person.setName("FanFF");
        person.setAge(18);
        System.out.println(person);
        // 3 调用对象的方法
        person.showDetail("男");
    }

    /*java.lang.Class为反射的源头,可以返回其运行时类
     * 有了其运行时类就可以知道该类的父类、构造方法、异常的声明、注解、方法、接口等全部结构
     * */
    public static void testReflection1() {
        Person person = new Person();
        Class clazz = person.getClass();// 通过运行时类的对象,返回其运行时类
        System.out.println(clazz);// class com.mycode.reflection.Person
    }

    /*使用反射来获取对象,进而再获取其方法、属性*/
    public static void testReflection2() {
        // 1. 创建clazz对应的运行时Person类的对象
        // Class<Person> clazz = Person.class;
        Class clazz = Person.class;
        try {
            Person person = (Person)clazz.newInstance();
            System.out.println(person);
            // 2 获取对象的public属性
            Field ageField = clazz.getField("age");
            ageField.set(person, 18);
            // 2.1 获取对象的非public属性(private\protected)
            Field idField = clazz.getDeclaredField("id");
            idField.setAccessible(true);
            idField.set(person, "12150203");
            // 2.2 获取对象的属性
            Field nameField = clazz.getDeclaredField("name");
            nameField.setAccessible(true);
            nameField.set(person, "FanFF");
            System.out.println(person);
            // 3. 调用运行是类的指定方法
            // 3.1 调用无参方法
            Method show = clazz.getMethod("show");
            show.invoke(person);
            // 3.2 调用有参方法,可变参数列表确定了方法重载时的指定方法
            Method showDetail1 = clazz.getMethod("showDetail", String.class);
            showDetail1.invoke(person, "男");
            Method showDetail2 = clazz.getMethod("showDetail", String.class, String.class);
            showDetail2.invoke(person, "男", "China");
            System.out.println(person);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return;
        }
    }
}

(2). Demo2-后续操作
上面简单演示了获取Class的实例之后,可以进行的操作。其实我们在获取Class的实例之后可以进行如下的操作:
A. 获取对应的运行时类的完整结构:属性、方法、构造器、内部类、父类、所在的包、异常、注解等;
B. 调用对应的运行时类的指定结构(属性、方法、构造器)
这里再说明一下获取Class实例的四种方法。

/**
 * 通过反射获取Class类实例的4种方法
 * @author herdyouth
 *
 */
public class TestReflection2 {
    public static void main(String[] args) {
        getClassInstance1();
        getClassInstance2();
        getClassInstance3();
        new TestReflection2().getClassInstance1();
    }

    /*1. 通过类.class属性获取*/
    public static void getClassInstance1() {
        Class clazz0 = String.class;
        System.out.println(clazz0);
        Class clazz = Person.class;
        try {
            Person person = (Person) clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println(clazz);
    }

    /*2. 通过运行时类的对象获取*/
    public static void getClassInstance2() {
        Person person = new Person();
        Class clazz = person.getClass();
        System.out.println(clazz);
    }

    /*3. 通过Class.forname()方法获取*/
    public static void getClassInstance3() {
        String className = "com.mycode.reflection1.Person";
        try {
            Class clazz = Class.forName(className);
            System.out.println(clazz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /*4. 通过类的加载器获取*/
    public void getClassInstance4() {
        String className = "com.mycode.reflection1.Person";
        ClassLoader classLoader = this.getClass().getClassLoader();
        try {
            Class clazz = classLoader.loadClass(className);
            System.out.println(clazz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

(3). Demo3-动态代理
静态代理在线程中已经做了说明,即实现Runnable接口的本质。
http://blog.csdn.net/u014294166/article/details/50833134
在反射的应用中看看动态代理的实现,比较详细的说明我已经写在注释中了。

/**
 * 代理角色和真实角色需要实现的共同接口
 * @author herdyouth
 *
 */
public interface CommonInterface {
    void action();
}
/**
 * 真实角色(被代理的角色)
 * @author herdyouth
 *
 */
public class RealityRole implements CommonInterface {

    @Override
    public void action() {
        System.out.println("真实角色!");
    }

}
public class MyInvocationHandler implements InvocationHandler {

    Object obj = null;// 实现了接口的真实角色的对象的声明

    /*1. 实例化真实角色;2. 返回代理角色的对象*/
    public Object binder(Object obj) {
        this.obj = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
    }

    /*当通过代理角色的对象发起对被重写的方法的调用时,
     * 都会转换为对如下的invoke方法的调用*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object returnValue = method.invoke(obj, args);
        return returnValue;
    }

}
/**
 * 反射的应用:动态代理
 * @author herdyouth
 *
 */
public class TestReflection3 {
    public static void main(String[] args) {
        // 1. 真实角色的对象
        RealityRole realRole = new RealityRole();
        // 2. 创建实现了InvocationHandler接口的类的对象
        MyInvocationHandler invocationHandler = new MyInvocationHandler();
        // 3. 调用关联的方法,动态地返回一个代理类的对象
        Object obj = invocationHandler.binder(realRole);
        CommonInterface com = (CommonInterface)obj;//此时com就是代理类的对象
        // 4. 调用相应的方法,进而调用的invoke()
        com.action();
    }
}

这里写图片描述
Demo下载:https://github.com/herdyouth/ReflectionDemo

四、优缺点

最后谈谈反射的优缺点吧。

1. 优点

A:能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性;
B:与Java动态编译相结合,可以实现无比强大的功能。

2. 缺点

A:使用反射的性能较低;
B:使用反射相对来说不安全;
C:破坏了类的封装性,可以通过反射获取这个类的私有方法和属性

3. 传说中的经验

A:如果一个功能可以不用反射就不用,只有语言基础非常扎实的开发者才应该使用它。
B: 反射无疑为我们提供了更多更丰富的设计手段和实现技巧,但是不可避免的还是部分的“破坏”了整个Java最初的设计理念:更透明更安全。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值