Java反射机制

-------------------siwuxie095

   

   

   

   

   

   

   

   

Java 语言允许通过程序化的方式间接对 Class 的对象实例操作,

Class 文件由类装载器装载后,在 JVM 中将形成一份描述 Class

结构的元信息对象,通过该元信息对象可以获知 Class 的结构信

息,如:构造函数、属性和方法

   

   

· 示例讲解:下面通过实例探访 Java 反射机制

· ClassLoader:介绍 ClassLoader 的工作机制以及重要方法

· Java 反射机制:深入讲解 Java 的反射机制

· IoC 的关系:通过实例介绍 Java 反射机制与 Spring IoC 之间的关系

   

   

   

   

示例讲解

   

   

编写一个简单实例,通过比较 传统方法 反射机制 创建类实例的不同,

来介绍 Java 反射机制的原理

   

首先创建 Car 类:

   

package com.siwuxie095.reflect;

   

public class Car {

private String brand;

private String color;

private int maxSpeed;

//默认构造函数

public Car() {

System.out.println("init car");

}

//带参数的构造函数

public Car(String brand,String color,int maxSpeed){

this.brand=brand;

this.color=color;

this.maxSpeed=maxSpeed;

}

//不带参数的方法

public void introduce() {

System.out.println("brand"+brand+" color"+color+" maxSpeed"+maxSpeed);

}

   

public String getBrand() {

return brand;

}

   

public void setBrand(String brand) {

this.brand = brand;

}

   

public String getColor() {

return color;

}

   

public void setColor(String color) {

this.color = color;

}

   

public int getMaxSpeed() {

return maxSpeed;

}

   

public void setMaxSpeed(int maxSpeed) {

this.maxSpeed = maxSpeed;

}

}

   

   

传统调用方法,使用构造函数设置属性 set 方法设置属性

   

1)构造函数:

   

Car car = new Car("红旗轿车", "黑色", "180");

   

2set 方法:

   

Car car = new Car();

car.setBrand("红旗轿车");

   

以上两种方法都采用传统方式直接调用目标类的方法来创建类的实例

以及为类的实例设置相关的属性

   

   

而 Java 反射机制,以一种更加通用的方式间接地操作目标类

   

再创建 ReflectTest 类:

   

package com.siwuxie095.reflect;

   

import java.lang.reflect.Constructor;

import java.lang.reflect.Method;

   

public class ReflectTest {

   

/**

* initByDefaultConst() 的主要作用:

* 通过获取类的默认构造器对象,来实例化 Car

* 并设置 Car 实例的相关属性

*/

public static Car initByDefaultConst() throws Throwable {

/**

* 1.通过类装载器获取Car类对象:

* 获取当前线程的ClassLoader

* 通过指定类的完全限定名来装载Car类对应的反射实例

*/

ClassLoader loader = Thread.currentThread().getContextClassLoader();

Class clazz = loader.loadClass("com.siwuxie095.reflect.Car");

   

/**

* 2.获取类的默认构造器对象并实例化Car

* 通过Car的反射类对象来获取Car的构造函数对象,

* 通过构造函数对象的newInstance()方法来实例化Car对象,

* 其效果等同于 new Car()

*/

Constructor cons = clazz.getDeclaredConstructor((Class[]) null);

Car car = (Car) cons.newInstance();

   

//

/**

* 3.通过反射方法设置属性:

* 通过Car的反射类对象的getMethod()方法来获取属性的set方法对象,

* 第一个参数是目标类的方法名,第二个参数是方法入参的对象类型

*

* 获取方法反射对象之后,即可通过invoke()方法来调用目标类的方法

* 第一个参数是操作的目标类对象的实例,第二个参数是目标方法的入参

*

*

* 奔驰、黑色、200 这些信息即是通过反射方法操作目标类的元信息

*

* 如果将这些信息以一个配置文件的方式来提供,就可以使用Java语言

* 的反射功能来编写一段通用的代码,利用这段通用的代码就可以对类

* 似于Car的类进行实例化以及功能调用的操作

*/

Method setBrand = clazz.getMethod("setBrand", String.class);

setBrand.invoke(car, "奔驰");

Method setColor = clazz.getMethod("setColor", String.class);

setColor.invoke(car, "黑色");

Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class);

setMaxSpeed.invoke(car, 200);

return car;

}

/**

* initByParamConst() 的主要作用:

* 通过获取类的带有参数的构造器对象,来实例化 Car

* 并设置 Car 实例的相关属性

*/

public static Car initByParamConst() throws Throwable {

// 1.通过类装载器获取Car类对象

ClassLoader loader = Thread.currentThread().getContextClassLoader();

Class clazz = loader.loadClass("com.siwuxie095.reflect.Car");

   

// 2.获取类的带有参数的构造器对象

Constructor cons = clazz.getDeclaredConstructor(

new Class[] { String.class, String.class, int.class });

   

// 3.使参数的构造器对象实例化Car

Car car = (Car) cons.newInstance(new Object[] { "宝马", "红色", 180 });

return car;

}

   

public static void main(String[] args) throws Throwable {

Car car1 = initByDefaultConst();

Car car2 = initByParamConst();

car1.introduce();

car2.introduce();

}

   

}

   

   

运行一览:

   

   

   

通过以上实例,说明完全可以通过编程的方式来调用类的相关功能,

这和直接通过构造函数和方法来调用类的功能是等效的,只不过前

者是间接调用,而后者是直接调用

   

ReflectTest 类中,使用了几个重要的反射类:ClassLoader、Class、

Constructor 和 Method,通过这些反射类就可以间接调用目标类的各项

功能

   

   

   

   

   

   

ClassLoader

   

   

ClassLoader,即 类装载器 或 类加载器

   

ClassLoader 就是寻找类的字节码文件并构造出类在 JVM 内部表示的对象

组件,主要工作由 ClassLoader 及其子类负责,ClassLoader 是一个重要

Java 运行时系统组件,它负责在运行时查找和装入 Class 字节码文件

   

   

   

ClassLoader 的工作机制:

   

1)装载:查找和导入 Class 文件

   

2)链接:执行校验、准备和解析步骤

       1)校验:检查载入 Class 文件数据的正确性

       2)准备:给类的静态变量分配存储空间

       3)解析:将符号引用转变成直接引用

   

(3)初始化:对类的静态变量、静态代码块执行初始化工作

   

   

   

JVM 在运行时会产生三个 ClassLoader:

   

1)根装载器

2)ExtClassLoader(扩展类装载器)

3)AppClassLoader(系统类装载器)

   

其中,根装载器不是 ClassLoader 的子类,它使用 C++ 编写,

因此在 Java 中看不到它,根装载器负责装载 JRE 的核心类库,

如:rt.jar,charsets.jar

   

ExtClassLoader AppClassLoader 都是 ClassLoader 的子类

   

其中 ExtClassLoader 负责装载 JRE 扩展目录 ext 中的 JAR 类包,

而 AppClassLoader 负责装载 ClassPath 路径下的类包

   

这三个类装载器之间存在父子层级关系,根装载器是 ExtClassLoader 的父装载器,

ExtClassLoader AppClassLoader 的父装载器

   

默认情况下,使用 AppClassLoader 装载应用程序的类

   

   

如下:创建 ClassLoaderTest 类

   

package com.siwuxie095.reflect;

   

public class ClassLoaderTest {

public static void main(String[] args) {

/**

* 从当前线程中获取ClassLoader类装载器对象

*/

ClassLoader loader = Thread.currentThread().getContextClassLoader();

//打印类装载器的相关信息

System.out.println("current loader:"+loader);

//打印当前类装载器的父装载器

System.out.println("parent loader:"+loader.getParent());

//打印当前类装载器的祖父装载器

System.out.println("grandparent loader:"+loader.getParent().getParent());

}

}

   

   

运行一览:

   

   

   

当前装载器是 AppClassLoader,父装载器是 ExtClassLoader,

祖父装载器是 根装载器,因为在 Java 中无法获得它的句柄,即

Java 中访问不到,所以直接返回 null

   

   

JVM 装载类时使用 "全盘负责委托机制"

   

"全盘负责",是指当一个 Classloader 装载一个类时,这个类所依赖

和引用的类也由这个 ClassLoader 负责载入,除非显式地使用另一个

ClassLoader 载入

   

"委托机制",是指先委托父装载器寻找目标类,只有在找不到的情况下,

才从自己的类路径中查找并装载目标类。这一点是从安全角度来考虑的

   

试想:如果有人编写了一个恶意的基础类(如:java.lang.String),

并装载到 JVM 中将会引起多么可怕的后果

   

但是由于有了 "全盘负责委托机制",就永远是由根装载器来装载的,

这样就避免了恶意的基础类被装载到 JVM 中

   

   

   

ClassLoader 的重要方法

   

Java 中,ClassLoader 是一个抽象类,位于 java.lang 包中

   

下面对其重要的一些接口方法进行介绍:

   

1)Class loadClass(String name)

   

name 参数指定类装载器需要装载类的名字,必须使用全限定类名,

该方法有一个重载方法 loadClass(String name ,boolean resolve),

resolve 参数告诉类装载器是否需要解析该类。在初始化类之前,应

考虑进行类解析的工作,但并不是所有的类都需要解析,如果 JVM

只需要知道该类是否存在 或找出该类的超类,就不需要进行解析

   

   

2)Class defineClass(String name, byte[] b, int off,int len)

   

将类文件的字节数组转换成 JVM 内部的 java.lang.Class 对象,字节数组

可以从本地文件系统、远程网络获取。name 为字节数组对应的全限定类名

   

   

3)Class findSystemClass(String name)

   

从本地文件系统载入 Class 文件,如果本地文件系统不存在该 Class 文件,将

抛出ClassNotFoundException 异常。该方法是 JVM 默认使用的装载机制

   

   

4)Class findLoadedClass(String name)

   

调用该方法来查看 ClassLoader 是否已装入某个类。如果已装入,

那么返回 java.lang.Class 对象,否则返回 null。如果强行装载已

存在的类,将会抛出链接错误

   

   

5)ClassLoader getParent()

   

获取类装载器的父装载器,除根装载器外,所有的类装载器都 有且仅有

一个父装载器,ExtClassLoader 的父装载器是根装载器,因为根装载器

Java 编写,所以无法获得,将返回 null

   

   

   

   

除了 JVM 默认的三个 ClassLoader 以外,可以编写自己的第三方类装载器,

以实现一些特殊的需求

   

类文件被装载并解析后,在 JVM 内将拥有一个对应的 java.lang.Class

描述对象,该类的实例拥有指向这个类描述对象的引用,而类描述对象又

拥有指向关联 ClassLoader 的引用

   

每一个类在 JVM 中都拥有一个对应的 java.lang.Class 对象,它提供了类

结构信息的描述

   

数组、枚举、注解 以及 基本 Java 类型(如:int、double),甚至 void

都拥有对应的 Class 对象。Class 没有 public 的构造方法。Class 对象是

在装载类时由 JVM 通过调用类装载器中的 defineClass() 方法自动构造的

   

   

   

   

   

   

Java 反射机制

   

   

Class 反射对象描述类语义结构,可以从 Class 对象中获取构造函数、

成员变量、方法等类元素的反射对象,并以编程的方式通过这些反射

对象对目标类对象进行操作

   

   

这些反射对象类在 java.lang.reflect 包中定义,下面是最主要的三个反射类:

   

1Constructor:类的构造函数反射类

   

通过 getConstructors() 方法可以获得类的所有构造函数反射对象数组

   

Constructor 的一个主要方法是 newInstance(Object[] initargs),通过

该方法可以创建一个对象类的实例,相当于 new 关键字

   

在JDK 5.0 中该方法演化为更为灵活的形式:newInstance (Object... initargs)

   

   

   

2Method:类的方法反射类

   

通过 getDeclaredMethods() 方法可以获取类的所有方法反射对象数组

   

Method 最主要的方法是 invoke(Object obj, Object[] args),

obj 表示操作的目标对象,args 为方法入参

   

JDK 5.0 中,该方法的形式调整为:invoke(Object obj, Object... args)

   

此外,Method 还有很多用于获取类方法更多信息的方法:

 1)Class getReturnType():获取方法的返回值类型
 2)Class[] getParameterTypes():获取方法的入参类型数组

 3)Class[] getExceptionTypes():获取方法的异常类型数组

 4)Annotation[][] getParameterAnnotations():获取方法的注解信息,JDK 5.0 中的新方法

   

   

3Field:类的成员变量反射类

   

通过 getDeclaredFields() 方法可以获取类的成员变量反射对象数组

   

通过 getDeclaredField(String name) 则可获取某个特定名称的成员变量反射对象

   

Field 最主要的方法是 set(Object obj, Object value),obj 表示操作的

目标对象,通过 value 为目标对象的成员变量设置值

   

如果成员变量为基础类型,用户可以使用 Field 类中提供的带类型名的值设置方法,

如:setBoolean(Object obj, boolean value)、setInt(Object obj, int value) …

   

   

   

此外,Java还为包提供了 Package 反射类,在 JDK 5.0 中还为注解提供了

AnnotatedElement 反射类

   

总之,Java 的反射体系保证了可以通过程序化的方式访问目标类中

所有的元素,对于 private protected 的成员变量和方法,只要

JVM 的安全机制允许,也可以通过反射进行调用

   

在访问 private、protected 成员变量和方法时,必须通过

setAccessible(boolean access) 方法取消 Java 语言检查,

否则将抛出 IllegalAccessException

   

如果 JVM 的安全管理器设置了相应的安全机制,调用该方法

将抛出 SecurityException

   

   

   

   

   

   

IoC 的关系

   

   

Spring 中,通过 IoC 可以将实现类、参数信息等配置在其对应的

配置文件中,当需要更改实现类或参数信息时,只需要修改配置文件

即可,还可以对某对象所需要的其它对象进行注入,这种注入都是在

配置文件中做的

   

Spring IoC 的实现原理利用的就是 Java 的反射机制,Spring 还充当了

工厂的角色,不需要自己建立工厂类,Spring 的工厂类会帮我们完成配置

文件的读取、利用反射机制注入对象等工作,我们直接通过 bean 的名称来

获取对应的对象即可

   

   

为了更清晰的认识 Spring IoC 的实现原理,看如下实例:

   

创建一个 JavaBean、一个 Spring 的 Bean 工厂类

一个 config.xml 配置文件,并需要使用 dom4j

的库文件

   

dom4j 的下载链接:http://www.dom4j.org/dom4j-1.6.1/

   

   

工程结构目录如下:

   

   

   

   

JavaBean.java:

   

package com.siwuxie095.reflect;

   

public class JavaBean {

   

private String userName;

private String password;

   

public String getPassword() {

return password;

}

   

public String getUserName() {

return userName;

}

   

public void setUserName(String userName) {

this.userName = userName;

}

   

public void setPassword(String password) {

this.password = password;

}

}

   

   

   

BeanFactory.java(主类):

   

package com.siwuxie095.reflect;

   

import java.io.InputStream;

import java.lang.reflect.Method;

import java.util.HashMap;

import java.util.Iterator;

import java.util.Map;

   

import org.dom4j.Attribute;

import org.dom4j.Document;

import org.dom4j.Element;

import org.dom4j.io.SAXReader;

   

public class BeanFactory {

   

private Map<String, Object> beanMap = new HashMap<String, Object>();

   

/**

* bean工厂的初始化.

* init()通过指定的XML文件来给对象注入相关属性

*

* @param xml xml配置文件

*/

public void init(String xml) {

try {

//1.创建读取配置文件的reader对象

SAXReader reader = new SAXReader();

 

//2.获取当前线程中的类装载器对象

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

 

//3.class目录下获取指定的xml文件

InputStream ins = classLoader.getResourceAsStream(xml);

Document doc = reader.read(ins);

Element root = doc.getRootElement();

Element foo;

 

//4.遍历xml文件当中的Bean实例

for (Iterator i = root.elementIterator("bean"); i.hasNext();) {

foo = (Element) i.next();

 

//5.针对每个一个Bean实例,获取bean的属性idclass

Attribute id = foo.attribute("id");

Attribute cls = foo.attribute("class");

 

//6.利用Java反射机制,通过class的名称获取Class对象

Class bean = Class.forName(cls.getText());

//7.获取对应class的信息

java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);

//8.获取其属性描述

java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();

   

//9.创建一个对象,并在接下来的代码中为对象的属性赋值

Object obj = bean.newInstance();

 

//10.遍历该beanproperty属性

for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {

Element foo2 = (Element) ite.next();

 

//11.获取该propertyname属性

Attribute name = foo2.attribute("name");

String value = null;

 

//12.获取该property的子元素value的值

for (Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();)

{

Element node = (Element) ite1.next();

value = node.getText();

break;

}

 

//13.利用Java的反射机制调用对象的某个set方法,并将值设置进去

for (int k = 0; k < pd.length; k++) {

if (pd[k].getName().equalsIgnoreCase(name.getText()))

{

Method mSet = null;

mSet = pd[k].getWriteMethod();

mSet.invoke(obj, value);

}

}

}

   

//14.将对象放入beanMap中,其中keyid值,value为对象obj

beanMap.put(id.getText(), obj);

}

} catch (Exception e) {

System.out.println(e.toString());

}

}

   

/**

* 通过beanid获取bean的对象.

*

* @param beanName

* beanid

* @return 返回对应对象

*/

public Object getBean(String beanName) {

Object obj = beanMap.get(beanName);

return obj;

}

   

/**

* 测试方法.

*

* @param args

*/

public static void main(String[] args) {

//创建工程类实例

BeanFactory factory = new BeanFactory();

//读取配置文件,并将配置文件中的属性信息注入到相关的类对象中

factory.init("config/config.xml");

//从工厂中获取JavaBean实例

JavaBean javaBean = (JavaBean) factory.getBean("javaBean");

//虽然在main()方法中没有对属性赋值,但属性值已经被注入

System.out.println("userName=" + javaBean.getUserName());

System.out.println("password=" + javaBean.getPassword());

 

}

}

   

   

运行一览:

   

   

   

   

   

参考链接:

参考链接1参考链接2参考链接3参考链接4参考链接5

   

   

   

   

   

   

【made by siwuxie095】

原文地址:https://www.cnblogs.com/siwuxie095/p/6739225.html