Java反射—框架的灵魂(相当重要)

前言

接上一篇,最近刚学完反射,还有一些地方不得其解,于是写一篇博客串一下刚学的内容吧,本文内容全是自己整理的笔记加上我自己的理解,有不对的地方还请各位大佬提出。

反射的概念

什么是反射(Reflection):将类的各个组成部分封装为其他对象,这就是反射机制。反射作为框架的灵魂在各种框架中用到的比较多。

反射的好处

可以在程序运行的过程中操作这些对象,可以解耦,提高程序可扩展性。举个例子,加载数据库配置文件的时候一般就是运用的反射。

动态语言的理解

Java本身是一门静态语言,但是通过反射可以让Java很灵活。
所谓动态语言我个人理解就是在程序的运行过程中可以加载对象的一种语言。

关于Class

获取Class对象的三种方式

 public static void main(String[] args ) throws Exception{
    
    
        //创建Class对象的三种方式
        //1、通过Class.forName("全类名")
        Class c1 = Class.forName("cn.sjy.Person");
        System.out.println(c1);
        //2、通过 类名.class
        Class<Person> c2 = Person.class;
        System.out.println(c2);
        //3、 通过对象.getClass()
        Person person = new Person();
        System.out.println(person.getClass());
    }

结果如图:
在这里插入图片描述
由此可见这三种方式获取的都是一个Class对象,值得一提的是同一个字节码文件(.class文件)在一次程序运行过程中只加载一次。所有的类型都可以有Class对象,像属性,方法,构造函数,泛型,注解等。

对Class类的理解

Class类包含了类的所有信息,包括构造方法,属性,方法等。反射也可以理解为将Class类加载进内存之后返回一个Class对象,通过这个Class对象将它本身的方法属性等映射出来的一个过程。

类的加载与ClassLoader的理解

加载过程

类名.class是一个字节码文件,我们要知道一个Java程序运行是先从.java文件变为.class文件的,之后再由.class文件加载进内存,也就是将.class文件放进JVM中才能运行程序,只有.class文件放入JVM才可以实现跨平台。类加载器(ClassLoader)的作用就是将.class文件放入JVM加载成Class对象(java.lang.Class对象),只有Class对象才可以调用别的方法。

类加载器(ClassLoader)

类加载器负责读取.class文件,并转换成java.lang.Class类的一个实例,每个这样的实例用来表示一个java类,通过此实例的newInstance()方法就可以创建出该类的一个对象。通过反射,类对我们是完全透明的不管你是不是private还是什么,只要通过反射我们就可以得到我们想要的任何东西,一般来说当一个属性是private的时候就需要调用get和set方法来取值和赋值,但是通过反射直接就可以对private修饰的属性进行赋值和取值,当然这也有弊端,那就是慢!相比new一个对象来调用属性和方法,反射要慢得多!
一句话总结类加载器(个人理解):通过获取一个class对应的Class实例后,就可以获取该class的所有信息。
通过Class实例获取class信息的方法称为反射(Reflection)

Class对象功能及其实例

通过反射动态调用对象

代码与上一篇博客一样,在这里一起做个汇总

 public static void main(String[] args ) throws Exception{
    
    
        //获取Class对象
        Class c1 = Class.forName("cn.sjy.Person");
        //实例化该对象,创建由此类对象表示的新的对象
        Person person = (Person) c1.newInstance();
        //newInstance()方法规定调用无参的构造函数
        System.out.println(person);

        //获得一个类的构造器
        Person person2 = (Person)c1.getConstructor(int.class, String.class).newInstance(2017021, "虎哥");
        //将这个类的构造器进行实例化
        System.out.println(person2);

        //调用一个普通方法
        Method fan = c1.getDeclaredMethod("fan");
        //需要忽略访问权限修饰符的安全检查--》暴力反射
        fan.setAccessible(true);
        fan.invoke(person);

        //操作属性
        Field id = c1.getDeclaredField("id");
        id.setAccessible(true);
        id.set(person,123);
        System.out.println(id.get(person));
    }
 public static void main(String[] args ) throws Exception {
    
    
        //获取Class对象,将这个字节码文件加载进内存
        Class c1 = Class.forName("cn.sjy.Person");
        //实例化这个Class对象,创建一个由此Class对象表示的新对象
            //newInstance()方法只能调用无参的构造函数
        Person person = (Person)c1.newInstance();

        //输出一个无参的构造函数
        System.out.println(person);
        //调用一个有参的构造函数,并且将其实例化
        Person p = (Person) c1.getConstructor(int.class, String.class).newInstance(2017021, "反射君");
        System.out.println(p);

        //调用属性,如果是public调用getField即可,但是如果是private需要调用getFDeclaredFeild方法
        Field id = c1.getDeclaredField("id");
        //关闭安全检查
        id.setAccessible(true);
        id.set(p,2017021);
        id.set(person,2017022);
        //输出这个属性的值
        System.out.println(id.get(person));
        System.out.println(id.get(p));

        //调用普通方法
        Method fan = c1.getDeclaredMethod("fan");
        fan.setAccessible(true);
        fan.invoke(p);
    }

第二段代码运行结果:
在这里插入图片描述

Person类

package cn.sjy;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    
    
    public int id;
    private String name;

    /**普通方法*/
    private void fan(){
    
    
        System.out.println("通过反射来调用普通方法!");
    }
}

我这个Person类用了一个插件,通过注解就可以得到构造方法之类的。

反射案例细讲

通过获得一个配置文件的信息来调用一个普通方法

public static void main(String[] args ) throws Exception{
    
    
        //获取配置文件的位置
        File file = new File("D:\\JavaIDEA\\Test\\src\\pro.properties");
        //调用Properties类-->唯一一个和IO流结合的集合
            //Properties类实现了Map接口
        Properties pro = new Properties();
        //调用一个FileInputStream类读取一个文件
        FileInputStream fis = new FileInputStream(file);
        //读取这个文件的信息
        pro.load(fis);
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
        //输出文件的信息
        System.out.println(className);
        System.out.println(methodName);

        //根据配置文件的值创建Class对象
        Class c1 = Class.forName(className);

        //调用Person的普通方法
        Method fan = c1.getDeclaredMethod("fan");
        //因为该普通方法是private修饰的所以需要关闭检测
        fan.setAccessible(true);
        //执行该方法
        fan.invoke(c1.newInstance());
    }

结果如图(此Person类与上面的是一个类):
在这里插入图片描述
接下来详细讲一下这个每一步的作用
Class.forName(“全类名”):通过全类名将这个类加载进内存然后对其进行实例化产生一个Class对象。
.newInstance():将这个Class对象进行实例化,换种方法来说就是调用这个对象的无参构造函数,注意是无参的构造函数,newInstance()默认调用无参的构造函数,如果要调用有参的构造函数需要用getConstractor(Object.class,Object.class…).newInstance(obj.obj…)来实现。
注:对于static{}代码块是在类的初始化之前就运行了,所以一般static{}代码块一般也用作加载配置文件等信息。
类的主动引用(一定会发生类的初始化),反射,new一个对象
类的被动引用(不会发生类的初始化),子类.父类变量,数组

总结

关于Java反射的一些知识目前我就学到这里,学的很浅显,并没有深入,打算日后接触到框架的时候再作深入,接下来我会整理一下关于多线程的内容,能力有限,多多包涵,就酱~

猜你喜欢

转载自blog.csdn.net/weixin_44475741/article/details/109450921