反射机制详解

0x01、类的加载流程

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

①加载
就是指将class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象。

②连接
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值 static{}语句块
解析 将类的二进制数据中的符号引用替换为直接引用

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

0x02.反射机制入门

通过上方阅读,当我们的程序在运行后,第一次使用某个类的时候,会将此类的class文件读取到内存,并为此类创建一个Class对象

类的加载时机

1. 创建类的实例。
2. 类的静态变量,或者为静态变量赋值。
3. 类的静态方法。
4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
5. 初始化某个类的子类。
6. 直接使用java.exe命令来运行某个主类。

public class Test {
    public static void main(String[] args) throws Exception{
        // 类的加载时机
        //  1. 创建类的实例。
        //  Student stu = new Student();

        // 2. 类的静态变量,或者为静态变量赋值。
        // Person.country = "中国";

        // 3. 类的静态方法。
        // Person.method();

        // 4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
        // Class<?> c = Class.forName("com.itheima.demo1_类的加载.Student");

        //  5. 初始化某个类的子类。
        // Zi zi = new Zi();

        // 6. 直接使用java.exe命令来运行某个主类。
    }
}

读取到内存,并为次类创建一个Class对象;这时候我们反射的内容开始了,我们第一步,就是通过反射,去获取class对象

0x03. 获取Class对象并创建对象

方式1: 通过`类名.class`获得
方式2:通过`对象名.getClass()`方法获得
方式3:通过Class类的静态方法获得:` static Class forName("类全名")`
    * 每一个类的Class对象都只有一个。

下面是获得到class对象,这是反射的三种方法,最常见的,是forName获取class对象

/*
    反射获取class对象的三种方式
 */
public class Demo3 {
    public static void main(String[] args) throws ClassNotFoundException {

        // 1、类名.class
        Class<Student> student1 = Student.class;
        System.out.println(student1);

        // 2、对象名.getClass
        Student student = new Student();
        Class<? extends Student> student2 = student.getClass();
        System.out.println(student2);

        // 3、 Class.forName("")
        Class<?> student3 = Class.forName("com.itheima_反射.Student");
        System.out.println(student3);
    }
}

这时候我们需要使用 newInstance() 方法来创建对象

由于返回值是 OBject类型的,所以我们需要转型

public class Demo3 {
    public static void main(String[] args) throws Exception {
        // 1、获取Class对象
        Class<?> student = Class.forName("com.itheima_反射.Student");
        // 2、实例化对象
        Student o = (Student) student.newInstance();

    }
}

而这一段代码,可以变为

public class Demo3 {
    public static void main(String[] args) throws Exception {
        // 等价于  Student student = new Student();
        Student student = (Student) Class.forName("com.itheima_反射.Student").newInstance();
    }
}

0x04、操作构造方法

因为Student中是有构造方法的,而这边newInstance是默认调用无参构造方法;

反射之操作构造方法的目的
    * 获得Constructor对象来创建类的对象。

Constructor类概述
    * 类中的每一个构造方法都是一个Constructor类的对象

1. Constructor getConstructor(Class... parameterTypes)
        * 根据参数类型获得对应的Constructor对象。
        * 只能获得public修饰的构造方法
 2. Constructor getDeclaredConstructor(Class... parameterTypes)
        * 根据参数类型获得对应的Constructor对象
    	* 可以是public、protected、(默认)、private修饰符的构造方法。
 3. Constructor[] getConstructors()
        获得类中的所有构造方法对象,只能获得public的
 4. Constructor[] getDeclaredConstructors()
        获得类中的所有构造方法对象
    	可以是public、protected、(默认)、private修饰符的构造方法。

1. T newInstance(Object... initargs)
 	根据指定的参数创建对象
2. void setAccessible(true)
   设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消
import java.lang.reflect.Constructor;

public class Demo {
    public static void main(String[] args) throws Exception {
        // 1、获取class对象
        Class<?> student = Class.forName("org.test.反射01.Student");
        // 2、获取Student类的带有三个参数的构造方法,并且参数的类型顺序为:String,String,int
        Constructor<?> user = student.getConstructor(String.class,String.class, int.class);
        // 3、传入参数,并使用 newInstance() 实例化对象,并向下转型
        Student o = (Student)user.newInstance("吴迪", "男", 19);
        System.out.println(o);

    }
}

这是构造方法的public修饰的获取,获取private修饰的构造方法,需要先setAccessible(true),然后再执行

具体我就不细说了,毕竟用法都一样

0x05. 操作成员方法

* Method getMethod(String name,Class...args);
      * 根据方法名和参数类型获得对应的成员方法对象,只能获得public的
* Method getDeclaredMethod(String name,Class...args);     掌握
       * 根据方法名和参数类型获得对应的成员方法对象,包括public、protected、(默认)、private的
* Method[] getMethods();
        * 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
* Method[] getDeclaredMethods();    掌握
         * 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的

Method对象常用方法
*  Object invoke(Object obj, Object... args)
    * 调用指定对象obj的该方法
    * args:调用方法时传递的参数
*  void setAccessible(true)
    设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消

我们先看看调用没有传参的成员方法
Studeng.java文件:

public class Student {
    public void show1(){
        System.out.println("public 修饰的show1方法,无参数...");
    }

    public void show1(String str,int num){
        System.out.println("public 修饰的show1方法,2个参数...");
        System.out.println("str:"+str+",num:"+num);
    }

    public void show2(){
        System.out.println("public 修饰的show2方法...");
    }

    private void show3(){
        System.out.println("private 修饰的show3方法...");
    }
}

我们这边尝试去调用show1方法

import java.lang.reflect.Method;

public class Demo {
    public static void main(String[] args) throws Exception {

        //获取class对象
        Class<?> clzz = Class.forName("org.test.反射01.Student");
        //实例化对象(默认调用无参构造方法)
        Object o = clzz.newInstance();
        // 通过class对象,调用getMethod方法去获取名为show1()的成员方法,并没有传参
        Method showM = clzz.getMethod("show1");
        showM.invoke(o);


    }
}

那我们再去调用另外一个show1()方法

import java.lang.reflect.Method;

public class Demo {
    public static void main(String[] args) throws Exception {
        //获取class对象
        Class<?> clzz = Class.forName("org.test.反射01.Student");
        //实例化对象(默认调用无参构造方法)
        Object o = clzz.newInstance();
        // 通过class对象,调用getMethod方法去获取名为show1()的成员方法,并没有传参
        Method showM = clzz.getMethod("show1",String.class,int.class);
        showM.invoke(o,"吴一凡",17);


    }
}

0x05. 操作成员变量

反射之操作成员变量的目的
    * 通过Field对象给对应的成员变量赋值和取值

Field类概述
    * 每一个成员变量都是一个Field类的对象。

通过反射获取类的成员变量

Class类中与Field相关的方法
* Field getField(String name);
    *  根据成员变量名获得对应Field对象,只能获得public修饰
* Field getDeclaredField(String name);
    *  根据成员变量名获得对应Field对象,包括public、protected、(默认)、private的
* Field[] getFields();
    * 获得所有的成员变量对应的Field对象,只能获得public的
* Field[] getDeclaredFields();
    * 获得所有的成员变量对应的Field对象,包括public、protected、(默认)、private的

通过反射访问成员变量

Field对象常用方法
void  set(Object obj, Object value) 
void setInt(Object obj, int i) 	
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z) 
void setDouble(Object obj, double d) 

Object get(Object obj)  
int	getInt(Object obj) 
long getLong(Object obj) 
boolean getBoolean(Object ob)
double getDouble(Object obj) 

void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
Class getType(); 获取属性的类型,返回Class对象。

setXxx方法都是给对象obj的属性设置使用,针对不同的类型选取不同的方法。

getXxx方法是获取对象obj对应的属性值的,针对不同的类型选取不同的方法。

package org.test.反射01;

import java.lang.reflect.Field;

public class Demo {
    public static void main(String[] args) throws Exception {
        //获取class对象
        Class<?> clzz = Class.forName("org.test.反射01.Student");
        //实例化对象(默认调用无参构造方法)
        Object stu = clzz.newInstance();
        // 通过class对象,调用getDeclaredField,获取被private修饰名为sex的属性
        Field sexF = clzz.getDeclaredField("sex");
        // 暴力反射
        sexF.setAccessible(true);
        //设置值
        sexF.set(stu,"李四");
        // 获取
        System.out.println(sexF.get(stu));// 李四



    }
}

例子:本地命令执行

Runtime.getRuntime().exec("calc.exe");

怎么通过反射机制调用呢?
首先我们要了解运行流程

也就是说,这边的Runtime.getRuntime(),其实就是Runtime runtime = new Runtime()
然后再runtime.exec()去执行命令

import java.lang.reflect.Method;

public class Demo {

    public static void main(String[] args) throws Exception {

        //获取Runtime的class对象
        Class clazz = Class.forName("java.lang.Runtime");
        // 获取exec的方法,并有一个参数
        Method execMethod = clazz.getMethod("exec", String.class);
        // 获取getRuntime方法
        Method getRuntimeMethod = clazz.getMethod("getRuntime");
        /*
            执行 getRuntime() 方法,返回runtime对象
            
        */
        Object runtime = getRuntimeMethod.invoke(clazz);
        //最后执行exec方法
        execMethod.invoke(runtime, "calc.exe");
    }
}

再整合一下,就更短了

import java.lang.reflect.Method;

public class Demo {

    public static void main(String[] args) throws Exception {
//        Class clazz = Class.forName("java.lang.Runtime");
//        clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");
    }
}

invoke 的作用是执行方法,它的第一个参数是:
如果这个方法是一个普通方法,那么第一个参数是类对象
如果这个方法是一个静态方法,那么第一个参数是

posted @ 2021-01-07 19:01  0X7e  阅读(156)  评论(0编辑  收藏  举报