- 1. 前言
- 2. 反射
- 2.1 获得代表类的Class对象
- 2.1.1 getClass
- 2.2.2 Class.forName
- 2.2.3 类的class属性
- 2.2.4 基本类型的TYPE属性
- 2.2 获取类的成员
- 2.2.1 构造函数
- 2.2.2 普通方法
- 2.2.3 静态方法
- 2.2.4 私有非静态属性
- 2.2.5 私有静态属性
- 2.3 对泛型类的反射
- 3. 后记
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的反射是指程序在运行期可以拿到一个类的所有属性和方法,并得到实例化对象。代码可以在运行时装配,无需在源代码中进行链接。进而降低了代码的耦合度。基本反射包括一下几个技术:
- 根据一个字符串得到一个类的对象;
- 获取一个类的所有公有或者私有、静态或者实例的字段、方法和属性;
- 对泛型类的反射;
即:通过一个类的对象,来获取代表这个类的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);
}
结果:
同样的,这里在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对象。故而这里也就不再需要获取到构造器对象。运行结果:
需要注意的是,在前面的案例中。我定义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));
}
结果为:
因为这里我定义为常量字符串,所以这里就不测试修改语法了。
和前面一样,这里为了测试案例首先定义一个泛型类:
public abstract class Singleton{ public Singleton(){} private volatile T mInstance; 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 userBean = new Singleton() {
@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 userBean = new Singleton() {
@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 o = (Singleton) userBean.get(obj);
Person person = o.getInstance();
System.out.println(person);
}
}
注意到这里使用的是测试类的Class对象,因为抽象方法需要被实现。所以这里使用的是userBean。
3. 后记当然上面的只是一些比较原始的反射用法。后续将继续学习jOOR。
References
- Java中一个逐渐被遗忘的强大功能,强到你难以置信!!
- Spring是什么
- Java 反射详解



