深入 -- Class反射

Class反射是深入学习Java必须的,而且也是学习Spring内幕所需要的。


前言

反射是Java语言的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。

Java语言允许通过程序化的方式间接对Class进行操作,Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象。

通过该元信息对象可以获取Class的结构信息:如构造函数、属性和方法等..

Oracle官方对反射的解释:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

简而言之,通过反射,我们可以在运行时获得程序或程序集中的每一个类型的成员和成员信息。

程序中一般的对象的类型都是在编译期就确定下来的,而Java反射机制可以动态的创建对象并调用其属性,这样的对象的类型在编译期是未知的。

所以我们可以通过 反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,他不需要实现(写代码的时候或编译期)知道运行对象是谁。


一个简单的Demo

创建一个Car对象:

package com.spring.demo.reflection_1;

public class Car {

	private String brand;
	private String color;
	private int maxSpeed;

	// 默认构造函数
	public 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;
	}
}

我们看下反射的方法:

package com.spring.demo.reflection_1;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Reflection1 {

	@SuppressWarnings({ "rawtypes", "unused", "unchecked" })
	public static void main(String[] args) throws Exception {

	/**
	 * 一般情况下,我们都是直接创建Car对象
	 */
	// Car car = new Car();
	// car.setBrand("宝马X1");

	/**
	 * 或者这样子
	 */
	// Car car2 = new Car("宝马X6", "红色", 1);

	/**
	 * ------以上两种都是传统的创建对象的方式,下面我们通过Java反射来创建对象。------
	 */

	// Class clazz = Class.forName("com.spring.demo.reflection_1.Car");
	// Object obj = clazz.newInstance();
	// Car car3 = (Car) obj;
	// car3.setBrand("奔驰");

	/**
	 * ---------或者---------
	 */

	// 通过类的默认装载器
	ClassLoader loader = Thread.currentThread().getContextClassLoader();
	Class clazz2 = loader.loadClass("com.spring.demo.reflection_1.Car");
	// 获取类的默认构造器对象并通过它实例化Car对象
	Constructor cons = clazz2.getDeclaredConstructor((Class[]) null);
	Car car4 = (Car) cons.newInstance();

	// 通过反射方式设置属性
	Method setBrand = clazz2.getMethod("setBrand", String.class);
	setBrand.invoke(car4, "悍马");
	Method setColor = clazz2.getMethod("setColor", String.class);
	setColor.invoke(car4, "黄色");
	Method setMaxSpeed = clazz2.getMethod("setMaxSpeed", int.class);
	setMaxSpeed.invoke(car4, 1);

	car4.introduce();

	}

}

以上代码说明我们完全可以通过编程的方式调用Class的各项功能,这和直接通过构造函数和方法调用功能的效果是一致的。

只不过前者是间接调用,后者是直接调用。


Java反射机制

Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。

三个主要的反射类:

Constructor:

    Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。 

Method:

    Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。 

Field:

    Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。


通过反射访问私有变量和方法

Java的反射体系保证了可以通过程序化的方式访问目标中所有的元素,对于private或protected的成员变量和方法,只要JVM的安全机制允许,也可以通过反射进行调用。

我们看个栗子:

package com.spring.demo.reflection_2;

public class PrivateCar {

	// private成员变量:使用传统的类实例变量调用方式,只能在本类中访问。
	private String color;

	// protected方法:使用传统的类实例调用方法,只能在子类和本包中访问。
	protected void driver() {
		System.out.println("driver private car! the color is : " + color);
	}

}

PrivateCar类中color变量和driver方法都是私有的;

通过类实例变量无法在外部访问私有变量、调用私有方法的,但通过反射机制却可以绕过这个限制。

package com.spring.demo.reflection_2;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* PrivateCar类中color变量和driver方法都是私有的;
* 通过类实例变量无法在外部访问私有变量、调用私有方法的,但通过反射机制却可以绕过这个限制
* 
* @author CYX
* @time 2017年12月22日上午9:57:07
*/
public class PrivateCarReflect {

	@SuppressWarnings("rawtypes")
	public static void main(String[] args) throws Exception {

	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	Class clazz = classLoader.loadClass("com.spring.demo.reflection_2.PrivateCar");

	PrivateCar privateCar = (PrivateCar) clazz.newInstance();

	Field colorField = clazz.getDeclaredField("color");
	colorField.setAccessible(true);// 取消Java语言访问检查以访问private变量
	colorField.set(privateCar, "红色");

	Method driverMethod = clazz.getDeclaredMethod("driver", (Class[]) null);
	driverMethod.setAccessible(true);// 取消Java语言访问检查以访问protected方法
	driverMethod.invoke(privateCar, (Object[]) null);

	}

}

执行结果:

在访问private、protector变量和方法时,必须通过setAccessible(boolean access)方法取消Java语言检查。

否则将会抛出IllegalAccessException异常信息。


通过反射获取对象中变量的参数
package com.spring.demo.reflection_3;

public class Car3 {

	// private成员变量:使用传统的类实例调用方式,只能在本类中访问。
	private String brand;

	public String getBrand() {
		return brand;
	}

	public String getBrand(String brand) {
		return "汽车品牌 :" + brand;
	}

	public void setBrand(String brand) {
		this.brand = brand;
	}
}
package com.spring.demo.reflection_3;

import java.lang.reflect.Method;

public class TestCarMain {

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void main(String[] args) throws Exception {

	// 首先通过反射,设置变量参数
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	Class clazz = classLoader.loadClass("com.spring.demo.reflection_3.Car3");
	Object obj = clazz.newInstance();
	Car3 car3 = (Car3) obj;

	Method setBrandMethod = clazz.getMethod("setBrand", String.class);
	setBrandMethod.setAccessible(true);
	setBrandMethod.invoke(car3, "宝马X1");// 关键方法

	// 通过反射方法获取变量参数
	// 方法1
	Method getBrandMethod = clazz.getMethod("getBrand", null);
	Object brand = getBrandMethod.invoke(car3, (Object[]) null);
	System.out.println("执行无参方法 :" + brand);

	// 方法2
	getBrandMethod = clazz.getDeclaredMethod("getBrand", String.class);
	brand = getBrandMethod.invoke(car3, "宝马X2");
	System.out.println("执行有参方法:" + brand);

	// 方法3
	getBrandMethod = clazz.getMethod("getBrand", String.class);
	brand = getBrandMethod.invoke(car3, "宝马X6");
	System.out.println("执行有参方法:" + brand);
	}

}

运行结果:


反射在实际Java开发应用中的用途

(下面是刘老师的博文,这里直接搬运过来了)


如何理解反射?

(知乎-学习java应该如何理解反射?-郑剑锋的回答)


实际体验

很早之前,个人也不太能理解反射的作用,就是一种稀里糊涂的状态。

后来做了一个框架之后,对反射有点认识了....

当时是将一个大程序进行拆分,按照业务不同,拆分成不同的模块。

然后写一个框架,要求框架能够加载这些单独的业务模块。

框架的代码工程和业务模块的工程是完全分开的,不要说什么编译器和运行期了,我写框架代码的时候,压根就不知道它模块的类名是什么...

当时的做法就是,抽出的模块中,增加一个xml配置文件,将模块主类的全类名配置进去。

当框架启动后,就会去读取这些配置文件,然后随着,服务被调用,根据不同的参数,映射获取不同的class(就是不同的模块)。

然后通过反射,获取到对象实例,继而进行调用业务模块。


参考文章:

《学习Spring必学的Java基础知识(1)----反射》

《给小白的Java EE生存指南(6) :Java 反射》

《深入解析Java反射(1) - 基础》

《知乎-学习java应该如何理解反射?-郑剑锋的回答》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值