反射的概述

关于反射的理解

Reflection(反射)是被视为动态语言的关键,反射机制允许程序再执行期间借助于反射的API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
框架 = 反射 + 注解 + 设计模式

体会反射机制的动态性

动态地去根据需求在运行时创造类的对象

反射机制能提供的功能

在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理

相关API

java.lang.Class:反射的源头
java.lang.reflect.Method:方法
java.lang.reflect.Field:属性
java.lang.reflect.Constructor:构造器
….

Class类的理解与获取Class的实例

Class类的理解

类的加载过程

程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,此过程就称为类的加载。
加载到内存中的这个类,我们就称为”运行时类”,此”运行时类”,就作为Class的一个实例

换句话说,Class的实例就对应着一个运行时类

加载到内存中的”运行时类”,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此”运行时类”

获取Class实例的几种方式

前三种需要掌握

  1. /*
  2. 获取Class的实例的方式
  3. */
  4. @Test
  5. public void test3() throws ClassNotFoundException {
  6. //
  7. //方式一:调用运行时类的属性:.class
  8. Class clazz = PersonTest.class;
  9. System.out.println(clazz);
  10. //方式二:通过运行时类的对象
  11. PersonTest p1 = new PersonTest();
  12. Class clazz2 = p1.getClass();
  13. System.out.println(clazz2);
  14. //方式三:调用Class的静态方法 forName(String classPath)
  15. Class clazz3 = Class.forName("com.zcx.test.Learn.JavaSenior.reflection.PersonTest");
  16. System.out.println(clazz3);
  17. System.out.println(clazz2 == clazz);
  18. System.out.println(clazz2 == clazz3);
  19. //方式四:使用类的加载器 ClassLoader (了解)
  20. ClassLoader classLoader = ReflectionTest.class.getClassLoader();
  21. Class clazz4 = classLoader.loadClass("com.zcx.test.Learn.JavaSenior.reflection.PersonTest");
  22. System.out.println(clazz4);
  23. System.out.println(clazz4 == clazz);
  24. }

总结:创建类的对象的方式

new + 构造器
要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在。可以调用其静态方法来创建Xxx对象
通过反射实现

Class实例可以是是哪些结构的说明

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

    了解ClassLoader

    类的加载过程

    当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过三个步骤来对该类进行初始化

    类的加载(Load)

    将类的class文件通过java.exe命令读入内存,并为之创建一个java.lang.Class的实例。此过程由类的加载器完成

    将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址) 所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与

类的链接(Link)

将类的二进制数据合并到JRE中

将Java类的二进制代码合并到JVM的运行状态之中的过程
验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程

类的初始化(Initialize)

JVM负责对类进行初始化

执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步

类的加载器的作用

类加载的作用

将class文件字节码内容加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class的对象,作为方法区中类数据的访问入口

类缓存

标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象

类的加载器的分类

引导类加载器

C++编写,JVM自带的类加载器,负责Java平台和辛苦,用来装载核心类库,该加载器无法直接获取

扩展类加载器

负责jre/lib/ext下的jar包装入工作库

系统类加载器

负责java -classpath所指的目录下的类与jar包装入工作库,是最常用的类加载器

  1. @Test
  2. public void test1() {
  3. //对于自定义类,使用系统类加载器进行加载
  4. ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
  5. System.out.println(classLoader);
  6. //调用系统类加载器的getParent(),获取扩展类加载器
  7. ClassLoader classLoader1 = classLoader.getParent();
  8. System.out.println(classLoader1);
  9. //调用扩展类加载器的getParent(),无法获取引导类加载器
  10. //引导类加载器主要负责加载java的核心类库,无法加载自定义类的(获取不到)
  11. ClassLoader classLoader2 = classLoader1.getParent();
  12. System.out.println(classLoader2);
  13. ClassLoader classLoader3 = String.class.getClassLoader();
  14. System.out.println(classLoader3);
  15. }

Java类编译、运行的执行的流程

image.png

使用ClassLoader加载src目录下的配置文件

  1. @Test
  2. public void test2() throws Exception {
  3. Properties pros = new Properties();
  4. //读取配置文件方式一:
  5. //此时的文件默认在当前module下
  6. // FileInputStream fis = new FileInputStream("jdbc.properties");
  7. // pros.load(fis);
  8. //读取配置文件方式二:
  9. //此时的文件默认在当前MODULE的src下
  10. ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
  11. InputStream is = classLoader.getResourceAsStream("application.yml");
  12. pros.load(is);
  13. System.out.println(pros.getProperty("name"));
  14. System.out.println(pros.getProperty("password"));
  15. }

反射的应用

创建运行时类的对象

说明

newInstance():调用此方法,创建对应的运行时类的对象

内部调用了运行时类的空参构造器

要想此方法正常的创建运行时类的对象,要求: 1.运行时类必须提供空参的构造器 2.空参的构造器的访问权限得够,通常设置为public

在javabean中要求提供一个public的空参构造器? 便于通过反射,创建运行时类的对象 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

代码举例

  1. @Test
  2. public void test1() throws IllegalAccessException, InstantiationException {
  3. Class clazz = PersonTest.class;
  4. Object obj = clazz.newInstance();
  5. System.out.println(obj);
  6. }

获取运行时类的完整结构

获取属性

FieldTest.java

获取方法

MethodTest.java

其他结构

OtherTest.java

调用运行时类的指定结构

调用指定的属性

  1. /*
  2. 调用运行时类中指定的结构:属性
  3. */
  4. @Test
  5. public void testField() throws NoSuchFieldException, IllegalAccessException, InstantiationException {
  6. Class clazz = Person.class;
  7. //创建运行时类的对象
  8. Person person = (Person) clazz.newInstance();
  9. transferField1(clazz, person, "id");
  10. transferField2(clazz, person, "name");
  11. }
  12. /**
  13. * 属性赋值方式一(通常不采用此方法)
  14. */
  15. private void transferField1(Class clazz, Person person, String fieldName) throws NoSuchFieldException, IllegalAccessException {
  16. //获取指定的属性:要求运行时类中属性声明为public
  17. Field id = clazz.getField(fieldName);
  18. /*
  19. 设置当前属性的值
  20. set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少
  21. */
  22. id.set(person, 1001);
  23. /*
  24. 获取当前属性的值
  25. get():参数1:获取哪个对象的当前属性值 参数2:将此属性值设置为多少
  26. */
  27. int pId = (int) id.get(person);
  28. System.out.println(pId);
  29. }
  30. /**
  31. * 属性赋值方式二(常用)
  32. */
  33. private void transferField2(Class clazz, Person person, String fieldName) throws NoSuchFieldException, IllegalAccessException {
  34. //1.getDeclaredField(String name):获取运行时类中指定变量名的属性
  35. Field name = clazz.getDeclaredField(fieldName);
  36. //2.保证当前属性是可访问的
  37. name.setAccessible(true);
  38. //3.获取,设置指定对象的此属性值
  39. name.set(person, "Tom");
  40. System.out.println(name.get(person));
  41. }

调用指定的方法

  1. /*
  2. 调用运行时类中指定的结构:方法
  3. */
  4. @Test
  5. public void testMethod() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
  6. Class clazz = Person.class;
  7. //创建运行时类的对象
  8. Person person = (Person) clazz.newInstance();
  9. //获取指定的某个方法
  10. //getgetDeclaredMethod(): 参数1:指明获取的方法的名称 参数2:指明获取的方法的形参列表
  11. Method show = clazz.getDeclaredMethod("show", String.class);
  12. show.setAccessible(true);
  13. //invoke(): 参数1:方法的调用者 参数2:给方法形参赋值的实参
  14. //invoke()的返回值即为对应类中调用的方法的返回值
  15. String returnValue = (String) show.invoke(person, "CHN");
  16. System.out.println(returnValue);
  17. System.out.println("************************如何调用静态方法***************************");
  18. //private static void showDesc()
  19. Method showDesc = clazz.getDeclaredMethod("showDesc");
  20. showDesc.setAccessible(true);
  21. //如果调用的运行时类中的方法没有返回值,则此invoke()返回null
  22. Object returnVal = showDesc.invoke(Person.class);
  23. System.out.println(returnVal);
  24. }

调用指定的构造器

  1. /*
  2. 调用运行时类中指定的结构:构造器
  3. */
  4. @Test
  5. public void testConstructor() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  6. Class clazz = Person.class;
  7. //private Person(String name)
  8. //获取指定的构造器
  9. //getDeclaredConstructor(): 参数:指明构造器的参数列表
  10. Constructor constructor = clazz.getDeclaredConstructor(String.class);
  11. //保证此构造器是可访问的
  12. constructor.setAccessible(true);
  13. //调用此构造器创建运行时类的对象
  14. Person tom = (Person) constructor.newInstance("Tom");
  15. System.out.println(tom);
  16. }

动态代理

静态代理

StaticProxyTest.java

动态代理

DynamicProxyTest.java