Java 反射机制学习

前言

java反射算是java学习过程中不可绕过的一关,在第一次学习的时候迷迷糊糊,这次再来学习一遍。

java 反射

反射允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。
反射的核心是JVM在运行时动态加载类或调用方法或访问属性。

class 类

我们正常类加载的方式是

  • 导入包名--->通过new实例化--->取得实例化对象
    而反射是
  • 实例化对象--->getclass()方法--->得到包名

实现反射相关API

java.lang.Class           代表一个类
java.lang.reflect.Method  代表类的方法
java.lang.reflect.Field   代表类的成员属性
java.lang.reflect.Constructor  代表类的构造方法

列举一个demo
User.java

public class User extends Person{
    private int id;
    private String username;
    private String password;
    public int age;

    public User(){}
    public User(int id, String username, String password, int age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
    private void show(String username,String password) {
        System.out.println("用户名:"+username+",密码:"+password);
    }

    public void study(String username) {
        System.out.println(username+"正在学习~");
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }
}

Person类

public class Person {
    public String name;
    private int age;

    public Person(){}
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    private String show(String name) {
        //System.out.println(name+"");
        return name+"正在洗澡";
    }
    private static void teststatic(){
        System.out.println("static method start");
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

}

获取class类对象的四种方法

    public void getclassTest(){
        //1.获取class对象
        User user1 = new User();
        Class c1 = user1.getClass();
        System.out.println("第一种"+c1);

        //2.通过类的方式获取
        Class c2 = User.class;
        System.out.println("第二种"+c2);

        //3.class.forName() 将字节码文件加载到内存
        Class c3;
        try {
            c3 = Class.forName("com.atguigu.java.User");
            System.out.println("第三种"+c3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //4. classLoader 类加载器
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class aClass = null;
        try {
            aClass = classLoader.loadClass("com.atguigu.java.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.print("第四种"+aClass);
    }
image.png

获取成员属性

getFields()          获取所有public修饰的成员属性,包括父类
getDeclaredFields()  获得当前类的所有属性,包括private
    public void test() throws ClassNotFoundException {
        Class c = Class.forName("com.atguigu.java.User");
        Field[] fields = c.getDeclaredFields();
        //Field[] fields = c.getFields();
        for (Field f : fields) {
            System.out.println(f);
        }
    }
image.png

获取构造方法

getConstructors()    获得所有public修饰的构造方法,包括父类
getDeclaredConstructors()  获得当前类的所有构造方法,包括私有
getDeclaredConstructor()   获得当前类指定的构造方法
    public void test() throws ClassNotFoundException {
        Class clazz = Class.forName("com.atguigu.java.User");
        Constructor[] cs = clazz.getDeclaredConstructors();
        //Constructor[] cs = clazz.getConstructors();
        for (Constructor con : cs) {
            System.out.println(con);
        }
    }
image.png
    public void test() throws ClassNotFoundException, NoSuchMethodException {
        Class clazz = Class.forName("com.atguigu.java.User");
        Constructor declaredConstructor = clazz.getDeclaredConstructor();
        Constructor declaredConstructor1 = clazz.getDeclaredConstructor(int.class, String.class, String.class, int.class);
    }

获取指定的构造方法,可以是无参构造方法,也可以是有参构造方法。
如果想要获取父类的构造方法呢?

    public void test() throws ClassNotFoundException {
        Class clazz = Class.forName("com.atguigu.java.User");
        Class superclass = clazz.getSuperclass();
        Constructor[] cs = superclass.getDeclaredConstructors();
        for (Constructor con : cs) {
            System.out.println(con);
        }
    }
image.png

获取成员方法

getMethods()        获取当前类和父类的所有公有的public方法,并以数组返回
getDeclaredMethods() 只查询当前类的所有定义的方法(包含private),并以数组返回
getDeclaredMethod() 获取某个方法,传入的第一个参数为方法名,第二个参数为方法参数
    public void test() throws ClassNotFoundException {
        Class c = Class.forName("com.atguigu.java.User");
        Method[] method = c.getDeclaredMethods();
        //Method[] method = c.getMethods();
        for (Method meth : method) {
            System.out.println(meth);
        }
    }
image.png

如果想要获取show()方法,使用getDeclaredMethod()方法,并传入方法名,以及形参。又因为该方法为private,所以设置访问权限。
注:setAccessible作用是启动和禁止访问安全检查的开关,参数为true表示反射的对象在使用时应该取消java语言访问检查,参数为false则表示反射的对象实施对java语言的访问检查。

    public void test() throws ClassNotFoundException, NoSuchMethodException {
        Class c = Class.forName("com.atguigu.java.User");
        Method[] method = c.getDeclaredMethods();
        Method show = c.getDeclaredMethod("show", String.class,String.class);
        show.setAccessible(true);
    }

反射调用属性

调用public age属性

    public void test() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Class c = Class.forName("com.atguigu.java.User");
        Field agefield = c.getField("age");

        User u = new User(1,"cseroad","123456",18);
        System.out.println(agefield.getInt(u));
    }

调用private username属性

    public void test() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Class c = Class.forName("javase.bibi.reflection.User");
        Field usernamefield = c.getDeclaredField("username");
        usernamefield.setAccessible(true);//忽略访问权限

        User u = new User(1,"cseroad","123456",18);
        System.out.println(usernamefield.get(u));
    }

反射调用方法

调用public方法

    public void test() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class c = Class.forName("com.atguigu.java.User");
        Method study = c.getDeclaredMethod("study", String.class);
        User u = new User(1,"vxeroad","123456",18);
        study.invoke(u, "cseroad");
    }
image.png

调用private方法

    public void test() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class c = Class.forName("com.atguigu.java.User");
        Method show = c.getDeclaredMethod("show", String.class, String.class);
        show.setAccessible(true);
        User u = new User(1,"vxeroad","123456",18);
        show.invoke(u, "cseroad","1q2w3e4r");
    }
image.png

反射创建对象

上面的例子我们看到还是用new的方式来创建的对象。也可以通过反射创建对象。

Class.newInstance()   只能够调用无参的构造方法,即默认的构造方法;要求构造方法必须是public类型的。
Constructor.newInstance() 可以根据传入的参数,调用任意的构造方法; 特定情况下可以调用私有的构造方法。

尝试反射创建对象并调用public study() 方法

    public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 1. 获得class对象
        Class clazz = Class.forName("com.atguigu.java.User");
        // 2. 获取无参构造器
        Constructor declaredConstructor = clazz.getDeclaredConstructor();
        // 3. Constructor.newInstance() 创建对象
        Object cseroad = declaredConstructor.newInstance();
        User user = (User) cseroad;
        // 4. 获取方法
        Method show = clazz.getDeclaredMethod("study", String.class);
        show.invoke(user, "cseroad");
    }

因为User类有两个构造方法,我们尝试用另一个有参构造器。

    public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 1. 获得class对象
        Class clazz = Class.forName("com.atguigu.java.User");
        // 2. 获取无参构造器
        Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
        // 3. Constructor.newInstance() 创建对象
        Object cseroad = declaredConstructor.newInstance(1,"xx","xx",18);
        User user = (User) cseroad;
        // 4. 获取方法
        Method show = clazz.getDeclaredMethod("study", String.class);
        show.invoke(user,"cseroad");
    }

为了区别实参和形参,在User类的study()方法中调整为

    public void study(String username) {
        System.out.println("我的年龄:"+age);
        System.out.println(username+"正在学习~");
    }
image.png

结论为:在newInstance创建对象的时候,对应有参构造器,传入形参。
再尝试反射创建对象并调用public show() 私有方法

    public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 1. 获得class对象
        Class clazz = Class.forName("com.atguigu.java.User");
        // 2. 获取无参构造器
        Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
        // 3. Constructor.newInstance() 创建对象
        Object cseroad = declaredConstructor.newInstance(1,"xx","xx",18);
        User user = (User) cseroad;
        // 4. 获取方法
        Method show = clazz.getDeclaredMethod("show", String.class,String.class);
        show.setAccessible(true);
        show.invoke(user,"cseroad","123456");
    }
image.png

应用

我们趁热打铁,看看如何反射调用经常用到的Runtime类。
正常使用Runtime类执行系统命令为

Process p = Runtime.getRuntime().exec("calc");

而反射过程是加载类调用方法。
首先获得Runtime类

Class clazz = Class.forName("java.lang.Runtime");

然后看一下该类的构造器。

Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor con : declaredConstructors){
   System.out.print(con);
}
image.png

只有一个私有的无参构造器。
获取该构造器并设置访问权限。

Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);

有了构造器就可以创建对象。

Object o = declaredConstructor.newInstance();

然后获取指定的方法,之前也可以查看当前类的所有方法。

Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method meth : declaredMethods){
   System.out.println(meth);
}
image.png

可以看到exec()方法对应的都是public属性,且可以传入String类型,也可以传入String[]类型。
比如使用java.lang.String
那在getDeclaredMethod()方法后传入的第一个参数为exec()方法,第二个参数为String.class。

Method show = clazz.getDeclaredMethod("exec", String.class);

并使用invoke()方法执行exec()方法

show.invoke(o,"touch 1.txt");

所有以上代码为

    public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 1. 获得class对象
        Class clazz = Class.forName("java.lang.Runtime");
        // 2. 获取无参构造器
        Constructor declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        // 3. Constructor.newInstance() 创建对象
        Object o = declaredConstructor.newInstance();
        //Runtime o1 = (Runtime) o;
        // 4. 获取方法
        Method show = clazz.getDeclaredMethod("exec", String.class);
        show.invoke(o,"touch 1.txt");
    }

也可以传入String[] 类型

    public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 1. 获得class对象
        Class clazz = Class.forName("java.lang.Runtime");
        // 2. 获取无参构造器
        Constructor declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        // 3. Constructor.newInstance() 创建对象
        Object o = declaredConstructor.newInstance();
        //强制转换Runtime
        //Runtime o1 = (Runtime) o;
        // 4. 获取方法
        String[] cmds = new String[]{"/bin/bash","-c","touch 2.txt"};
        Method show = clazz.getDeclaredMethod("exec", String[].class);
        // 强制转换为Object
        show.invoke(o,(Object) cmds);
    }

同样ProcessImpl类、ProcessBuilder类也可以反射调用执行系统命令

    public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        Constructor declaredConstructor = clazz.getDeclaredConstructor(List.class);
        ArrayList lists = new ArrayList<>();
        lists.add("touch");
        lists.add("2.txt");
        Object o = declaredConstructor.newInstance(lists);
        Method start = clazz.getDeclaredMethod("start");
        start.invoke(o);
    }

    public void test2() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = Class.forName("java.lang.ProcessImpl");
        Constructor declaredConstructor = clazz.getDeclaredConstructor(byte[].class,byte[].class,int.class,byte[].class,int.class,byte[].class,int[].class,boolean.class,boolean.class);
        declaredConstructor.setAccessible(true);
        Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        start.setAccessible(true);
        start.invoke(null, new String[]{"/bin/bash","-c","touch 1.txt"}, null, ".", null, true);
    }

总结

想比较第一次学习,对反射多了一些理解,并结合几个执行命令的类实践调用了反射。

参考资料

https://www.bilibili.com/video/BV1p4411P7V3
https://re0.top/2020/03/22/java-reflection/#Class%E7%B1%BB
https://m0nit0r.top/2020/06/07/java-deserialize-learn2/

你可能感兴趣的:(Java 反射机制学习)