对于反射机制,学了有两遍,实际工作中用的不多,但是用来理解框架还是很有帮助的,打算花点时间把java基础的一些内容总结一遍,然后把框架再仔细搞搞。
一、反射机制概述
JAVA反射机制是在运行状态中,对于任意一个类(class文件),都能知道这个类所有的属性和方法。
对于任意一个对象,都能够调用它的任意一个方法和属性。
这种动态获取的信息以及动态调用对象的方法的功能成为java语言的反射机制。
其实就是:动态获取类中的信息。可以理解为对类的解剖。
反射的应用场景:spring框架中是有大量用到反射技术的;其次在完成的应用中,可以通过配置文件,将要新增功能的类名提供给应用,然后应用通过反射机制获取这个类,并使用这个功能。
二、类加载机制初步
小写class表示是一个类类型,大写Class表示这个类的名称。Class是一个类,封装了当前对象所对应的类的信息;对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
1,获取Class对象的三种方式
(1)通过类名获取 类名.class
// 1.通过类名
clazz = Person.class;
(2)通过对象获取 对象名.getClass()
// 2.通过对象名
// 这种方式是用在传进来一个对象,却不知道对象类型的时候使用
Person person = new Person();
clazz = person.getClass();
// 上面这个例子的意义不大,因为已经知道person类型是Person类,再这样写就没有必要了
// 如果传进来是一个Object类,这种做法就是应该的
Object obj = new Person();
clazz = obj.getClass();
(3)通过全类名获取 Class.forName(全类名)
// 3.通过全类名(会抛出异常)
// 一般框架开发中这种用的比较多,因为配置文件中一般配的都是全类名,通过这种方式可以得到Class实例
String className = "zday06_reflect.Person";
clazz = Class.forName(className);
2,类加载类ClassLoader
类装载器是用来把类(class)装载进 JVM 的。
JVM 规范定义了两种类型的类装载器:启动类装载器(bootstrap)
和用户自定义装载器(user-defined class loader)。
测试代码:
// 1. 获取一个系统的类加载器(可以获取,当前这个类ClassTest就是它加载的)
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
// 2. 获取系统类加载器的父类加载器(扩展类加载器,可以获取).
classLoader = classLoader.getParent();
System.out.println(classLoader);
// 3. 获取扩展类加载器的父类加载器(引导类加载器,不可获取).
classLoader = classLoader.getParent();
System.out.println(classLoader);
// 4. 测试当前类由哪个类加载器进行加载(系统类加载器):
classLoader = Class.forName("zday06_reflect.ClassTest").getClassLoader();
System.out.println(classLoader);
// 5. 测试 JDK 提供的 Object 类由哪个类加载器负责加载(引导类)
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
首先,系统类加载器可以加载当前项目src目录下面的所有类,
如果文件也放在src下面,也可以用类加载器来加载
3,加载类的过程
// 为了使用类而做的准备工作一般有以下3个步骤:
// 加载:由类加载器完成,找到对应的字节码,创建一个Class对象
// 链接:验证类中的字节码,为静态域分配空间
// 初始化:如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块
// 不会初始化静态块
Class clazz1 = Base.class;
System.out.println("------");
// 会初始化
Class clazz2 = Class.forName("zday06_reflect.Base");
Base base = new Base();
System.out.println(clazz2);
System.out.println(base.getClass());
三、反射技术的相关操作
1.获取与操作方法
测试类:Person:
//Java让我们在运行时识别对象和类的信息,主要有2种方式:
//一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型信息;
//另一种是反射机制,它允许我们在运行时发现和使用类的信息。
public class Person implements Serializable {
private String name;
private int age;
// get/set方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private void privateMethod() {
System.out.println("privateMethod");
}
@Override
public String toString() {
return "name : " + name + ";age=" + age;
}
}
1.1 获取取clazz对应类中的所有方法--方法数组(一)
不能获取private方法,且获取从父类继承来的所有方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.print(" " + method.getName());
}
1.2.获取所有方法,包括私有方法 --方法数组(二)
// 所有声明的方法,都可以获取到,且只获取当前类的方法
methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.print(" " + method.getName());
}
System.out.println();
1.3.获取指定的方法
// 需要参数名称和参数列表,无参则不需要写
// 对于方法public void setName(String name) { }
Method method = clazz.getDeclaredMethod("setName", String.class);
System.out.println(method);
// 而对于方法public void setAge(int age) { }
method = clazz.getDeclaredMethod("setAge", int.class);
System.out.println(method);
// 这样写是获取不到的,如果方法的参数类型是int型
// 如果方法用于反射,那么要么int类型写成Integer: public void setAge(Integer age) { }
// 要么获取方法的参数写成int.class
1.4执行方法
// invoke第一个参数表示执行哪个对象的方法,剩下的参数是执行方法时需要传入的参数
Object obje = clazz.newInstance();
method.invoke(obje, 2);
总结:
// 这种反射实现的主要功能是可配置和低耦合。只需要类名和方法名,
// 而不需要一个类对象就可以执行一个方法。
// 如果我们把全类名和方法名放在一个配置文件中,
// 就可以根据调用配置文件来执行方法
2,操作属性、私有属性
String className = "zday06_reflect.Person";
Class clazz = Class.forName(className);
2.1 获取所有字段 -- 字段数组
// 可以获取公用和私有的所有字段,但不能获取父类字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.print(" " + field.getName());
}
System.out.println();
2.2获取指定字段
Field field = clazz.getDeclaredField("name");
System.out.println(field.getName());
Person person = new Person("ABC", 12);
2.3获取指定对象的指定字段的值
Object val = field.get(person);
System.out.println(val);
2.4设置指定对象的指定对象Field值
field.set(person, "DEF");
System.out.println(person.getName());
2.5 如果字段是私有的,不管是读值还是写值,都必须先调用setAccessible(true)方法
// 比如Person类中,字段name字段是公用的,age是私有的
field = clazz.getDeclaredField("age");
field.setAccessible(true);
System.out.println(field.get(person));
3,操作构造函数
3.1 获取全部构造函数
Constructor<Person>[] constructors = (Constructor<Person>[]) Class.forName(className).getConstructors();
for (Constructor<Person> constructor : constructors) {
System.out.println(constructor);
}
3.2获取某一个,需要参数列表
Constructor<Person> constructor = clazz.getConstructor(String.class, int.class);
System.out.println(constructor);
3.3 调用构造器的 newInstance() 方法创建对象
Object obj = constructor.newInstance("zhagn", 1);
4,获取注解
相关注解类:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.METHOD})
public @interface AgeValidator {
public int min();
public int max();
}
测试方法:
String className = "com.atguigu.java.fanshe.Person";
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("setAge", int.class);
int val = 6;
// 获取指定名称的注解
Annotation annotation = method.getAnnotation(AgeValidator.class);
if (annotation != null) {
if (annotation instanceof AgeValidator) {
AgeValidator ageValidator = (AgeValidator) annotation;
if (val < ageValidator.min() || val > ageValidator.max()) {
throw new RuntimeException("年龄非法");
}
}
}
method.invoke(obj, 20);
System.out.println(obj);