java中的反射机制

发布于:2022-12-29 ⋅ 阅读:(525) ⋅ 点赞:(0)

一.类加载器

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表示的是一个类对应的全类名(就是需要加上包名)
public class MyTest {
    public static void main(String[] args) throws ClassNotFoundException {
        //方式1:getClass方法
        Student student = new Student();
        Student student2 = new Student();
        Class<? extends Student> aClass = student.getClass();
        Class<? extends Student> aClass1 = student2.getClass();


        //方式2:每个类,都有一个 class属性,通过他就可以获取
        Class<Student> studentClass = Student.class;

        //方式3:Class类中的一个静态方法forName
        Class<?> aClass2 = Class.forName("org.xingyun.demo2.Student");
    }
}

package org.xingyun.demo2;

public class Student {
}

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

1.获取构造方法

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

  • public Constructor<?>[] getConstructors()                       //获取所有的  公共的构造方法  对象不包含私有的
  • public Constructor<?>[] getDeclaredConstructors()         //获取  所有的构造方法  对象,包括私有的
  • public Constructor<T> getConstructor(Class<?>... parameterTypes)                 //获取  单个的公共的构造方法  对象, 不包含私有的
  • public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)   //获取  单个的构造方法  对象,包含私有的
public class MyTest {
    public static void main(String[] args) throws ClassNotFoundException {
       
        //获取字节码文件对象(方式3)
        Class<?> aClass = Class.forName("org.xingyun.demo3.Student");

        //获取所有的公共的构造方法对象
        Constructor<?>[] constructors = aClass.getConstructors();
        //循环输出
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }

        //获取所有的构造方法对象包括私有的构造方法对象
        Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

        //获取空参的构造方法对象
        Constructor<?> constructor = aClass.getConstructor();
        System.out.println(constructor);
        Constructor<?> constructor2 = aClass.getConstructor(String.class);
        System.out.println(constructor2);
        Constructor<?> constructor3 = aClass.getConstructor(String.class, int.class);
        System.out.println(constructor3);

        //获取私有的构造方法对象
        Constructor<?> constructor4 = aClass.getDeclaredConstructor(String.class, int.class, double.class);
        System.out.println(constructor4);
    }
}

2.获取成员变量

  • public Field[] getFields()                                //获取所有  公共的成员变量  对象包含从父类继承过来的
  • public Field[] getDeclaredFields()                  //获取  所有的成员变量  对象,包含私有的 也包含从父类继承过来的成员变量
  • public Field getField(String name)                 //获取  单个公共的成员变量  对象
  • public Field getDeclaredField(String name)   //获取  单个成员变量  对象(包含私有成员变量)
  • set();                                                               //给成员变量设置值,set后面可以跟很多数据类型,因此可以设置不同数据类型的数据,例如:setInt()等等.
  • get();                                                               //获取成员变量的值
  • setAccessible(true);                                      //取消私有权限检查(私有成员方法必须取消的哈)
public class MyTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //获取字节码文件对象,org.xingyun.demo5为路径名
        Class<?> aClass = Class.forName("org.xingyun.demo5.Teacher");
        //获取所有的成员变量对象,包含私有的
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }

        //获取所有公共的成员变量对象
        Field[] fields = aClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        //获取单个公共的成员变量对象
        Field nameField = aClass.getField("name");
        System.out.println(nameField);

        //获取单个成员变量对象(包含私有成员变量)
        Field sal = aClass.getDeclaredField("sal");
        System.out.println(sal);


        //给姓名字段设置值
        nameField.set(teacher, "张三");
        //获取字段对象的值
        String nameValue = (String) nameField.get(teacher);
        System.out.println(nameValue);

        //给年龄字段设置值
        Field ageField = aClass.getField("age");
        ageField.setInt(teacher, 20);
        //取出年龄字段值
        int anInt = ageField.getInt(teacher);
        System.out.println(anInt);

        Field salField = aClass.getDeclaredField("sal");
        salField.setAccessible(true);      //取消私有权限的检查
        salField.setDouble(teacher, 3.25);
        double aDouble = salField.getDouble(teacher);
        System.out.println(aDouble);
    }
}


public class Teacher {
    public String name;
    public int age;
    private double sal;
}

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为创建的对象   
public class MyTest {
    public static void main(String[] args) throws NoSuchMethodException {
        //获取字节码文件对象
        Class<Dog> dogClass = Dog.class;
        //获取所有的公共的成员方法对象,包括父类的。
        Method[] methods = dogClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        
        //获取所有的成员方法对象包括私有的
        Method[] methods2 = dogClass.getDeclaredMethods();
        for (Method method : methods2) {
            System.out.println(method);
        }
       //获取单个公共的成员方法对象
        Method show = dogClass.getMethod("show");      //传入需要获取的方法的方法名

       //获取单个公共的带参的成员方法(在后面获取数据类型的类的字节码文件对象)
        Method haha = dogClass.getMethod("haha", String.class, int.class);

       //获取单个带参的成员方法(在后面获取数据类型的类的字节码文件对象),可包含私有成员方法
        Method test = dogClass.getDeclaredMethod("test", String.class);

        //通过空参构造创建对象,用下面这个也可以
        Dog dog1 = dogClass.newInstance();
        Method show = dogClass.getMethod("show");//传入方法名
        //调用方法执行
        show.invoke(dog1);
        

        Method haha = dogClass.getMethod("haha", String.class, int.class);
        Double o = (Double) haha.invoke(dog1, "小黑", 2);
        System.out.println(o);

        
        Method test = dogClass.getDeclaredMethod("test", String.class);
        test.setAccessible(true);        //取消私有权限检查
        test.invoke(dog1, "abc");
    }
}




public class Dog {

    public void show() {
        System.out.println("我是小白");
    }

    public double haha(String name, int age) {
        System.out.println("哈哈" + name + "===" + age);

        return 3.25;
    }

    private void test(String name) {
        System.out.println("私有的方法调用了" + name);
    }

}

 四.动态代理

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()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体

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

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

                              

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到