原创文章,转载请注明出处。
Spring 之所以能够实例化并装配好程序所用到的 JavaBean,都归功于 Java 的反射机制。Java 反射机制的定义为:Java 语言允许通过程序化的方式间接对 Class 的对象实例操作,Class 文件由类装载器装载后,在 JVM 中将形成一份描述 Class 结构的信息对象,通过该元信息对象可以获知 Class 的结构信息,如构造函数、属性和方法等。下面将用一个简单示例,通过比较传统方法以及反射机制创建类实例的不同,来引入 Java 反射机制的原理。
一、简单示例演示 Java 反射机制的应用。
1、首先创建一个 Car 类,它包含三个属性,两个构造函数和一个方法。
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;
}
}
2、传统的调用方法是使用构造函数设置属性或者 set 方法设置属性。
(1)构造函数的方法:Car car=new Car("奔驰","white","180");
(2)Set 方法:Car car=new Car(); car.setBrand("奔驰");
3、使用 Java 反射机制,以一种更通用的方式间接地操作目标类。
public class ReflectTest {
public static Car initByDefaultConst() throws Throwable {
// 1、通过类装载器获取 Car 对象
ClassLoader loader=Thread.currentThread().getContextClassLoader();
Class clazz=loader.loadClass("com.springstudy.reflect.Car"); // 获取全限定类来装载对应的反射实例
// 2、获取类的默认构造器对象并实例化 Car
Constructor cons=clazz.getDeclaredConstructor((Class[])null); // 通过Car的反射类对象来获取它的构造类对象
Car car=(Car)cons.newInstance(); // 实例化Car对象,其效果等同于new Car()
// 3、通过反射方法设置属性
Method setBrand=clazz.getMethod("setBrand",String.class); // 第一个参数是目标类的方法,第二个是方法入参的对象类型
setBrand.invoke(car,"奔驰"); // 获取方法反射对象之后,使用invoke()来调用目标类的方法,第一个参数是操作的目标类对象的实例,第二个则是目标方法的入参
Method setColor=clazz.getMethod("setColor",String.class);
setColor.invoke(car,"white");
Method setMaxSpeed=clazz/getMethod("setMaxSpeed",int.class);
setMaxSpeed.invoke(car,"180");
return car;
}
public static Car initByParamConst() throw Throwable {
// 1、通过类装载器获取 Car 类对象
ClassLoader loader=Thread.currentThread().getContextClassLoader();
Class clazz=loader.loadClass("com.springstudy.reflect.Car");
// 2、获取类的带有参数的构造器对象
Constructor cons=clazz.getDeclaredConstructor(new Class[]{String.class,String.class,int.class});
// 3、使参数的构造器对象实例化 Car
Car car=(Car)cons.new Instance(new Object[]{"特斯拉","black",200});
return car;
}
public static void main(String[] args) {
Car car1=initByDefaultConst();
Car car2=initByParamConst();
car1.introduce();
car2.introduce();
}
}
类装载器就是寻找类的字节码文件并构造出类在 JVM 内部表示的对象组件,主要工作由 ClassLoader 及其子类负责,ClassLoader 是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入 Class 字节码文件。
1、类装载器的主要工作机制:
在 Java 中,类装载器把一个类装入到 Java 虚拟机当中需要经过一下的步骤:
(1)装载:查找和导入 Class 文件。
(2)链接:执行校验、准备和解析步骤。其中校验主要是检查载入 Class 文件的正确性,而准备工作就是给类的静态变量来分配存储空间,而解析就是将符号引用转变成直接引用。
(3)初始化:对类的静态变量、静态代码块执行初始化工作。
JVM 在运行时会产生 3 个 ClassLoader,根装载器、EXTclassLoader(扩展类装载器)、和 AppClassLoader(系统类装载器)。其中根装载器不是 ClassLoader 的子类,它是使用 C++ 编写的,所以我们在 Java 中看不到。根装载器来负责装载 JRE 的核心类库。而 ExtClassLoader 和 AppClassLoader 都是 ClassLoader 的子类,其中 ExtClassLoader 负责装载 JRE 扩展目录 ext 中的 JAR 类包,AppClassLoader 则负责装载 Classpath 路径下的类包。它们存在父子关系,即根装载器是 ExtClassLoader 的父装载器,而 ExtClassLoader 是 AppClassLoader 的父装载器。在默认情况下,使用 AppClassLoader 装载应用程序的类。
public class ClassLoaderTest {
public static void main(String[] args) {
// 从当前线程中获取当前线程的类装载器对象
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、null(根装载器在 Java 中找不到,所以返回 null 值)。
2、类装载器的重要方法:
(1)Class loadClass(String name):其中 name 参数是指定类装载器需要装载的类的名字,在这里需要注意的是我们必须使用全限定类名。
(2)Class defineClass(String name,byte[] b,int off,int len):这个方法是将类文件的字节数组转换成 java 虚拟机内部的 java.lang.Class 对象。字节数组可以从本地文件系统、远程网络进行获取。而 name 为字节数组对应的全限定类名。
(3)Class findSystemClass(String name):主要是从本地文件系统中来载入 class 文件。如果本地文件系统中不存在该 class 文件,那么它将会抛出 ClassNotFoundException 这个异常。这个方法是 Java 虚拟机默认使用的装载机制。
(4)Class findLoadedClass(String name): 调用该方法来查看 ClassLoader 是否已装入到某个类当中。
(5)ClassLoader getParent():获取类装载器的父装载器。除了根装载器外,所有的类装载器都有且只有一个父装载器。
三、反射类
Class 反射对象描述类语义结构,可以从 Class 对象中获取构造函数,成员变量,方法等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射类在 java.reflect 包中定义,下面是最主要的三个反射类:
1、Constructor:
类的构造函数反射类,通过 getConstructors() 方法可以获得类的所有构造函数反射对象数组。Constructor 的一个主要方法是 newInstance(),通过该方法可以创建一个对象类的实例,相当于 new 关键字。
2、Method:
类方法反射类,通过 getDeclaredMethods() 方法可以获取类的所有方法的反射对象数组。Method 的主要方法是 invoke() 方法。还有一些常用的获取类信息的方法:
(1)Class getReturnType():获取方法的返回值类型。
(2)Class[] getParameterTypes():获取方法的入参类型数组。
(3)Class[] getExceptionTypes():获取方法的异常类型数组。
(4)Annotation[][] getParameterAnnotations():获取方法的注解信息。
3、Field:
类的成员变量的反射类,通过 getDeclaredFields() 方法可以获取类的成员变量的反射对象数组。通过这个方法可以获取某个特定名称的成员变量反射对象。
* 此外,java 还为“包”提供了 package 反射类。
四、与 IoC 关系
在 Spring 中,通过 IoC 可以将实现类、参数信息等配置在其对应的配置文件中,那么当需要更改实现类或参数信息时,只需要修改配置文件即可,我们还可以对某对象所需要的其它对象进行注入,这种注入都是在配置文件中做的。
Spring 的 IoC 实现原理利用的就是 Java 的反射机制,Spring 的工厂类会帮我们完成配置文件的读取、利用反射机制注入对象等工作,我们可以通过 bean 的名称获取对应的对象。下面的例子和注释就演示了这些配置文件的读取、利用反射机制注入对象等的过程。
1、BeanFactory.java
package com.springstudy.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工厂的初始化.
*
* @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的属性id和class
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.遍历该bean的property属性
for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {
Element foo2 = (Element) ite.next();
//11.获取该property的name属性
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中,其中key为id值,value为对象
beanMap.put(id.getText(), obj);
}
} catch (Exception e) {
System.out.println(e.toString());
}
}
/**
* 通过bean的id获取bean的对象.
*
* @param beanName
* bean的id
* @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("conf/config.xml");
JavaBean javaBean = (JavaBean) factory.getBean("javaBean");
System.out.println("userName=" + javaBean.getUserName());
System.out.println("password=" + javaBean.getPassword());
}
}
2、JavaBean.java
package com.springstudy.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;
}
}
3、config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="javaBean" class="com.springstudy.reflect.JavaBean">
<property name="userName">
<value>root</value>
</property>
<property name="password">
<value>123456</value>
</property>
</bean>
</beans>
以上只是 Java 反射机制和 Spring IoC 的一些简单的应用,实际操作中可能更加复杂,但是其原理是一样的。下面有两点总结,参考自点击打开链接:
1、什么是反射机制?
简单的来说,反射机制指的是程序在运行时能够获取自身的信息。在 java 中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。
2、利用反射机制能获得什么信息?
一句话,类中有什么信息,它就可以获得什么信息,不过前提是得知道类的名字,要不就没有后文了。
(1)首先得根据传入的类的全名来创建Class对象。
Class c=Class.forName("className"); // 注意:className必须为全名,也就是得包含包名,比如,cn.netjava.pojo.UserInfo;
Object obj=c.newInstance(); // 创建对象的实例
(2)有了对象就什么都好办了,想要什么信息就有什么信息了。
①获得构造函数的方法
Constructor getConstructor(Class[] params) // 根据指定参数获得public构造器
Constructor[] getConstructors() // 获得public的所有构造器
Constructor getDeclaredConstructor(Class[] params) // 根据指定参数获得public和非public的构造器
Constructor[] getDeclaredConstructors() // 获得public的所有构造器
②获得类方法的方法
Method getMethod(String name, Class[] params) // 根据方法名,参数类型获得方法
Method[] getMethods() // 获得所有的public方法
Method getDeclaredMethod(String name, Class[] params) // 根据方法名和参数类型,获得public和非public的方法
Method[] getDeclaredMethods() // 获得所以的public和非public方法
③获得类中属性的方法
Field getField(String name) // 根据变量名得到相应的public变量
Field[] getFields() // 获得类中所以public的方法
Field getDeclaredField(String name) // 根据方法名获得public和非public变量
Field[] getDeclaredFields() // 获得类中所有的public和非public方法