目录
一.反射机制
1.Java Reflection
2.反射相关的主要类
3.反射的优缺点
4.反射调用优化—关闭访问检查
二.Class类
1.基本介绍
2.常用方法
3.获取Class对象的方式
4.那些类型有Class对象
三.类加载
1.介绍
2.类加载时机
3.类加载各阶段
四.获取类结构的信息
1. java.lang.Class类
2. java.lang.reflect.Field类
五.反射暴破
1.介绍
2.反射暴破构造器
3.反射暴破操作属性
4.反射暴破操作方法
一.反射机制
1.Java Reflection
反射机制允许程序在执行期借助于ReflectionAPI获取任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
加载完类后,在堆中就产生了一个Class类型的对象,这个对象包含了类的完整结构信息。通过这个对象得到类的结构。
下面是反射的原理图:
后面的内容也会用到这个图。
2.反射相关的主要类
类 | 说明 |
java.lang.Class | 代表一个类,Class对象表示某个类加载后在堆中的对象 |
java.lang.reflect.Method | 代表类的方法,Method对象表示某一个类的方法 |
java.lang.reflect.Field | 代表类的成员变量,Field对象表示某个类的成员变量 |
java.lang.reflect.Constructor | 代表类的构造方法,Constructor对象表示构造器 |
3.反射的优缺点
优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
缺点:使用反射基本是解释执行,对执行速度有影响。
4.反射调用优化—关闭访问检查
Method、Field和Constructor对象都有setAccessible()方法。setAccessible()的作用是启动和禁用访问安全检查的开关。参数为true表示反射的对象在使用是取消访问检查,提高反射的效率。参数为false则表示反射的对象执行访问检查。
二.Class类
1.基本介绍
Class也是类,也继承于Object类。Class类不是new出来的,而是系统创建的。对于某一个类的Class对象,在内存中只有一份,因此类只加载一次。每个类的实例都会记得自己是由哪个Class实例所完成。通过Class对象可以完整地得到一个类的完整结构。类的字节码二进制数据,是放在方法区的。
2.常用方法
方法 | 说明 |
forName(String name) | 返回指定类名 name 的 Class 对象 |
newInstance() | 调用缺省构造函数,返回给Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体名称 |
getSuperClass() | 返回当前Class对象的父类的Class对象 |
getInterfaces() | 获取当前Class对象的接口 |
getClassLoader() | 返回该类的类加载器 |
getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
getConstructors() | 返回一个包含某些Constructor对象的数组 |
getDeclaredFields() | 返回Field对象的一个数组 |
getMethod(String name,Class ...paramType) | 返回一个Method对象,此对象的形参类型为paramType |
应用示例:
String classAllPath="newBag.Cat";
//1.获取Cat类 对应的Class对象
Class<?> cls = Class.forName(classAllPath);
//2.输出cls
System.out.println(cls); //显示cls对象,是哪个类的Class对象
System.out.println(cls.getClass()); //输出cls运行类型
//3.得到包名
System.out.println(cls.getPackage().getName());
//4.得到全类名
System.out.println(cls.getName());
//5.通过cls创建对象实例
Cat cat=(Cat) cls.newInstance();
System.out.println(cat);
//6.通过反射获取属性brand
Field name = cls.getField("name");
System.out.println(name.get(cat));
//7.通过反射给属性赋值 name
name.set(cat,"大橘");
System.out.println(name.get(cat));
//8.得到所有的属性(字段)
Field[] fields = cls.getFields();
for(Field f:fields){
System.out.println(f.getName());
}
3.获取Class对象的方式
1)已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法 forName() 获取。应用场景:多用于配置文件,读取类全路径,加载类。
补充:全类名(Fully Qualified Class Name)是一个类的完整名称,它包含了类所在的包名以及类名本身。全类名的目的是在整个Java程序或Java环境中唯一地标识一个类。
String classAllPath="newBag.Cat";
Class cls=Class.forName(classAllPath);
2)若已知具体的类,通过类的class获取,该方法最为安全可靠,程序性能最好。应用场景:多用于参数传递,比如通过反射得到对应构造器对象。
Class cls=类名.class;
Class cls= Cat.class;
3)已知某个类的实例,调用该实例的getClass()方法获取。应用场景:通过创建好的对象,获得Class对象。
Cat cat = new Cat();
Class cls = cat.getClass();
4)通过类加载器获取。
ClassLoader cl=对象.getClass().getClassLoader();
Class clazz=cl.loadClass("类的全类名");
String classAllPath="newBag.Cat";
Cat cat = new Cat();
ClassLoader classLoader = cat.getClass().getClassLoader();
Class cls = classLoader.loadClass(classAllPath);
5)基本数据(int等)使用 Class cls=基本数据类型.class;
Class cls=基本数据类型.class;
Class<Integer> integerClass = int.class;
基本数据类型对应的包装类,使用 Class cls=包装类.TYPE;
Class cls=包装类.TYPE;
Class<Integer> type = Integer.TYPE;
4.那些类型有Class对象
1)外部类,成员内部类,静态内部类,局部内部类,匿名内部类;2)interface:接口;3)数组;4)enum:枚举;5)annotation:注解;6)基本数据类型;7)void。
三.类加载
1.介绍
反射机制是Java实现动态语言的关键,也就是通过反射实现类动态加载。
加载有两种类型:
静态加载:编译时加载相关的类,如果没有则报错,依赖性太强。
例子:
public static void main(String[] args){
Scanner scan=new Scanner(System.in);
String str=scan.nextLine();
switch (str){
case "1":
Dog dog=new Dog();
break;
case "2":
System.out.println("输入了2");
break;
}
}
我们并没有写Dog类,搜易编译器会报错,告诉我们没有Dog类。这就是静态加载,即在编译时就加载了,没有写直接告诉我们错了。
动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在也不会报错,降低了依赖性。
例子:
public static void main(String[] args) throws ClassNotFoundException {
Scanner scan=new Scanner(System.in);
String str=scan.nextLine();
switch (str){
case "1":
System.out.println("输入了1");
break;
case "2":
Class cat = Class.forName("Cat");
break;
}
}
我们没有写Cat类,但是编译器不会报错。接着我们运行代码,只要我们不选2,就不会报错。只有选了2,才会出现错误。这就是动态加载,不用就没事。
2.类加载时机
1)当创建对象时(new)进行静态加载;
2)当子类被加载时,父类也加载(静态加载);
3)调用类中的静态成员时进行静态加载;
4)通过反射加载类是动态加载。
3.类加载各阶段
3.1 加载
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、jar包等)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象
3.2 验证
目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机本身的安全。包含:文件格式验证(是否以oxcafebabe开头)、元数据验证、字节码验证和符合引用验证
3.3 准备
JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量所使用的内存都将在方法区中进行分配
//n1是实例属性,不是静态变量,在准备阶段不会分配内存
public int n1=10;
//n2是静态变量,分配内存;n2默认初始化为0,而不是10
public static int n2=10;
3.4 解析
虚拟机将常量池内的符号引用替换为直接引用的过程
3.5 初始化
到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行<clinit>()方法的过程。
什么是<clinit>()方法呢?
<clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
四.获取类结构的信息
1. java.lang.Class类
方法 | 说明 |
getName() | 获取全类名 |
getSimpleName() | 获取简单类名 |
getFields | 获取所有public修饰的属性,包含本类以及父类的 |
getDeclaredFields | 获取本类中的所有属性 |
getMethods | 获取所有public修饰的方法,包含本类以及父类的 |
getDeclaredMethods | 获取本类中所有方法 |
getConstructors | 获取所有public修饰的构造器,包含本类,没有父类 |
getDeclaredConstructors | 获取本类中所有构造器 |
getPackage | 以Package形式返回 包信息 |
getSuperClass | 以Class形式返回父类信息 |
getInterfaces | 以Class[]形式返回接口信息 |
getAnnotations | 以Annotation[]形式返回注解信息 |
2. java.lang.reflect.Field类
方法 | 说明 |
getModifiers | 以int形式返回修饰符 ,返回0是默认修饰符,1是public,2是private,4是protected,8是static,16是final |
getType | 以Class形式返回类型 |
getName | 返回属性名 |
五.反射暴破
1.介绍
首先我要知道这个反射暴破是干什么的?
暴破即暴力破解,我们可以通过暴破操作一些私有属性(private)的构造器、属性和方法。
2.反射暴破构造器
Class类相关方法:
newInstance:调用类中的无参构造器,获取对应类的对象
getConstructor(Class...):根据参数列表,获取对应的构造器对象
getDecalaredConstructor(Class...):根据参数列表,获取对应构造器对象
Constructor类相关方法:
setAccessible:暴破
newInstance(Object...):调用构造器
//例子
Class<?> cls = Class.forName("User");
Constructor<?> constructor = cls.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Object o = constructor.newInstance("Tom", 13);
System.out.println(o);
3.反射暴破操作属性
暴破:setAccessible(true)。
访问:set(o,值)和get(o)。
如果属性是静态的,set和get中的参数o可以写成null。
Class<?> cls = Class.forName("Person");
Object o = cls.getConstructor(String.class, int.class).newInstance("Tom", 0);
Field age = cls.getDeclaredField("age");
age.setAccessible(true);
age.set(o,14);
System.out.println(o);
4.反射暴破操作方法
暴破:setAccessi(true);
访问:invoke(o,参数列表);如果是静态方法,这里的o可以换从null
Class<?> student = Class.forName("Student");
Method getIm = student.getDeclaredMethod("getIm", int.class, String.class);
getIm.setAccessible(true);
getIm.invoke(null,13,"Tom");
//下面是getIm方法
private static void getIm(int age,String s){
System.out.println(age+" "+s);
}