zoukankan      html  css  js  c++  java
  • Java基础-反射

    反射

    • 反射的理解
    • Class类的特点
    • 通过反射创建Class类的对象
    • 通过反射解析对应类的结构,获取信息
    • 通过反射创建对应类的对象
    • 通过反射调用类的成员(属性、方法、构造)

    关键字:

    • 反射机制
    • 动态语言

    一.Java反射机制概述


    1.关于类的加载

    1.类的加载过程:

    当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:

    • 1)类的装载:将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
    • 2)类的连接:将类的二进制数据合并到JRE中
    • 3)类的初始化:JVM负责对类进行初始化

    2.ClassLoader 类加载器:

    • 类加载的作用: 将class文件字节码内容加载到内存中, 并将这些静态数据转换成方法区的运行时数据结构, 然后在堆中生成一个代表这个类的java.lang.Class对象, 作为方法区中类数据的访问入口。

    • 类缓存: 标准的JavaSE类加载器可以按要求查找类, 但一旦某个类被加载到类加载器中, 它将维持加载(缓存) 一段时间。 不过JVM垃圾回收机制可以回收这些Class对象。

    • 类加载器是用来把类(class)装载进内存的。

    JVM 规范定义了两种类型的类加载器:启动类加载器(bootstrap)和用户自定义加载器(user-defined class loader)。

    JVM在运行时会产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:

    • 引导类加载器:用C++编写的,是JVM自带的类装载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
    • 扩展类加载器:负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库
    • 系统类加载器:负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器
      在这里插入图片描述

    3.类的加载时期:

    • 编译期加载:又称为静态加载,编译时则加载所有用到的类,如果该类不存在,则直接报编译错误,依赖性太强
    • 运行期加载:又称为动态加载,运行时加载用到的类,如果该类(字节码文件)不存在,则报运行错误,降低了依赖性,提高了维护性

    4.类的加载时机:

    • new对象
    • 调用类的静态成员
    • 加载子类
    • 反射

    5.类加载器的使用案例:

    //1.获取一个系统类加载器
    ClassLoader classloader = ClassLoader.getSystemClassLoader();
    System.out.println(classloader);
    //2.获取系统类加载器的父类加载器,即扩展类加载器
    classloader = classloader.getParent();
    System.out.println(classloader);
    //3.获取扩展类加载器的父类加载器,即引导类加载器
    classloader = classloader.getParent();
    System.out.println(classloader);
    //4.测试当前类由哪个类加载器进行加载
    classloader = Class.forName("exer2.ClassloaderDemo").getClassLoader();
    System.out.println(classloader);
    //5.测试JDK提供的Object类由哪个类加载器加载
    classloader = Class.forName("java.lang.Object").getClassLoader();
    System.out.println(classloader);
    //*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流
    InputStream in = null;
    in = this.getClass().getClassLoader().getResourceAsStream("exer2\test.properties");
    System.out.println(in);
    View Code

    2.什么是:Reflection

    1.什么是反射:

    反射,属于java实现动态语言的关键,可以获取运行期间的类的信息

    Reflection(反射)是被视为动态语言的关键。

    反射机制:允许程序在运行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

    2.Java反射机制提供的功能:

    • 在运行时,判断任意一个对象所属的类
    • 在运行时,构造任意一个类的对象
    • 在运行时,判断任意一个类所具有的成员变量和方法
    • 在运行时,调用任意一个对象的成员变量和方法
    • 生成动态代理

    3.反射相关的主要API:

    • java.lang.Class:代表一个类
    • java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructor:代表类的构造方法

    3.反射的好处

    java实现动态语言的关键,可以动态获取一个类的结构信息,并调用成员

    1)Java中的软编码:维护性高

    //配置文件信息
    <class-name>com.atguigu.reflect.Student</class-name>
    //拿到类名
    String clazzName = xxxx;
    //使用反射,创建类的对象、调用方法等等
    ......
    View Code

    2)硬编码:维护性差

    Person per = new Person();

    二.Class类的理解

    There is a class named Class.

    • 1 Class本身也是一个类
    • 2 Class 对象只能由系统建立对象,但我们可以通过一些方式获取该对象的引用
    • 3 一个类在 JVM 中只会有一个Class实例 ,因为一个类只可能加载一次
    • 4 一个Class对象对应的是一个加载到JVM中的一个.class文件
    • 5 每个类的对象,都能找到它所属的类型,也就是可以获取对应的Class类的对象
      Class clazz = 对象.getClass();
    • 6 通过Class类对象的引用,可以解剖类的结构,可以获取类的所有结构信息,并且调用方法、属性、构造等

    Class 也是一个类:其对象是方法区中对应.class文件的加载

    所谓反射,从程序的运行结果来看也很好理解,即:可以通过对象反射出类,以及对类的各种操作。

    通过反射:可以解剖使用到的这个类

    因为:通过Class可以完整地得到一个类中的完整结构

    问题:如何获得Class对象(对应加载到JVM中的.class文件),以及通过Class对象,解剖对应的类?

    三.反射的使用

    1.通过反射,获取Class类的对象:

    问题:如何获得一个Class对象?

    获取Class对象的四种方法:

      • 通过全类名字符串,获取Class类的对象:Class.forName()

      • 通过某个具体类的对象获取Class类的对象:对象名.getClass()

      • 通过类名获取Class类的对象,比较适合传参:String.class

      • 通过类加载器获取Class类的对象,用的较少

    //方式1:通过全类名字符串获取Class类的对象
    @Test
    public void test1() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("java.lang.String");
        System.out.println(clazz.getSimpleName());
        System.out.println(clazz.getPackage().getName());
    }
    //方式2:通过某个具体类的对象获取Class类的对象
    @Test
    public void test2() {
        String s = "令狐冲";
        //可以调用 对象.getClass()方法,返回对应Class对象
        Class clazz = s.getClass();
        method("java");
    }
    public <T> void method(T t){
        System.out.println(t.getClass().getSimpleName());    //String
    }
    //方式3:通过类名获取Class类的对象:比较适合传参
    @Test
    public void test3() {
        //每个类有对应的Class类型的class成员属性
        Class clazz = String.class;
    }
    //方式4:通过类加载器获取Class类的对象,用的较少
    @Test
    public void test4() throws ClassNotFoundException {
        //1)首先,使用当前对象的Class对象获取,获取一个类加载器
        ClassLoader loader = this.getClass().getClassLoader();
        //2)使用类加载器,通过全类名,加载获取对应的Class对象
        Class clazz = loader.loadClass("java.lang.String");
    }
    View Code

    2.通过反射,获取类的结构信息

    问题:通过反射获得了Class对象,Class对象又能做什么?

    1)通过Class对象,获取类的成员

    包括类的全部属性、方法、构造器、父类、接口、包、泛型、注解

    java.lang.Class 类中的方法:

    1)获取类的属性

    • getFields():获取本类以及从父类继承来的所有public修饰的属性,不限于直接父类
    • getDeclaredFields():获取本类中定义的所有属性,不问修饰符
    • getField(String name) : 返回一个 Field 对象

    2)获取类的方法们

    • getMethods():获取本类以及从父类继承来的所有public修饰的方法,不限于直接父类
    • getDeclaredMethods():获取本类中定义的所有方法,不问修饰符
    • getMethod(String name, Class<?>… parameterTypes) :返回对应方法名,参数列表的Method

    3)获取类的构造器们

    • getConstructors():获取本类以及从父类继承来的所有public修饰的构造器,不限于直接父类
    • getDeclaredConstructors():获取本类中定义的所有构造器,不问修饰符
    • getConstructor(Class<?>… parameterTypes)

    4)获取类的父类

    • getSuperClass():以Class类型返回父类对象

    5)获取类的接口

    • getInterfaces():以Class[]类型返回接口对象

    6)获取类的注解

    • getAnnotations():返回此元素上存在的所有注释。返回 Annotation[]
    • getDeclaredAnnotations()
    • getAnnotation(Class annotationClass) :如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。

    **注意:**只能获取保留策略为RetentionPolicy.RUNTIME的注解

    7)获取父类或接口的泛型

    • getGenericSuperClass():获取父类的泛型(父类类型+泛型)
    • getGenericInterfaces():获取接口的泛型(接口类型+泛型)
    • 泛型类型:ParameterizedType

    ParameterizeType pt = (ParameterizeType)type;
    pt.getActualTypeArguments();

    2.其次:返回值是对应类的数组,根据对应类的方法,获取类的结构信息

    1)java.lang.reflect.Field类:

    • getModifiers() 修饰符:public static
    • getType()
    • getName()

    2)java.lang.reflect.Method:

    • getModifiers()
    • getReturnType()
    • getName()
    • getParameterTypes()

    3)java.lang.reflect.Constructor:

    • getModifiers()
    • getName()
    • getParameterTypes()

    4)java.lang.reflect.Annotation:

    • annotationType()

    **注意:**并不是所有的注解都有返回的资格

    • 注解的 @Retention 保留策略 --> 会使通过反射获取该类的注解时,获取不到

    注解的作用机制:编译器通过反射判断你的注解是否正确;

    3.通过反射,访问类的成员


    问题:能不能通过Class对象,访问/设置类或对象对应单个的具体成员?

    1.首先,通过Class获取具体的类成员,并返回该类型的对象

    Class类的API:获取对应成员

    • getField(属性名):获取本类以及从父类继承来的,公共的属性
    • getDeclaredField(属性名):获取本类定义的属性,不问修饰符
    • getMethod(方法名,形参列表):获取本类以及从父类继承来的,公共的方法
    • getDeclaredMethod(方法名,形参列表):获取本类定义的方法,不问修饰符

    2.通过具体的对象,实现类成员的访问和操作

    Field类的API:

    • set(对象,属性值):为指定对象的属性赋值,如果该属性为静态属性,则对象可以使用null
    • get(对象):获取指定对象的该属性值,如果该属性为静态属性,则对象可以使用null
    • setAccessible(true):暴破

    Method类的API

    • invoke(对象,实参列表):调用指定对象的方法,如果该方法为静态方法,则对象可以使用null
    • setAccessible(true):暴破

    1)访问属性

    //步骤1:获取Class类对象
    Class clazz = Class.forName("全类名");
    
    //步骤2:获取name属性对象
    Field field = clazz.getDeclaredField("属性名");
    
    //步骤3:暴破(属性为私有时,需要爆破)
    field.setAccessible(true);
    
    //步骤4:访问
    Object object = clazz.newInstance();//确保类中有public修饰的无参构造器
    //设置object对象的field字段值为“属性值”
    field.set(object,"属性值");
    //获取该对象的filed值
    field.get(object);
    View Code

    2)访问方法

    //步骤1:获取Class类对象
    Class clazz = Class.forName("全类名");
    
    //步骤2:获取方法对象
    Method method = clazz.getDeclaredMethod(方法名,参数列表);
    
    //步骤3:暴破
    method.setAccessible(true);
    
    //步骤4:访问
    Object object = clazz.newInstance();//确保类中有public修饰的无参构造器
    //调用object对象的method方法,并传入实参
    Object value = method.invoke(object,实参列表);
    View Code

    3)获取接口的泛型/父类的泛型

    @Test
    public void testGetGeneric() {
        //获取接口的类型Type[]:Class类实现了Type接口
        Type[] types = clazz.getGenericInterfaces();    //Fly,Swim<String> 实现的 接口类型+泛型
        for (Type type : types) {
            //判断接口类型 Type 是否为参数化类型:ParameterizedType(Type接口的子类),如 Collection<String>
            if (!(type instanceof ParameterizedType)) {
                continue;
            }
            //若是参数化类型(即该类型有参数,也就是有泛型),则向下转型
            ParameterizedType pt = (ParameterizedType) type;
            //调用ParameterizedType接口的getActualTypeArguments()方法:返回表示此类型实际类型参数的 Type 对象的数组
            Type[] actualTypeArguments = pt.getActualTypeArguments();
            //向下转型为Class,获取其简单类名
            System.out.println(((Class)actualTypeArguments[0]).getSimpleName());
        }
    }
    View Code

    4)获取注解

    @Test
    public void testAnnotation() {
        //获取Class对象的注解们
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            //获取该注解的Class对象
            Class<? extends Annotation> annotationType = annotation.annotationType();
            System.out.println(annotationType.getSimpleName());
        }
    }
    View Code

    4.通过反射,创建运行时类的对象


    问题:有了Class对象,对应类也就加载到了内存,如何通过Class对象创建该类的对象呢?

    1)使用无参构造器

    调用Class对象的newInstance()方法

    Object object = clazz.newInstance();

    要求:

    • 1)类必须有一个无参数的构造器(或默认有)
    • 2)类的构造器的访问权限需要足够。

    难道没有无参的构造器就不能创建对象了吗?

    2)使用有参构造器

    Object object = clazz.getDeclaredCosntructor(参数列表).newInstance(实参列表);

    步骤如下:

    • 1)通过Class类的getDeclaredConstructor(Class … parameterTypes):取得本类的指定形参类型的构造器
    • 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
    //1.根据全类名获取对应的Class对象
    String name = “com.mytest.Person";
    Class clazz = Class.forName(name);
    //2.获取指定参数结构的构造器 Constructor 的实例
    Constructor con = clazz.getConstructor(String.class,Integer.class);
    //3.通过Constructor的实例,创建对应类的对象,并初始化类属性
    Person p2 = (Person)con.newInstance("Peter",20);
    System.out.println(p2);
    View Code

    泛型擦除


    泛型擦除:

    • 泛型只保留在编译期间,运行期间不存在泛型

    面试题1:通过反射调用list2对象的add方法

    List list1 = new ArrayList();
    list1.add("");
    list1.add(123);
    //使用泛型
    
    List<String> list2 = new ArrayList<>();
    list2.add("");
    list2.add(123);//错误
    
    //泛型擦除,测试:
    Class clazz = list2.getClass();
    //Method method =clazz.getMethod("add",String.class);//找不到该方法
    Method method =clazz.getMethod("add",Object.class);//
    method.invoke(list2,"john");
    View Code

    四、反射的应用:动态代理

    1.什么是动态代理

    1)代理设计模式的原理:

    • 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。
    • 任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原
      始对象上。

    2)动态代理是指:

    • 客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

    3)动态代理使用场合:

    • 调试
    • 远程方法调用

    4)静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代
    理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。 最好可以通过一个代理类完成全部的代理功能。

    动态代理相比于静态代理的优点:

    • 抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

    2.Java动态代理相关API

    **1.java.lang.reflect.Proxy **:

    • 专门完成代理的操作类,是所有动态代理类的父类;通过此类为一个或多个接口动态地生成实现类。

    2.提供用于创建动态代理类和动态代理对象的静态方法

    //创建一个动态代理类所对应的Class对象:
    static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
    //直接创建一个动态代理对象 
    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
    InvocationHandler h) 
    View Code

    参数含义:

    • ClassLoader loader:类加载器
    • Class<?>[] interfaces:得到被代理类实现的全部接口
    • InvocationHandler h:得到InvocationHandler接口的实现类实例

    3.动态代理与AOP(Aspect Orient Programming)

    前面介绍的Proxy和InvocationHandler,很难看出这种动态代理的优势。

    在这里插入图片描述
    代码段1,2,3中都存在有相同的代码段,按照一般思路可以将相同的代码段封装成公共的方法,然后进行统一的调用。
    在这里插入图片描述
    改进后的说明:代码段1、代码段2、代码段3和深色代码段分离开了,但代码段1、 2、 3又和一个特定的方法A耦合了!

    最理想的效果是:代码块1、 2、 3既可以执行方法A,又无须在程序中以硬编码的方式直接调用深色代码的方法

    AOP(Aspect Orient Programming)面向切面编程:

    使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理,这种动态代理在AOP中被称为AOP代理——AOP代理可代替目标对象, AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:

    • AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理

    在这里插入图片描述

     
    /**
     * 动态代理与AOP的举例
     * @author zxy
     */
    //1.被代理类实现的接口
    interface Human{
        String getBelief();
        void eat(String food);
    }
    //2.被代理类
    class SuperMan implements Human{
        @Override
        public String getBelief() {
            return "I believe I can fly!";
        }
        @Override
        public void eat(String food) {
            System.out.println("我喜欢吃" + food);
        }
    }
    //AOP代理演示
    class HumanUtil{
        public void method1(){
            System.out.println("====================通用方法一====================");
    
        }
        public void method2(){
            System.out.println("====================通用方法二====================");
        }
    }
    
    /*
    要想实现动态代理,需要解决的问题?
        问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
        问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
     */
     //3.代理类
    class ProxyFactory{
        //调用此方法,传入一个被代理类的实例,返回一个代理类的对象:用于解决问题一
        public static Object getProxyInstance(Object obj){//obj:被代理类的对象
            //指派方法调用的调用处理程序 :用于解决问题二(详见4.)
            MyInvocationHandler handler = new MyInvocationHandler();
            handler.bind(obj);
            
            //调用Proxy.newProxyInstance():返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
        }
    }
    
    //4.java.lang.reflect.InvocationHandler 是代理实例的调用处理程序 实现的接口
    class MyInvocationHandler implements InvocationHandler{
        private Object obj;//需要使用被代理类的对象进行赋值
        //用于绑定被代理类的实例
        public void bind(Object obj){
            this.obj = obj;
        }
    
        //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
        //将被代理类要执行的方法a的功能就声明在invoke()中
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //演示AOP代理增加的通用方法
            HumanUtil util = new HumanUtil();
            //通用方法1
            util.method1();
    
            //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
            //obj:被代理类的对象
            Object returnValue = method.invoke(obj,args);
            
            //通用方法2
            util.method2();
            
            //上述方法的返回值就作为当前类中的invoke()的返回值。
            return returnValue;
        }
    }
    //测试类
    public class ProxyTest {
    
        public static void main(String[] args) {
            //创建一个被代理类的实例
            SuperMan superMan = new SuperMan();
            //proxyInstance:代理类的对象
            Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
            //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
            String belief = proxyInstance.getBelief();
            System.out.println(belief);
            proxyInstance.eat("四川麻辣烫");
    
            System.out.println("*****************************");
    
            NikeClothFactory nikeClothFactory = new NikeClothFactory();
    
            ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
    
            proxyClothFactory.produceCloth();
        }
    }
    View Code

    对比静态代理:代理类和被代理类在编译期间,就确定下来了

    /*
     * 静态代理举例:
     * 特点:代理类和被代理类在编译期间,就确定下来了。
     */
    interface ClothFactory{
        void produceCloth();
    }
    
    //代理类
    class ProxyClothFactory implements ClothFactory{
        private ClothFactory factory;//用被代理类对象进行实例化
        public ProxyClothFactory(ClothFactory factory){
            this.factory = factory;
        }
        @Override
        public void produceCloth() {
            System.out.println("代理工厂做一些准备工作");
            factory.produceCloth();
            System.out.println("代理工厂做一些后续的收尾工作");
        }
    }
    
    //被代理类
    class NikeClothFactory implements ClothFactory{
        @Override
        public void produceCloth() {
            System.out.println("Nike工厂生产一批运动服");
        }
    }
    //测试类
    public class StaticProxyTest {
        public static void main(String[] args) {
            //创建被代理类的对象
            ClothFactory nike = new NikeClothFactory();
            //创建代理类的对象
            ClothFactory proxyClothFactory = new ProxyClothFactory(nike);
            proxyClothFactory.produceCloth();
        }
    }
    View Code

    转自https://blog.csdn.net/select_alter_drop/article/details/98882949

  • 相关阅读:
    模拟google分页效果
    真理胜于一切 JAVA模拟表单提交
    springboot @vaule注解失效解决办法
    安装cnpm
    公众号微信支付开发
    vue去掉链接中的#
    springboot集成mongoDB简易使用
    Spring boot中使用aop详解
    Promise 的基础用法
    MySQL的if,case语句使用总结
  • 原文地址:https://www.cnblogs.com/Ke-Me/p/14266162.html
Copyright © 2011-2022 走看看