• java中的反射机制


    一.类加载器

    1.类的加载过程概述

    2.类的加载时机

    3.类加载器的概述

    4.类加载器的分类及各类类加载器的作用

    5.类加载器的双亲委派机制

    二.反射机制

    1.反射概述

     2.获取class文件对象的三种方式

     三.通过反射获取构造方法,成员变量和成员方法

    1.获取构造方法

    2.获取成员变量

    3.获取成员方法

     四.动态代理

    1.动态代理概述

    2.动态代理的特点,作用和分类

     3.如何创建代理对象

    4.动态代理的神奇之处


    一.类加载器

    1.类的加载过程概述

    当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过  加载,连接,初始化三步  来实现对这个类的初始化。

    • 加载  就是指将class文件读入内存,并为之创建一个Class对象。 (任何类被使用时系统都会建立一个Class对象。)
    • 连接  包含验证,准备,解析等过程。验证,是否有正确的内部结构,并和其他类协调一致。准备,负责为类的静态成员分配内存,并设置默认初始化值。解析,把类中的符号引用转换为直接引用。
    • 初始化  就是我们以前学过的初始化步骤。

    2.类的加载时机

    •     创建类的实例 new Student()  
    •     访问类的静态变量,或者为静态变量赋值 Math.PI   Math.class  
    •     调用类的静态方法  Math.abs()
    •     使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    •     初始化某个类的子类
    •     直接使用java.exe命令来运行某个主类

    3.类加载器的概述

    类加载器负责将.class文件加载到内在中,并为之  生成对应的Class对象

    4.类加载器的分类及各类类加载器的作用

    • Bootstrap ClassLoader  根类加载器  , 也被称为引导类加载器,负责Java核心类的加载  。比如System,String等。在JDK中JRE的lib目录下rt.jar文件中  
    • Extension ClassLoader  扩展类加载器 , 负责JRE的扩展目录中  jar包的加载  。在JDK中JRE的lib目录下ext目录
    • Sysetm ClassLoader  系统类加载器  , 负责在JVM启动时  加载来自java命令的class文件 ,以及classpath环境变量所指定的jar包和类路径

    5.类加载器的双亲委派机制

    工作原理

    • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
    • 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达项层的启 ​ 动类加载器; ​
    • 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制。

    为什么要采用双亲委派机制?

    • 避免核心API被篡改
    • 避免类的重复加载  

    说通俗点, 如果可以随意的就去执行自己写的String了,那么有人进行恶意攻击, 给你发送一个String类,项目就会直接崩溃! 所以这也是一种保护。


    二.反射机制

    1.反射概述

            JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制。要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。反射就是在运行状态中的一种动态调用方法或者属性的一种机制.就是  获取字节码文件对象,然后剖析该类中存在哪些构造方法(Constructor),哪些成员变量(Field),哪些成员方法(Method)

     2.获取class文件对象的三种方式

    • Object类的  getClass()  方法
    • 静态  属性class
    • Class类中静态方法  forName()      补:public static Class forName(String className), 其中的className表示的是一个类对应的全类名(就是需要加上包名)
    1. public class MyTest {
    2. public static void main(String[] args) throws ClassNotFoundException {
    3. //方式1:getClass方法
    4. Student student = new Student();
    5. Student student2 = new Student();
    6. Classextends Student> aClass = student.getClass();
    7. Classextends Student> aClass1 = student2.getClass();
    8. //方式2:每个类,都有一个 class属性,通过他就可以获取
    9. Class studentClass = Student.class;
    10. //方式3:Class类中的一个静态方法forName
    11. Class aClass2 = Class.forName("org.xingyun.demo2.Student");
    12. }
    13. }
    14. package org.xingyun.demo2;
    15. public class Student {
    16. }

     三.通过反射获取构造方法,成员变量和成员方法

    1.获取构造方法

            之前我们创建一个类的对象,通过new调用构造方法来创建,因为只要能调用到这个类的构造方法,就能创建该类的对象。现在采用反射的方式来创建一个类的对象, 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。 通过这个类的字节码文件对象,就能够获取到这个类的构造方法对象。  获取构造方法对象的方法如下:

    • public Constructor[] getConstructors()                       //获取所有的  公共的构造方法  对象不包含私有的
    • public Constructor[] getDeclaredConstructors()         //获取  所有的构造方法  对象,包括私有的
    • public Constructor getConstructor(Class... parameterTypes)                 //获取  单个的公共的构造方法  对象, 不包含私有的
    • public Constructor getDeclaredConstructor(Class... parameterTypes)   //获取  单个的构造方法  对象,包含私有的
    1. public class MyTest {
    2. public static void main(String[] args) throws ClassNotFoundException {
    3. //获取字节码文件对象(方式3)
    4. Class aClass = Class.forName("org.xingyun.demo3.Student");
    5. //获取所有的公共的构造方法对象
    6. Constructor[] constructors = aClass.getConstructors();
    7. //循环输出
    8. for (Constructor constructor : constructors) {
    9. System.out.println(constructor);
    10. }
    11. //获取所有的构造方法对象包括私有的构造方法对象
    12. Constructor[] declaredConstructors = aClass.getDeclaredConstructors();
    13. for (Constructor declaredConstructor : declaredConstructors) {
    14. System.out.println(declaredConstructor);
    15. }
    16. //获取空参的构造方法对象
    17. Constructor constructor = aClass.getConstructor();
    18. System.out.println(constructor);
    19. Constructor constructor2 = aClass.getConstructor(String.class);
    20. System.out.println(constructor2);
    21. Constructor constructor3 = aClass.getConstructor(String.class, int.class);
    22. System.out.println(constructor3);
    23. //获取私有的构造方法对象
    24. Constructor constructor4 = aClass.getDeclaredConstructor(String.class, int.class, double.class);
    25. System.out.println(constructor4);
    26. }
    27. }

    2.获取成员变量

    • public Field[] getFields()                                //获取所有  公共的成员变量  对象包含从父类继承过来的
    • public Field[] getDeclaredFields()                  //获取  所有的成员变量  对象,包含私有的 也包含从父类继承过来的成员变量
    • public Field getField(String name)                 //获取  单个公共的成员变量  对象
    • public Field getDeclaredField(String name)   //获取  单个成员变量  对象(包含私有成员变量)
    • set();                                                               //给成员变量设置值,set后面可以跟很多数据类型,因此可以设置不同数据类型的数据,例如:setInt()等等.
    • get();                                                               //获取成员变量的值
    • setAccessible(true);                                      //取消私有权限检查(私有成员方法必须取消的哈)
    1. public class MyTest {
    2. public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
    3. //获取字节码文件对象,org.xingyun.demo5为路径名
    4. Class aClass = Class.forName("org.xingyun.demo5.Teacher");
    5. //获取所有的成员变量对象,包含私有的
    6. Field[] declaredFields = aClass.getDeclaredFields();
    7. for (Field declaredField : declaredFields) {
    8. System.out.println(declaredField);
    9. }
    10. //获取所有公共的成员变量对象
    11. Field[] fields = aClass.getFields();
    12. for (Field field : fields) {
    13. System.out.println(field);
    14. }
    15. //获取单个公共的成员变量对象
    16. Field nameField = aClass.getField("name");
    17. System.out.println(nameField);
    18. //获取单个成员变量对象(包含私有成员变量)
    19. Field sal = aClass.getDeclaredField("sal");
    20. System.out.println(sal);
    21. //给姓名字段设置值
    22. nameField.set(teacher, "张三");
    23. //获取字段对象的值
    24. String nameValue = (String) nameField.get(teacher);
    25. System.out.println(nameValue);
    26. //给年龄字段设置值
    27. Field ageField = aClass.getField("age");
    28. ageField.setInt(teacher, 20);
    29. //取出年龄字段值
    30. int anInt = ageField.getInt(teacher);
    31. System.out.println(anInt);
    32. Field salField = aClass.getDeclaredField("sal");
    33. salField.setAccessible(true); //取消私有权限的检查
    34. salField.setDouble(teacher, 3.25);
    35. double aDouble = salField.getDouble(teacher);
    36. System.out.println(aDouble);
    37. }
    38. }
    39. public class Teacher {
    40. public String name;
    41. public int age;
    42. private double sal;
    43. }

    3.获取成员方法

    • public Method[] getMethods()                                 //获取所有的  公共的成员方法  不包含私有的 包含从父类继承过来的过来的公共方法
    • public Method[] getDeclaredMethods()                   //获取自己的  所有成员方法  包含私有的
    • public Method getMethod(String name,Class... parameterTypes)          //获取  单个的公共方法  不包含私有的(参数1: 方法名称  参数2:方法行参的class 对象)
    • public Method getDeclaredMethod(String name,Class... parameterTypes)         //获取  单个成员方法  包括私有的
    • dogClass.newInstance();                                        //通过空参构造创建对象
    • show.invoke(dog1);                                                //调用方法执行,show为方法名,dog1为创建的对象   
    1. public class MyTest {
    2. public static void main(String[] args) throws NoSuchMethodException {
    3. //获取字节码文件对象
    4. Class dogClass = Dog.class;
    5. //获取所有的公共的成员方法对象,包括父类的。
    6. Method[] methods = dogClass.getMethods();
    7. for (Method method : methods) {
    8. System.out.println(method);
    9. }
    10. //获取所有的成员方法对象包括私有的
    11. Method[] methods2 = dogClass.getDeclaredMethods();
    12. for (Method method : methods2) {
    13. System.out.println(method);
    14. }
    15. //获取单个公共的成员方法对象
    16. Method show = dogClass.getMethod("show"); //传入需要获取的方法的方法名
    17. //获取单个公共的带参的成员方法(在后面获取数据类型的类的字节码文件对象)
    18. Method haha = dogClass.getMethod("haha", String.class, int.class);
    19. //获取单个带参的成员方法(在后面获取数据类型的类的字节码文件对象),可包含私有成员方法
    20. Method test = dogClass.getDeclaredMethod("test", String.class);
    21. //通过空参构造创建对象,用下面这个也可以
    22. Dog dog1 = dogClass.newInstance();
    23. Method show = dogClass.getMethod("show");//传入方法名
    24. //调用方法执行
    25. show.invoke(dog1);
    26. Method haha = dogClass.getMethod("haha", String.class, int.class);
    27. Double o = (Double) haha.invoke(dog1, "小黑", 2);
    28. System.out.println(o);
    29. Method test = dogClass.getDeclaredMethod("test", String.class);
    30. test.setAccessible(true); //取消私有权限检查
    31. test.invoke(dog1, "abc");
    32. }
    33. }
    34. public class Dog {
    35. public void show() {
    36. System.out.println("我是小白");
    37. }
    38. public double haha(String name, int age) {
    39. System.out.println("哈哈" + name + "===" + age);
    40. return 3.25;
    41. }
    42. private void test(String name) {
    43. System.out.println("私有的方法调用了" + name);
    44. }
    45. }

     四.动态代理

    1.动态代理概述

            代理形象点就是, 本来应该自己做的事情,却请了别人来做,被请的人那就是代理对象。而  动态代理便是在程序运行过程中产生的这个对象  。在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的静态方法获取一个代理对象 ,  最终调用InvocationHandler的方法来实现。

    2.动态代理的特点,作用和分类

    特点字节码随用随创建,随用随加载
    作用不修改源码的基础上对方法增强
    涉及的类Proxy
    提供者JDK官方
    分类
    • 基于接口的动态代理
    • 基于接口的动态代理
    • 基于接口的动态代理

     3.如何创建代理对象

    强大的代理cglib,使用Proxy类中的  newProxyInstance方法来创建代理对象  。需要注意的是  被代理类最少实现一个接口, 如果没有则不能使用。

    public static Object newProxyInstance(ClassLoader loader,Class[]interfaces,InvocationHandler h)               创建代理对象

    newProxyInstance方法中的参数:

    • ClassLoader: 类加载器. 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
    • Class[]:字节码数组,  代理对象需要实现的接口,  它是用于让代理对象和被代理对象有相同方法。属于固定写法。
    • InvocationHandler: 用于提供增强的代码,  它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。一般此接口的实现类都是谁用谁写。

    InvocationHandler Object invoke(Object proxy,Method method,Object[] args)         同时绑定的一个方法,  执行被代理对象的任何接口方法都会经过该方法

    new InvocationHandler() {
                        /**
                         * 作用:执行被代理对象的任何接口方法都会经过该方法
                         * 方法参数的含义
                         * @param proxy   代理对象的引用
                         * @param method  当前执行的方法
                         * @param args    当前执行方法所需的参数
                         * @return        和被代理对象方法有相同的返回值
                         */
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        }
                    }

     InvocationHandler 方法调用的实际处理者,代理对象的方法调用都会转发到这里。Proxy.newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。

    4.动态代理的神奇之处

    • 代理对象是在程序运行时产生的,而不是编译期;
    • 对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体

     (小编也在努力学习更多哟!以后再慢慢分享的啦!)    

    希望对友友们有所帮助!!!!

                                  

  • 相关阅读:
    【附源码】计算机毕业设计JAVA学习自律养成小程序前台.mp4
    【667. 优美的排列 II】
    力扣每日一题:1710. 卡车上的最大单元数【自定义排序+贪心】
    Vmware虚拟机 linux 固定IP
    2023年软件测试工具总结 —— 接口测试工具
    Mysql通过Canal同步Elasticsearch
    Spring注解驱动之声明式事务源码分析
    【编程题】【Scratch二级】2022.09 绘制图形
    SpringBoot整合MQTT(MqttClient)
    flex布局(理论+案例解释)
  • 原文地址:https://blog.csdn.net/naoguoteng/article/details/126678177