Java 反射机制

5 篇文章 0 订阅
2 篇文章 0 订阅

原创文章,转载请注明出处。

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方法






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值