Reflection
我在前面写springIoC是就有写到动态代理,这就是反射的运用。
Reflection(反射),在上一章说了该类中的很多方法。这里总结上一篇的累述观点:反射是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能够直接操作任意对象的内部属性及方法。
这里不得不讲讲运行过程:*.java文件— 》 java编译器 --》 生成.class文件 --》 类装载器 --》 字节码校验器 --》 解释器 --》OS
java反射机制提供的功能:(每个运行时类,只加载一次,且对应一个.class实例)
1,在运行时,判断任意一个对象属性的类。
2,在运行时,构造任意一个类的对象。
3,在运行时,判断任意一个类所具有的成员变量和方法。
4,在运行时,调用任意一个对象的成员变量和方法。
5,生成动态代理。
Reflection相关API:
java.lang.class : 代表一个类 ;
java.lang.reflect.Method : 代表类的方法 ;
java.lang.reflect .Field :代表类的成员变量(即属性);
java.lang.reflect.Constructor : 代表类的构造方法
重中之重–通过反射调用类中指定的方法、属性
抛砖引玉:
// 实体类
package com.reflect.first;
public class Person {
public String name;
private int age;
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;
}
public void show() {
System.out.println("hello!" + name + ";您已经" + age + "了");
}
}
测试类
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.junit.Test;
public class TestReflection {
@Test
public void test2() throws Exception {
// 不用反射,创建类对象,并调用其中的方法和属性。
Person person = new Person();
person.setAge(25);
person.setName("zhouyi");
person.show();
// 1,用反射创建类对象,并调用其中的方法和属性。
Class clazz =Person.class; // 拿到com.reflect.first.Person 。这里提醒一句有时也用Class<Person>
System.out.println(clazz);
// 2,创建clazz对应的运行时类Person类的对象。
Person person1 = (Person) clazz.newInstance();
// 3,获取Person类的结构。
Field fname = clazz.getField("name");
Field fage = clazz.getDeclaredField("age");
// 设置person1对象的,属性fname
fname.set(person1, "huihui");
fage.setAccessible(true); // 设置private,protected修饰符限定符,可写操作
fage.set(person1, 24);
// 4,通过反射调用运行时类的指定方法
Method method = clazz.getMethod("show");
method.invoke(person1); // 返回值就是show方法的返回值。
}
}
运行结果:
类获取
四种常见的拿到类方式:(就不给出实例了)
// 1,第一种:运行时类本身(推荐使用)
Class clazz1 = Person.class;
System.out.println(clazz1.getName());
// 2,第二种:运行时类对象
Person person = new Person();
Class clazz2 = person.getClass();
System.out.println(clazz2.getName());
// 3,第三种:Class静态方法来获取,就好比数据库链接。(这样的共通性较好)
String className = "com.reflect.first.Person";
Class clazz3 = Class.forName(className );
System.out.println(clazz3.getName());
// 4,通过类的加载器
ClassLoader classLoader = this.getClass().getClassLoader();
Class clazz4 = classLoader.loadClass(className);
ClassLoader 的介绍
类加载器是用来把类装载进内存的。JVM规范定义了两种类型的类加载器:启动类加载器Bootstrap和用户自定义加载器user-defined class loader。JVM在运行时会产生3个类加载器组成的初始化加载器层次结构,如下所示:
Bootstrap ClassLoader(引导类加载器) ----- Extension ClassLoader(扩展类加载器) ----System ClassLoader(系统加载器) 的来回加载方式。
属性获取
属性 = 权限修饰符 + 变量类型 + 变量名
前提准备clas和接口(这样的话,想要测试什么,我只需要修改测试类即可)
后期改动:为了测试拿到方法的注解,给show加上了@PersonAnnotation(value = “zhouyi”)
package com.reflect.first;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import org.junit.Test;
public class TestReflection {
@Test
public void test2() throws Exception {
// 1,用反射创建类对象,并调用其中的方法和属性。
Class clazz =Person.class; // 拿到com.reflect.first.Person
// 拿到运行时类以及父类的所有public类型的属性--getFields
Field[] fields = clazz.getFields();
Arrays.asList(fields).forEach(t->{System.out.println(t.getName());});
System.out.println("------------------------------");
// 解决上面拿不到私有和保护类型的问题getDeclaredFields ;只能拿到运行时类本身的属性
Field[] fields1 = clazz.getDeclaredFields();
for (Field field1 : fields1) {
// 属性 = 权限修饰符 + 变量类型 + 变量名
System.out.println("变量名:" + field1.getName()); // 变量名
System.out.println("变量类型:" + field1.getType()); // 变量类型
System.out.print("权限修饰符:" + field1.getModifiers() + "-----"); // 权限修饰符
System.out.println(Modifier.toString(field1.getModifiers())); // 权限数字转换为原本权限修饰符
}
System.out.println("------------------------------");
System.out.println(Arrays.toString(fields1));
// 2,创建clazz对应的运行时类Person类的对象。
Person person = (Person) clazz.newInstance();
}
}
运行结果:
方法获取
方法的完整结构 — 方法 = 注解 + 权限修饰符 + 返回值类型 + 方法名 + 形参列表 + 异常
package com.reflect.first;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import org.junit.Test;
public class TestReflection {
@Test
public void test2() throws Exception {
// 1,用反射创建类对象,并调用其中的方法和属性。
Class clazz =Person.class;
// 拿到运行时类以及父类的所有public方法--getMethods
Method[] methods = clazz.getMethods();
// 当前类:compareTo,show;父类:say
Arrays.asList(methods).forEach(t->{System.out.print(t.getName() + "---");});
System.out.println("------------------------------");
// 拿到当前类所有方法,拿不到直接/间接父类
// 方法的完整结构 方法 = 注解 + 权限修饰符 + 返回值类型 + 方法名 + 形参列表 + 异常
Method[] methods1 = clazz.getDeclaredMethods();
Arrays.asList(methods1).forEach(t->{System.out.print(t.getName()+ "---");});
for (Method method : methods1) {
Annotation[] ann = method.getAnnotations(); // 注解
Arrays.asList(ann).forEach(t->{System.out.println(t + "---");});
System.out.println(Modifier.toString(method.getModifiers()) + "---"); // 权限修饰符
System.out.println(method.getReturnType().getName() + "---"); // 返回值类型
System.out.println(method.getName() + "---"); // 方法名
Class[] prams = method.getParameterTypes(); // 形参列表
Arrays.asList(prams).forEach(t->{System.out.println("["+ t.getName() +"]");});
Class[] exceptions = method.getExceptionTypes(); // 异常 由于没有这里就不打印了
}
}
}
运行结果:
获得构造器,创建运行类的对象(带参数有无)
前面我们对象构造时,使用newInstance ,我们发现他只能是空参,显然是获取我们所需要的有参构造器的
这里解释下:构造器最大的用处就是在创建对象时执行初始化,当创建一个对象时,系统会为这个对象的实例进行默认的初始化。如果想改变这种默认的初始化,就可以通过自定义构造器来实现。比如上面person类的构造器:Person() { }
举例:
Constructor[] constructors = clazz.getConstructors(); // 简单同上,就不运行了,参数无/有
Person person = (Person) constructors .newInstance("zhouyi",20);
各种杂项获得
父类,包,接口,类的注解
package com.reflect.first;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import org.junit.Test;
public class TestReflection {
@Test
public void test2() throws Exception {
// 1,杂项获得
Class clazz =Person.class;
// 获取父类
Class superClass = clazz.getSuperclass(); // 一般父类
System.out.println(superClass);
Type type = (Type) clazz.getGenericSuperclass(); // 带有泛型的父类
System.out.println(type);
Type[] types = ((ParameterizedType)type).getActualTypeArguments();
System.out.println(((Class)types[0]).getName()); // 获得泛型的类型
System.out.println("--------------------");
// 获取实现的接口, 只拿到了直接接口
Class[] interfaes = clazz.getInterfaces();
Arrays.asList(interfaes).forEach(t->{System.out.println(t);});
System.out.println("--------------------");
// 获取所在的包,类的注解
System.out.println(clazz.getPackage()); // 获取所在包
// 获取类的注解,拿到都是Runtime级别注解
Annotation[] annotations = clazz.getAnnotations();
Arrays.asList(annotations).forEach(t->{System.out.println(t);});
}
}
讲实话学到这里,我很疑惑,反射只能是子类和父类之间的调用吗?如果这样的话,作用范围不是很小吗?我尝试,跨域去调用方法全是调式通不过,难受。。。。。。让我再研究研究?
经过研究终于发现了问题在哪里:我发现了上面的理解除了两点问题。
1,对invoke的理解出了问题;2,对构造器的理解。
前提准备:
测试类:
package com.reflect.second;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
public class TestClass {
public static void main(String[] args) throws Exception {
// 这里我们用class的静态方法来获取
Class<?> clazz = Class.forName("com.reflect.second.Person" );
/**
* 这里我们来说明上次留下的另一个问题:构造器
* 1,其实每一个类都有一个默认的无参构造器,不如public Person() { }
* 2,但是当我们人为的写了一个构造器,就不会再有上面的没人构造器,比如
* public Person(String name, int age, String sex, String charcter) {
this.name = name;
this.age = age;
this.sex = sex;
this.charcter = charcter;
}
当然invoke的理解,也在其中
*/
//clazz.getConstructor(); 为了实验现象,选用下面的,其实在这个用这个就可以了
Constructor[] cons = clazz.getConstructors();
// 这里的结果是获得了所有的构造函数,你可以尝试去掉没有参数的看看结果
Arrays.asList(cons).forEach(t->{System.out.println(t);});
Person person = (Person) cons[0].newInstance("zhouyi",25,"male","你是真的帅气的一批");
Method method = clazz.getMethod("show");
method.invoke(person);
// Person person2 = new Person(); // 这里这样写是不可以的,这又是个坑? 经过仔细思考实验得到,传的唯一person,不是参数,而是对象实例
// method.invoke(person,person); // 这样就很好的解释了这个问题
Method method1 = Class.forName("com.reflect.second.Animal").getMethod("distinct", Person.class);
Animal animal = (Animal) Class.forName("com.reflect.second.Animal").newInstance();
//Animal animal = new Animal(); // 这样也是可以的
method1.invoke(animal, person); // 方法 + 对象实例 + 传参参数
}
}
运行结果:
1,对构造器的理解,注释已经说说过了。
2,从上面的例子,我们很清楚地看到,并不是父类和子类才能调用,这里我们进入了一个误区,我们利用反射去哪了某样东西,构造参数,那方法,此时我们却忘了拿了里面的所有东西,参数,参数类型,方法等,但唯独没有去那个类实例。上面解决方式,就是拿了类实例即可,这就是之前我们对invoke的错误理解:他的第一个参数,是一个实例对象参数,第二个才是真的传参参数。
3,最终无非是达到这个效果,终极反射等式 = 方法 + 对象实例 + 传参参数。
来回一个周,终于填完了,反射的坑。一把辛酸泪啊!