反射

反射(框架的灵魂)

为什么需要反射?

问题:

通过读取配置文件里的类和方法,创建一个对象和调用这个方法

我的想法:用文件流获取配置文件的内容

public class quesion1 {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.load(new FileReader("D:\\java高级部分\\Reflex\\src\\com\\LiuDeHen\\Reflexquesion\\re.properties"));
        String lei = properties.getProperty("lei");
        String method = properties.getProperty("method");
        System.out.println(lei);
        System.out.println(method);
//        按我的想法可以直接 new lei,字符串lei内容确实是类名,但此时lei是一个字符串,怎么可能new 一个字符串呢
    }
}

所以目前我们传统的方法和技术做不到这一步

这样的需求在学习框架时特别多,即通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的ocp原则(开闭原则:不修改源码,开扩容功能)

用反射解决上面的问题

//        通过反射解决
//       1. 加载类,返回Class类型的的对象aClass
        Class aClass = Class.forName(lei);
//        2.通过newInstance方法获取配置文件中com.LiuDeHen.Reflex.cat(lei)这个对象
        Object o = aClass.newInstance();
       ** 3.这里我们不要把object转换成cat类型,因为反射就是要在不修改源代码,修改配置文件的情况下操作代码
  		**如果我们这里转成cat类型,我们是可以调用hi方法,但是有另外一个方法时,我们就要修改源码调用另外一个方法
//        这里我们用getMethod获取一个方法的对象(万物皆对象)
        Method Method = aClass.getMethod(method);
//        4.通过Method方法对象实现调用方法
        Method.invoke(o);//传统是对象.方法,反射机制也是方法对象.invoke方法(也可以说是方法.invoke(对象))
    }

反射的作用

作用:

加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子透过这个镜子看到类的结构,所以,形象的称之为:反射

java反射机制原理图

1641197159935

每一个类都有一个Class类对象,也就是说,所有的类都是Class类的实例化对象

程序的三个阶段:

首先我们写的源码/代码(.java文件)会被javac编译成(.class字节码文件),然后通过类加载器Classloader里的loadClass()方法将对应的类生成一个Class类对象(这里体现了反射)并到Class类加载阶段并把Class对象放在堆里(对象里有我们当前类的所有内容),加载完后到我们平时看的运行阶段,我们在运行阶段可以通过cat对象获取到cat对象是在哪一个Class类对象,得到这个Class类对象后可以对当前类进行操作

从这里我们创建一个对象就有:1.new 出来

​ 2.通过反射机制去创建

反射的优缺点

优点:能够动态的创建和使用对象(框架的核心),使用灵活

缺点:使用反射基本都是解释运行,对执行速度有影响

反射相关的类

  1. java.lang.Class:代表个类Cass对象表示某个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法, Method对象表示某个类的方法
  3. java.lang.reflect.Field:代表类的成员变量, Field对象表示某个类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示·构造器
//        通过反射解决
//       1. 加载类,返回Class类型的的对象aClass
        Class aClass = Class.forName(lei);
//        2.通过newInstance方法创建配置文件中com.LiuDeHen.Reflex.cat(lei)这个对象
        Object o = aClass.newInstance();
//        3.这里我们不要把object转换成cat类型,因为反射就是要在不修改源代码,修改配置文件的情况下操作代码
//        如果我们这里转成cat类型,我们是可以调用hi方法,但是有另外一个方法时,我们就要修改源码调用另外一个方法
//        这里我们用getMethod获取一个方法的对象(万物皆对象)
        Method Method = aClass.getMethod(method);
//        4.通过getMethod方法获取能够操作方法的对象
        Method.invoke(o);//传统是对象.方法,反射机制也是方法对象.invoke方法(也可以说是方法对象.invoke(类对象))
        
//        5.通过getField获取能操作属性的对象,注意:这里不能获取到私有化的属性
        Field age = aClass.getField("Age");
        System.out.println(age.get(o));   //一样的属性对象.get(类对象)
        
//        6.通过getConstructor获取能操作构造器的对象
        Constructor constructor = aClass.getConstructor();//行参为空就是获取无参构造器
        Constructor constructor1 = aClass.getConstructor(int.class,String.class);//行参为类对象就是对应的构造器
        System.out.println(constructor1);
    }

反射优化

1641189704046

1641189671700

1641189686015

全都继承了这个方法,所以我们可以通过设置setAccessible(true)关闭安全检查的开关

 public void m2() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> aClass = Class.forName("com.LiuDeHen.Reflex.cat");
        Method hi = aClass.getMethod("hi");
        Object o = aClass.newInstance();
//        设置setAccessible关闭安全检查开关
        hi.setAccessible(true);
        long start = System.currentTimeMillis();
        for (int i = 0; i <900000000 ; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("m2:"+(end-start));
    }
}

Class类

1641189965644

重要概念:

  1. 是Object的子类,它其实也是一个普通的类
  2. 对象不是new出来的,而是系统类加载器通过loadClass()创建的
//public Class<?> loadClass(String name) throws ClassNotFoundException {
//        return loadClass(name, false);
//    }

3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次

4.通过Class对象可以获取到一个类的完整结构,通过API

5 .Class对象是放在堆中的

获取Class对象的方式:

不同的阶段:

我们写代码的阶段(编译阶段):Class.forName()====》在已知类的全类名

应用场景:多用于配置文件,读取全路径,加载类

类加载阶段(此时类Class已经存在):类名.class

应用场景:参数传递

运行阶段(已经new了对象):对象.getClass()

应用场景:在已经有了实例化对象

类加载器阶段:获取类加载器,调用loadClass()

   public static void main(String[] args) throws ClassNotFoundException {
//        第一种
        Class<?> aClass1 = Class.forName("com.LiuDeHen.Reflex.cat");
//        第二种
        Class<cat> aClass2 = cat.class;
//        第三种
        cat cat = new cat();
        Class<? extends com.LiuDeHen.Reflex.cat> aClass3 = cat.getClass();
//        第四种
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Class<?> aClass4 = classLoader.loadClass("com.LiuDeHen.Reflex.cat");

        System.out.println(aClass1.hashCode());
        System.out.println(aClass2.hashCode());
        System.out.println(aClass3.hashCode());
        System.out.println(aClass4.hashCode());
    }

常用方法

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
        Class<?> aClass = Class.forName("com.LiuDeHen.Reflex.cat");
//        1.获取包名
        System.out.println(aClass.getPackage());
//        2.获取类名
        System.out.println(aClass);       //获取是哪个类的Class对象   com.LiuDeHen.Reflex.cat
        System.out.println(aClass.getClass());  //获取的是aClass的类型java.lang.Class
        System.out.println(aClass.getName());     //全类名    com.LiuDeHen.Reflex.cat
//        3.创建对象
        Object o = aClass.newInstance();
        System.out.println(o);      //输出的是对象.tostring
//        4.获取属性
        Field field = aClass.getField("Age");
        System.out.println(field.get(o));
//        5。设置属性
        field.set(o,120);
        System.out.println(field.get(o));
//        6.获取所有属性
        Field[] fields = aClass.getFields();
        for (Field f:fields
             ) {
            System.out.println(f);
        }
//        7.获取方法
        Method hi = aClass.getMethod("hi");
        hi.invoke(o);
//        8.获取所有方法
        for (Method method : aClass.getMethods()) {
            System.out.println(method);
        }
    }

静态和动态类加载

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。

1.静态加载:编译时加载相关的类,如果没有则报错,依赖性太强(普通new 对象)

2.动态加载:运行时加载需要的类,即使不存在这个类编译也不会报错,只有运行时没有该类,才会报错,降低了依赖性(使用反射创建对象)

普通创建对象就是在编译期要编译所以会报错,而用反射创建的对象,是在类加载阶段,只有在运行阶段运行到这段代码,调用了Class对象发现没有才报错

类加载

1641197347844

1641198441765

加载阶段

JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class 文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class 对象

连接-验证阶段

验证Class文件的字节流是不是满足jvm虚拟机格式

连接-准备阶段

静态变量的初始化赋值

连接-解析阶段

虚拟机将常量池内的符号引用替换成成直接引用(解析过程中,并没有放到内存,所以没有具体地址)

初始化

初始化阶段,才真正开始执行类中定义的java程序代码,此阶段是执行clinit():依次搜集类中所有的静态变量和静态代码块的赋值

class A{
    //        进入类加载阶段,先进入加载阶段,生成Class对象
//        再进入连接阶段验证,准备,所以这里静态代码块在上面,没有初始化也不会编译错误,因为在准备阶段就会给静态变量赋初始值值(这里为0)
//          再到解析
//    再到我们能够修改的阶段(前面都是jvm的工作),初始化阶段,执行静态代码块
//    顺序:静态变量n初始化=0 --   静态代码块执行,赋值为100, 再执行静态变量赋值为300
    static {
        System.out.println("静态代码块");
        n=100;
    }
    static int n=300;
    A(int m){
        n=m;
    }
}

获取类的结构信息

java.lang.Class类

  1. getName:获取全类名
  2. getSimpleName:获取简单类名
  3. getFields:获取所有public修饰的属性,包含本类以及父类的
  4. getDeclaredFields:获取本类中所有属性
  5. getMethods:获取所有public修饰的方法,包含本类以及父类的
  6. getDeclaredMethods:获取本类中所有方法
  7. getConstructors: 获取所有public修饰的构造器(不包括父类)
  8. getDeclaredConstructors:获取本类中所有构造器
  9. getPackage:以Package形式返回包信息
  10. getSuperClass:以Class形式返回父类信息
  11. getlnterfaces:以Class[]形式返回接口信息
  12. getAnnotations:以Annotation[]形式返回注解信息
public static void main(String[] args) throws ClassNotFoundException {
//        1. getName:获取全类名
        Class<?> aClass = Class.forName("com.LiuDeHen.Reflex.cat");
//        2. getSimpleName:获取简单类名
        String simpleName = aClass.getSimpleName();
        System.out.println("简单类名"+simpleName);
//        3. getFields:获取所有public修饰的属性,包含本类以及父类的
        for (Field field : aClass.getFields()) {
            System.out.println("所有public修饰的属性"+field);
        }
//        4. getDeclaredFields:获取本类中所有属性
        for (Field declaredField : aClass.getDeclaredFields()) {
            System.out.println("本类中所有属性"+declaredField);
        }
//        5. getMethods:获取所有public修饰的方法,包含本类以及父类的
        for (Method method : aClass.getMethods()) {
            System.out.println("获取所有public修饰的方法"+method);
        }

//        6. getDeclaredMethods:获取本类中所有方法
        for (Method declaredMethod : aClass.getDeclaredMethods()) {
            System.out.println("本类中所有方法"+declaredMethod);
        }

//        7. getConstructors: 获取所有public修饰的构造器
        for (Constructor<?> constructor : aClass.getConstructors()) {
            System.out.println("所有public修饰的构造器"+constructor);
        }

//        8. getDeclaredConstructors:获取本类中所有构造器
        for (Constructor<?> declaredConstructor : aClass.getDeclaredConstructors()) {
            System.out.println("本类中所有构造器"+declaredConstructor);
        }

//        9. getPackage:以Package形式返回包信息
        Package aPackage = aClass.getPackage();
        System.out.println("Package形式返回包信息"+aPackage);
//        10. getSuperClass:以Class形式返回父类信息
        Class<?> superclass = aClass.getSuperclass();
        System.out.println("Class形式返回父类信息"+superclass);
//        11. getlnterfaces:以Class[]形式返回接口信息
        for (Class<?> anInterface : aClass.getInterfaces()) {
            System.out.println("Class[]形式返回接口信息"+anInterface);
        }

//        12. getAnnotations:以Annotation[]形式返回注解信息
        for (Annotation annotation : aClass.getAnnotations()) {
            System.out.println("以Annotation[]形式返回注解信息"+annotation);
        }
    }

java.lang.reflect.Field类

  1. getModifiers: 以int形式返回修饰符
    [说明:默认修饰符是0,public是1,private是2,protected是4 static是8 , final是16] public(1) +static (8)
  2. getType:以Class形式返回类型
  3. getName:返回属性名

java.lang.reflect.Method类

  1. 1.getModifiers:以int形式返回修饰符[说明:默认修饰符是0,public是1,private是2, protected是4static是 8, final是16]
  2. getReturnType:以Class形式获取返回类型
  3. getName:返回方法名
  4. getParameterTypes:以Class[]返回参数类型数组

java.lang.reflect.Constructor

通过反射创建对象

Class类相关方法

  1. newlnstance:调用类中的无参构造器,获取对应类的对象
  2. getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象
  3. getDecalaredConstructor(Class..clazz):根据参数列表,获取对应的所有构造器对象

Constructor类相关方法.

  1. setAccessible:暴破
  2. newlnstance(Object...obj):调用构造器
 public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//        通过Class类
//        1.  newlnstance:调用类中的无参构造器,获取对应类的对象
        Class<?> aClass = Class.forName("com.LiuDeHen.Reflex.cat");
        Object o = aClass.newInstance();
        System.out.println(o);
//        2. getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象
//        先得到对应构造器,再创建实例    newlnstance(Object...obj):调用构造器
        Constructor<?> constructor = aClass.getConstructor(int.class,String.class);
        Object o1 = constructor.newInstance(1, "你哈");
        System.out.println(o1);
//        3.  getDecalaredConstructor(Class..clazz):根据参数列表,获取对应的所有构造器对象
//        这个可以获取所有的构造器,保括私有
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class);
//      setAccessible:暴破
        declaredConstructor.setAccessible(true);//暴力破解,使用反射运行访问私有构造器,反射面前都是弟弟
        Object o2 = declaredConstructor.newInstance("nimd");
        System.out.println(o2);
    }

通过反射获取类中属性

  1. 根据属性名获取Field对象

    Field f = clazz对象.getDeclaredField(属性名);

  2. 暴破: f.setAccessible(true); //f 是Field

  3. 访问f.set(o,值);

    sout(f.get(o));

  4. 如果是静态属性,则set和get中的参数o,可以写成null

public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
        Class<?> aClass = Class.forName("com.LiuDeHen.Reflex.cat");
        Object o = aClass.newInstance();
//        拿到公有属性
//        Field field = aClass.getField("age");
//        拿到所有的属性
        Field field = aClass.getDeclaredField("age");
        field.setAccessible(true);      //爆破(记住爆破的前提是获取了所有的,而不只是公共的)
        Object o1 = field.get(o);
        System.out.println(o1);
//        field.set(o,20);                //跟对象.set一样要想设置对象里的属性,就需要这个对象
        field.set(null,20);                //这里传null是因为age为静态,在类加载的阶段就有值了,不是靠对象访问
//        设置完后再次获取
        System.out.println(field.get(null));
    }

通过反射获取类中方法

1.根据方法名和参数列表获取Method方法对象:Method m=clazz.getDeclaredMethod(方法名,XX.class);//得到本类的所有方·

2.获取对象:Object o=clazz.newlnstance();
3.暴破:m.setAccessible(true);
4、访问:Object returnValue = m.invoke(o,实参列表);5.注意:如果是静态方法,则invoke的参数o,可以写成null!

    public static void main(String[] args) throws Exception {
        Class<?> aClass = Class.forName("com.LiuDeHen.Reflex.cat");
        Object o = aClass.newInstance();
//        获取公共方法
        Method h1 = aClass.getMethod("hi");
        h1.invoke(o);
//        获取所有方法,处理私有方法
        Method h2= aClass.getDeclaredMethod("hi2");
        h2.setAccessible(true);//爆破
        h2.invoke(o);
//         处理静态方法时,对象可传null
        Method h3= aClass.getDeclaredMethod("hi3",String.class);
        h3.setAccessible(true);
        h3.invoke(null,"ni");
//       当方法有返回值,统一返回Object类型,但是它运行类型和方法定义的返回值类型一致
        Method h4 = aClass.getDeclaredMethod("hi4", String.class);
        h4.setAccessible(true);
        System.out.println(h4.invoke(o, "你好"));
    }
posted @ 2022-03-14 18:05  又菜又ai  阅读(44)  评论(0)    收藏  举报