浅析Java Reflection Facility(反射机制)

时间:2022-10-26 21:09:08

前言:这里补一篇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中分层的内存结构

浅析Java Reflection Facility(反射机制)
这里是盗的图,关于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 — 代表构造方法
浅析Java Reflection Facility(反射机制)

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

浅析Java Reflection Facility(反射机制)
Demo下载:https://github.com/herdyouth/ReflectionDemo

四、优缺点

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

1. 优点

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

2. 缺点

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

3. 传说中的经验

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