文章目录
1. 前言
Java
中最强大的技术:反射!为什么这么说,不妨再次来简单回忆一下Spring
这个框架。
我们知道Spring
是目前主流的 Java Web
开发框架,是 Java
世界最为成功的框架。该框架是一个轻量级的开源框架,具有很高的凝聚力和吸引力。在Spring
框架中,以 IoC
(Inverse of Control
,控制反转)和 AOP
(Aspect Oriented Programming
,面向切面编程)为内核。
IoC
指的是将对象的创建权交给Spring
去创建。使用Spring
之前,对象的创建都是由我们使用new
创建,而使用Spring
之后,对象的创建都交给了Spring
框架。IoC
是一种编程思想,主要用于降低代码之间的耦合度。具体来说,就是在创建对象的时候,不需要在使用经典的new
来进行对象的创建,而是利用反射,结合xml
将工厂对象和生产对象相隔离开,提高了灵活性。AOP
用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP
还解决一些系统层面上的问题,比如日志、事务、权限等。
IoC
通常又叫做IoC
容器,也可以称为 Spring
容器。主要用来管理对象的实例化和初始化,以及对象从创建到销毁的整个生命周期。通过读取 XML
或 Java
注解中的信息来获取哪些对象需要实例化。而在IoC
容器对对象进行实例化的时候,就是使用Java
中的反射技术来实现的。所以说Java
的反射技术,支撑起了整个底层的实现。
虽然在之前学习Spring
的时候,简单使用了一些反射的技术,但是其实技术这种东西很快就忘记了。所以在这篇文章中将比较详细的介绍下Java
的反射技术,并做一些简单的案例搭配学习。
2. 反射
Java
的反射是指程序在运行期可以拿到一个类的所有属性和方法,并得到实例化对象。代码可以在运行时装配,无需在源代码中进行链接。进而降低了代码的耦合度。基本反射包括一下几个技术:
- 根据一个字符串得到一个类的对象;
- 获取一个类的所有公有或者私有、静态或者实例的字段、方法和属性;
- 对泛型类的反射;
2.1 获得代表类的Class对象
2.1.1 getClass
即:通过一个类的对象,来获取代表这个类的Class对象。这个也是最简单的,比如:
String name = "张三";
Class<? extends String> aClass = name.getClass();
2.2.2 Class.forName
通过类的命名空间和类的名称组成。比如:
try {
Class<?> name = Class.forName("java.lang.String");
Constructor<?> constructor = name.getDeclaredConstructor(String.class);
String o = (String) constructor.newInstance("123");
System.out.println(o);
}catch (Exception e){
e.printStackTrace();
}
2.2.3 类的class属性
每个类都有class
属性,也就可以得到Class
类型对象,比如前面使用过的String.class
。
2.2.4 基本类型的TYPE属性
对于基本数据类型的包装类,都有TYPE
类型,通过这个属性也可以得到代表这个类的Class
实例,比如:
public static void main(String[] args) throws ClassNotFoundException {
Boolean flag = false;
Class<? extends Boolean> name = flag.getClass();
Class<?> name1 = Class.forName("java.lang.Boolean");
Class<?> name2 = Boolean.class;
// 多了一种方式
Class<?> name3 = Boolean.TYPE;
}
2.2 获取类的成员
2.2.1 构造函数
对于反射来说,Java
的访问修饰符没有任何意义。比如某个类定义为私有的构造方法,这里同样可以得到。比如说这里定义这样一个类Person
,用来测试:
static class Person{
private String name;
private int age;
private Person(){
}
public Person(String name, int age){
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
private String getName() {
return name == null ? "未知姓名" : name;
}
public int getAge(int baseAge) {
return age == 0 ? 0 : baseAge + age;
}
}
为了测试类的有参和无参两种构造,这里做简单的测试:
public static void main(String[] args) throws Exception {
// ------------私有无参构造函数----------------
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); // 设置可访问
Person person = (Person) constructor.newInstance();
System.out.println(person);
// ------------公开有参构造函数----------------
constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // 设置可访问
person = (Person) constructor.newInstance("战三", 123);
System.out.println(person);
}
根据上面的案例我们知道,确实Java
的访问修饰符在反射这里没有任何意义。且在得到构造器的方式有两种,分为有参数和无参数。有参数的这种需要传入的为方法中定义的参数的class
类型。当然,在Java
中还提供了另一种方式,即:
// ------------得到所有构造函数,不用处理参数类型----------------
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
Constructor<?> temp = constructors[i];
temp.setAccessible(true);
Person person;
if(i == 0){
person = (Person) temp.newInstance();
System.out.println(person);
}else{
person = (Person) temp.newInstance("李四", 123);
System.out.println(person);
}
}
2.2.2 普通方法
这里还是接着上面的案例来进行展开,比如这里我们需要执行getName
和getAge
方法来得到用户的姓名/年龄:
public static void main(String[] args) throws Exception {
// ------------有参构造函数----------------
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // 设置可访问
Object person = constructor.newInstance("张三", 23);
// => 无参数普通方法
Method getName = clazz.getDeclaredMethod("getName");
getName.setAccessible(true);
String name = (String) getName.invoke(person, null);
System.out.println(name);
// => 有参数普通方法
Method getAge = clazz.getDeclaredMethod("getAge", int.class);
getAge.setAccessible(true);
int age = (int) getAge.invoke(person, 2);
System.out.println(age);
}
结果:
2.2.3 静态方法
同样的,这里在Person
类中新增一个静态方法用于测试,比如:
public static void printUserInfo(String parameter){
System.out.println("测试静态方法!,传入参数:" + parameter);
}
然后我们进行测试:
public static void main(String[] args) throws Exception {
// ------------静态方法----------------
Class<?> clazz = Person.class;
Method printUserInfo = clazz.getDeclaredMethod("printUserInfo", String.class);
printUserInfo.setAccessible(true);
printUserInfo.invoke(null, "User");
}
注意到,这里因为静态方法并不需要类的实例对象,所以这里在invake
中传入的是一个null
对象。故而这里也就不再需要获取到构造器对象。运行结果:
2.2.4 私有非静态属性
需要注意的是,在前面的案例中。我定义Person
这个类为我这里测试的一个静态内部类,所以在外部类中可以直接访问内部类的私有属性。这里为了测试私有属性,显然就不能通过反射得到类实例对象,然后直接方位私有属性。所以这里将之前定义的Person
单独放置在一个文件中,即:Person.java
。然后开始本次测试:
public static void main(String[] args) throws Exception {
// ------------有参构造方法----------------
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Object person = constructor.newInstance("张三", 25);
// --> 获取private类型的name字段
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
System.out.println("用户初始化的姓名为:" + (String) name.get(person));
// --> 修改字段
name.set(person, "李四");
System.out.println("修改后的用户姓名为:" + (String) name.get(person));
}
测试结果:
2.2.5 私有静态属性
和前面操作静态方法类似的操作,我们还是先定义一个静态的属性字段在Person
类中,比如:
private static final String TAG = "Person";
按照类似的处理操作,我们只需要在第一个参数中传入null
即可:
public static void main(String[] args) throws Exception {
// ------------有参构造方法----------------
Class<?> clazz = Person.class;
// --> 获取private类型的name字段
Field tag = clazz.getDeclaredField("TAG");
tag.setAccessible(true);
System.out.println("静态常量参数TAG的值为:" + (String) tag.get(null));
}
结果为:
因为这里我定义为常量字符串,所以这里就不测试修改语法了。
2.3 对泛型类的反射
和前面一样,这里为了测试案例首先定义一个泛型类:
/**
* DCL——懒汉式
* @param <T>
*/
public abstract class Singleton<T extends Person> {
public Singleton(){
}
private volatile T mInstance;
/**
* 真正的创建对象工作,交给子类去完成
* @return
*/
protected abstract T create();
public T getInstance(){
if(mInstance == null){
synchronized (this){
if(mInstance == null){
mInstance = create();
}
}
}
return mInstance;
}
}
上面的Singleton
类是一个泛型类,同时注意到也是一个抽象类。所以在实例化Singleton
的时候需要实现这个类的抽象方法create
。首先来看下正常的调用是如何做的:
public class Man{
// 正常使用抽象的泛型单例
private static final Singleton<Person> userBean = new Singleton<Person>() {
@Override
protected Person create() {
return new Person("未知", 23);
}
};
public static void main(String[] args) throws Exception {
Person instance = userBean.getInstance();
System.out.println(instance);
}
}
对应的,写法为:
public class Man{
// 正常使用抽象的泛型单例
private static final Singleton<Person> userBean = new Singleton<Person>() {
@Override
protected Person create() {
return new Person("未知", 23);
}
};
public static void main(String[] args) throws Exception {
// 反射
Class<?> clazz = Man.class;
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object obj = constructor.newInstance();
Field userBean = clazz.getDeclaredField("userBean");
userBean.setAccessible(true);
Singleton<Person> o = (Singleton<Person>) userBean.get(obj);
Person person = o.getInstance();
System.out.println(person);
}
}
注意到这里使用的是测试类的Class
对象,因为抽象方法需要被实现。所以这里使用的是userBean
。
3. 后记
当然上面的只是一些比较原始的反射用法。后续将继续学习jOOR
。
References