0.参考



点击查看【bilibili】


点击查看【processon】
10-反射.xmind


1.反射

1.1.需求引入

“通过外部配置文件, 在不修改Java程序源码的情况下, 来控制程序”

1.2反射概述

  1. - Reflection(反射) :动态语言的关键, 反射机制允许程序在执行器借助Reflection API 取得任何类的内部信息, 并能直接操作任意对象的内部属性及方法
  2. - 静态语言:编译时加载相关的类. 若无则编译报错, 依赖性过强. 运行时结构不可变. Java
  3. - 动态语言:运行时加载需要的类. 若硬编码了某类而运行时却不使用, 即使不存在该类也不报错, 低依赖.
  4. - Java不是动态语言但是可以称之为"准动态语言",即Java有一定的动态性,我们可以利用反射机制,字节码的操作来获得类时动态语言的特性
  5. - 类加载后, 堆内存中的方法去就产生了一个 Class 对象(类模板, 一个类只有一个 Class类模板). 这个Class对象包含了完整的(加载)类的所有结构信息. 我们可以通过这个对象看到类的结构. 这个Class对象就如一面镜子, 两边分别是Class对象 / 具体类的结构信息.

1.3主要API

  1. - java.lang.Class: 模板类, 反射的源头
  2. - Hello.java --(javac.exe编译)--> Hello.class(字节码文件) --(java.exe解释运行)--> 即将class字节码加载到内存中--内存中有一个对应的具体的运行时类: Class的一个实例, 即为Hello类的唯一类模板
  3. - java.lang.refiect.Method: 类的方法
  4. - java.lang.refiect.Field: 类的成员变量
  5. - java.lang.refiect.Constructor:类的构造器
  6. - java.lang.reflect.AccessibleObject: 它是 FieldMethod Constructor 对象的基类。它提供了**将反射的对象标记为在使用时取消默认 Java 语言访问安全控制检查**的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 FieldMethod Constructor 对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。
  7. - isAccessiblepublic boolean isAccessible(): 获得此对象的 accessible 标志的值。
  8. - 此对象的返回值 就是accessible的标志值,一般情况下无论 publicprivate,protected,默认等修饰的属性的access值均为false(注意他的意思并非是访问权限而是对该自己执行安全检查)。
  9. - setAccessible 设置访问检查/暴破操作:public static void setAccessible(object obj, boolean flag) throws SecurityException
  10. - 对通过 getDeclaredField 获取得到的私有属性(方法/构造器 同理), 若不暴破(通过如上方法)--即设置可访问, 就对其进行操作, 则报 IllegalAccessException.
  11. - 使用单一安全性检查(为了提高效率)为一对象设置 accessible 标志。如果存在安全管理器,则在 ReflectPermission("suppressAccessChecks") 权限下调用 checkPermission 方法。当flag true,表示不开启安全检查,但是不能更改输入 object的任何元素的可访问性(例如,如果元素对象是 Class 类的 Constructor 对象),则会引发 SecurityException。如果发生 SecurityException,对于少于(不包括)发生异常的元素的数组元素,可以将对象的可访问性设置为 flag;对于超出(包括)引发异常的元素的那些元素,则不更改其可访问性.
  12. - 一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,但有的时候,例如**要序列化的时候,我们又必须有能力去处理这些字段,这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问,而由于反射类中的FieldMethodConstructor继承自AccessibleObject,因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。( **但有的时候这将会成为一个安全隐患,为此,我们可以启用java.security.manager来判断程序是否具有调用setAccessible()的权限。默认情况下,内核API和扩展目录的代码具有该权限,而类路径或通过URLClassLoader加载的应用程序不拥有此权限。例如:当我们以这种方式来执行上述程序时将会抛出异常
  1. >java -Djava.security.manager ExampleExplorer
  2. Exception in thread "main" java.security.AccessControlException: access denied (
  3. java.lang.reflect.ReflectPermission suppressAccessChecks)
  4. at java.security.AccessControlContext.checkPermission(Unknown Source)

)

1.4反射原理图

H3OWRJ9R$8990$~)F1P(I9D.png

1.5反射的调用优化-关闭访问检查

  1. - Method / Field / Constructor 对象(的共同父类AccessibleObject)的 setAccessible(boolean flag)方法
  2. - setAccessible(): 启动或禁止访问安全检查的开关
  3. - flag参数:
  4. - true取消访问检查, 提高反射效率. 否则关闭.

1.6反射的优缺点

  1. - 优点:
  2. - 动态创建/使用对象, 十分灵活. 所以作为框架核心
  3. - 缺点:
  4. - 使**用反射基本是解释执行, 对执行速度有影响**

2.Class类 (rt.jar中的: java.lang.Class)

2.1基本介绍

  1. - Class也是类, 默认继承Object
  2. - Class是**final**类
  3. - Class类对象不是new出来的(其唯一构造器为private), 而是随着JVM启动被 Bootstrap启动类加载器 加载
  4. - 每个(运行时)类的Class对象, 它对应的Class类只会被加载一次(做一次加载操作), 内存中只有一份对应的Class对象(类模板)
  5. - 每个类的每个实例的 getClass: Class<?> 方法, 都能获取自己对应的类模板
  1. /**
  2. * Object.getClass():Class<?> 而 Class又继承自Object
  3. */
  4. @Test
  5. public void myTest(){
  6. MyBean bean = new MyBean();
  7. Class<? extends MyBean> beanClass = bean.getClass();
  8. System.out.println(MyBean.class);// class com.ryze.admin.bean.MyUser$MyBean 美刀号:因为是内部类
  9. System.out.println(bean.getClass());//class com.ryze.admin.bean.MyUser$MyBean
  10. System.out.println(beanClass == MyBean.class);// true
  11. System.out.println(beanClass instanceof Class);// true
  12. System.out.println(beanClass instanceof Object);// true
  13. System.out.println(String.class);// class java.lang.String
  14. System.out.println("test".getClass());// class java.lang.String
  15. }
  16. class MyBean{
  17. }
  1. - Class对象(的一系列API)可以完整地获取某个类的完整结构
  2. - Class对象是存放在堆中
  3. - 类对应的字节码二进制数据(class文件)(也叫类的元数据), 存放在方法区.

0@L]37G53`6(I~{XGJ19Y93.png

2.2举例

那些类型可以有Class对象?

  1. 1. class:外部类,成员类(成员内部类,静态内部类),局部内部类,匿名内部类
  2. 1. interface:接口
  3. 1. []:数组
  4. 1. enum:枚举
  5. 1. annotation:注解@interface
  6. 1. primitive type:基本数据类型
  7. 1. void

2.3类加载时到生成对象过程

  1. - ![](https://cdn.nlark.com/yuque/0/2021/png/1057015/1618550744763-436da19a-3f11-4416-b772-3b3e71185895.png#from=url&height=340&id=U2ifx&margin=%5Bobject%20Object%5D&originHeight=454&originWidth=563&originalType=binary&ratio=1&status=done&style=none&width=422)

3.类加载

3.1基本介绍

  1. - 反射是Java实现动态特性的关键, **通过反射实现类的动态加载(运行时确定)**

3.2类加载时机

  1. - 静态加载
  2. 1. (new)对象时
  3. 1. 在加载子类时, 会优先加载其直接父类, 同理依次向上先加载最高父类(只是加载对应类, 并不会创建一个父类对象)
  4. 1. 类被加载的时候, 其中的**静态**代码块、**静态**方法、**静态**变量以及前三者涉及/依赖的所有类也会被加载
  5. 1. 调用某类中的**静态**属性
  6. - 动态加载
  7. - 反射: Class.forName("com.ryze.MyTest");

3.3类加载过程图

3.3.1 .java文件到初始化全过程

image.png

3.3.2类加载具体阶段

image.png

  1. 1. 加载阶段: JVM在该阶段将字节码从不同数据源(class文件/jar包/网络等)转化为二进制字节流加载到内存中, 并且生成一个对应的Class类对象
  2. 1. 连接阶段
  3. 1. 验证:
  4. - 目的是为了确保class文件的字节流中包含的信息符合当前虚拟机的要求, 并且不会危害虚拟机自身安全
  5. - 验证条目:
  6. 1. 文件格式验证(是否以魔数 "oxcafebabe"" 开头)
  7. 1. 元数据验证
  8. 1. 字节码验证
  9. 1. 符号引用验证
  10. - 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施, 缩短虚拟机类加载的时间
  11. 2. **准备:**
  12. - JVM在该阶段对 静态变量: ①分配内存 ②默认初始化
  1. public class TestPreparation{
  2. // 在 准备阶段 中, 如下属性的情况
  3. public int n1 = 10; // 非静态, JVM不会对其分配内存等操作
  4. public static int n2 = 20; // 静态, 分配内存--非默认初始化0
  5. public static final int n3 = 30;// 静态, 分配内存--final默认初始化30.
  6. // final常量, 一旦赋值即不可变
  7. }
  1. 3. 解析:
  2. - 虚拟机将常量池中的符号引用替换为直接引用的过程
  3. 3. **初始化阶段 Initialization:**
  4. - 到初始化阶段, 才真正开始执行类中定义的Java程序代码, 此阶段是执行 <clinit>() 方法的过程
  5. - <clinit>() 方法是由 编译器 按照语句在源码中出现顺序, 依次自动收集类中的所有静态变量的赋值动作 静态代码块中的语句, 并进行合并.
  1. public class ClassLoad03 {
  2. public static void main(String[] args) {
  3. System.out.println(B.num); // 100
  4. /*
  5. clinit() {
  6. num = 300;
  7. num = 100;
  8. }
  9. //合并: num = 100
  10. */
  11. }
  12. }
  13. class B {
  14. static {
  15. num = 300;
  16. }
  17. static int num = 100;
  18. }
  1. - **虚拟机会保证一个类的 clinit() 方法在多线程环境中被正确地加锁**(ClassLoader.loadClass() Synchronized同步代码块)、同步. 若多个线程同时区初始化一个类, 结果也只有一个线程去执行这个类的 clinit()方法, 其它线程都 阻塞等待, 直到活动线程执行 clinit()方法完毕. -- 保证内存中某个类(内存中)只有一份对应的Class对象

4.反射机制中的常用方法

  1. * 获取Class实例
  2. 1. 调用运行类的class属性:例如 Class clazz = Person.class
  3. 2. 通过运行时类的对象: Person p= new Person; class clazz = p.getClass();
  4. 3. 调用Class的静态方法 : Class.forName("com.adong.java.person");
  5. 4. 使用类的加载器: CassLoader cl = 当前类.class.getClassLoader(); Class claszz = Cl.loadClass("com.adong.java.Rerson")
  6. * Class方法
  7. * newInstance() : 创建对应的运行时类的对象:调用空参构造器,空参构造器不能为private
  8. * Field getField(string name) : 获取运行时类的指定属性,要求属性为public
  9. * Feild对象: set(Object obj,object obj) : 参数一,为要设置对象,属性二属性值设置为多少.
  10. * get(object obj):获取属性的值
  11. * Field getDeclaredField(String name):获取运行时类指定的属性
  12. * setAccessible(true) :保证当前属性是 可以访问的
  13. * Field[] getFields(): 获取当前运行时类及其父类中声明为public访问权限的属性
  14. * Field[] getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类声明的属性)
  15. * 可以通过field[] 数组元素知道权限修饰符,数据类型,变量名
  16. * Field中方法
  17. * int getModifiers(): 返回权限修饰符,可以Modifier.toString(int modifier);获取具体
  18. * Class getType() : 获取数据类型, 获取的class对象.getName()获取具体类型
  19. * String getName(); : 获取变量名
  20. * Method getDeclareMethod(String name,形参.class) : 参数一获取方法的名称,参数二获取方法的形参列表
  21. * Object invoke(object obj,String name);参数一为方法的调用者,参数二给方法赋值的实参
  22. * 静态的化要是使用运行类.class
  23. * Method[] getMethods() : 获取当前运行时类及其所有父类声明为public权限的方法
  24. * Method[] getDeclareMethods();:获取当前运动时类中声明的所有方法(不包含父类中声明的方法)
  25. * Method中方法
  26. * Annotation[] getAnnotations():获取方法声明 的注解
  27. * int getModifiers(): 返回权限修饰符,可以Modifier.toString(int modifier);获取具体
  28. * Class getReturnType() : 返回值类型 .getName() 获取类型字符串
  29. * String getName() : 方法名
  30. * Class[] getParameterType() :参数列表
  31. * class[] getExceptionTypes() : 获取异常类型
  32. * Constructor getDEclaredConstructor(参数.class):获取指定为构造器,参数为构造器的参数列表
  33. * Constructor[] getConstructors() : 获取当前运行时类为public权限的构造器
  34. * Constructor[] getDeclaredConstructors() ; 获取当前运行时类的全部构造器
  35. * Class getSuperclass():获取运行类的父类
  36. * Type getGenericSuperclass() : 获取运行时带泛型的父类
  37. * Class[] getInterface() :获取运行时类实现的接口
  38. * Package getPackage() : 获取运行时类所在的包
  39. * Annotation[] getAnnotations():获取运行时类声明的注解
  40. * class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
  41. * 哪些类型可以有Class 对象?
  42. * 1class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  43. * 2interface:接口
  44. * 3)[]:数组
  45. * 4enum:枚举
  46. * 5annotation:注解@interface
  47. * 6primitive type:基本数据类型
  48. * 7void
  49. *
  50. //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
  51. Field[] fields = clazz.getFields();//getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性) Field[] declaredFields = clazz.getDeclaredFields();//获取属性的修饰符,数据类型,变量名Field[] declaredFields = clazz.getDeclaredFields();for(Field f : declaredFields){ //1.权限修饰符 int modifier = f.getModifiers(); System.out.print(Modifier.toString(modifier) + "\t"); //2.数据类型 Class type = f.getType(); System.out.print(type.getName() + "\t"); //3.变量名 String fName = f.getName(); System.out.print(fName); }//getMethods():获取当前运行时类及其所有父类中声明为public权限的方法 Method[] methods = clazz.getMethods();//getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法) Method[] declaredMethods = clazz.getDeclaredMethods();//获取方法的注解,权限修饰符,返回值,方法名,参数列表,抛出的异常 Class clazz = Person.class; Method[] declaredMethods = clazz.getDeclaredMethods(); for(Method m : declaredMethods){ //1.获取方法声明的注解 Annotation[] annos = m.getAnnotations(); //2.权限修饰符 System.out.print(Modifier.toString(m.getModifiers()) + "\t"); //3.返回值类型 System.out.print(m.getReturnType().getName() + "\t"); //4.方法名 System.out.print(m.getName()); //5.形参列表 Class[] parameterTypes = m.getParameterTypes(); if(!(parameterTypes == null && parameterTypes.length == 0)){ for(int i = 0;i < parameterTypes.length;i++){ if(i == parameterTypes.length - 1){ System.out.print(parameterTypes[i].getName() + " args_" + i); break; } System.out.print(parameterTypes[i].getName() + " args_" + i + ","); } } System.out.print(")"); //6.抛出的异常 Class[] exceptionTypes = m.getExceptionTypes(); if(exceptionTypes.length > 0){ System.out.print("throws "); for(int i = 0;i < exceptionTypes.length;i++){ if(i == exceptionTypes.length - 1){ System.out.print(exceptionTypes[i].getName()); break; } System.out.print(exceptionTypes[i].getName() + ","); } }//getConstructors():获取当前运行时类中声明为public的构造器 Constructor[] constructors = clazz.getConstructors();//getDeclaredConstructors():获取当前运行时类中声明的所有的构造器 Constructor[] declaredConstructors = clazz.getDeclaredConstructors();// 获取运行时类的父类 Class superclass = clazz.getSuperclass();// 获取运行时类的带泛型的父类 Type genericSuperclass = clazz.getGenericSuperclass();//获取运行时类的带泛型的父类的泛型 Type genericSuperclass = clazz.getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) genericSuperclass; //获取泛型类型 Type[] actualTypeArguments = paramType.getActualTypeArguments(); //二者选一 System.out.println(actualTypeArguments[0].getTypeName()); System.out.println(((Class)actualTypeArguments[0]).getName());// 获取运行时类实现的接口 Class[] interfaces = clazz.getInterfaces();//获取运行时类所在的包 Package pack = clazz.getPackage();//获取运行时类声明的注解 Annotation[] annotations = clazz.getAnnotations();//创建运行时类的对象 Class clazz = Person.class; Person p = (Person) clazz.newInstance();//操作运行时类中的指定的属性 Class clazz = Person.class; //创建运行时类的对象 Person p = (Person) clazz.newInstance(); //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性 Field name = clazz.getDeclaredField("name"); //2.保证当前属性是可访问的 name.setAccessible(true); //3.获取、设置指定对象的此属性值 name.set(p,"Tom"); //参数一为要操作属性的对象,参数二修改属性的值,静态的成员变量 :name.set(Person.class,"Tom");修改 //获取变量的值 System.out.println(name.get(p));//操作运行时类中的指定的方法 Class clazz = Person.class; //创建运行时类的对象 Person p = (Person) clazz.newInstance(); //1.获取指定的某个方法 : getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表 Method show = clazz.getDeclaredMethod("show", String.class); //2.保证当前方法是可访问的 show.setAccessible(true); //3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参,invoke()的返回值即为对应类中调用的方法的返回值。 Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN"); System.out.println(returnValue); System.out.println("*************如何调用静态方法*****************"); // private static void showDesc() Method showDesc = clazz.getDeclaredMethod("showDesc"); showDesc.setAccessible(true); //如果调用的运行时类中的方法没有返回值,则此invoke()返回null // Object returnVal = showDesc.invoke(null); Object returnVal = showDesc.invoke(Person.class); System.out.println(returnVal);//null //获取指定的构造器:getDeclaredConstructor():参数:指明构造器的参数列表 Constructor constructor = clazz.getDeclaredConstructor(String.class); //保证此构造器是可访问的 constructor.setAccessible(true); //调用此构造器创建运行时类的对象 Person per = (Person) constructor.newInstance("Tom");//读取配置文件Properties pros = new Properties(); //此时的文件默认在当前的module下。 //读取配置文件的方式一: //FileInputStream fis = new FileInputStream("jdbc.properties"); //FileInputStream fis = new FileInputStream("src\\jdbc1.properties"); //pros.load(fis); //读取配置文件的方式二:使用ClassLoader //配置文件默认识别为:当前module的src下 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("jdbc1.properties"); pros.load(is); String user = pros.getProperty("user"); String password = pros.getProperty("password"); System.out.println("user = " + user + ",password = " + password); //了解类的加载器 • //1.获取一个系统类加载器• 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("exer2.ClassloaderDemo").getClassLoader();• System.out.println(classloader);• //5.测试JDK提供的Object类由哪个类加载器加载• classloader = Class.forName("java.lang.Object").getClassLoader();• System.out.println(classloader);• //*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流• InputStream in = null;• in = this.getClass().getClassLoader().getResourceAsStream("exer2\\test.properties");• System.out.println(in);
  52. 反射的应用:动态代理