Java 基础 - 反射

1. 运行时类信息

Java中允许我们在运行时动态的获取某个Class的信息,通过一个Class对象返回给我们,Class对象中记录了这个类的各种信息,例如类名,字段,方法等等

2 为什么需要运行时信息

2.1 判断运行时对象的具体类型

在Java的多态中,常常会声明一个父类,然后传入子类对象,但有时我想对某些特殊的子类对象进行操作,需要通过子类的一些类信息进行判断
比如我在做CRUD的时候,我想对含有update_time字段的类进行设值,更新为当前时间,那么我如何针对每一个类做这件事情?
我要做的事情有两件:

  1. 查看这个类是否有update_time字段
  2. 根据这个类创建一个对象并调用对象的setUpdateTime方法(我们假设这个实体类是符合JavaBean规范的)
    那么这两个事情都可以通过反射做到。这一点也可以说成是希望针对不同类型的对象做一些通用的处理时可以使用反射

2.2 配置

反射中可以通过类的全路径类名来获取Class对象并创建实例,这给我们的程序带来了很大的灵活性。可以了解一下SPI机制,在springboot中使用的非常频繁
简单来说,就是当我们的一个接口拥有多种实现的时候,我们可以通过一个配置来决定使用哪一个实现(也可以全部实现),对使用程序的人来说非常方便,不用了解程序的具体逻辑,只需要更换配置文件即可。
例如说:我在获取web响应的时候,想要自己添加一些处理,比如说我想转一下驼峰,删除或增添一些字段,可以通过配置的方式决定使用哪一种实现

2.3 类型检查

在没有泛型的年代,我们从Java的集合中拿出一个对象都是Object类型,假设我们知道传进去的都应该是Integer类型,此时我们会进行强转,为了更安全的转换,在转换之前会进行检查,判断某个对象是否属于某个类,此时可以利用反射中的 instanceof关键字

2.4 小结

总结来说,反射的作用有以下几点:

  1. 获取类的信息
  2. 动态创建对象
  3. 方法调用
  4. 判断对象是否属于某个类

3 如何获取类的信息

  1. Class.forName,不持有对象,仅靠类型就可以拿到Class对象
  2. int.class,明确的知道某个类的类型
  3. obj.getClass(),只能拿到对象

3.1 获取类信息与初始化

使用.class时不会进行初始化
类加载的三个过程:

  1. 加载,这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个 Class 对象。>
  1. 链接,链接阶段将验证类中的字节码,为 static 字段分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。
  1. 初始化,如果该类具有超类,则先初始化超类,执行 static 初始化器和 static 初始化块。

—————— 《OnJava8》
.class只是加载和链接而已,不会进行初始化

3.2 创建对象

可以使用Class的newInstance()方法创建对象,可是如果你是直接使用newInstance(Constructor的就不进行举例了),被创建的对象必须有无参的构造函数,例如下面的例子会报错

Class<?> clazz = Integer.class;
Object obj = clazz.newInstance();

Integer没有无参构造(复习一下类初始化:当类自己有构造函数时,编译器就不会帮你默认构造一个了)

3.3 方法调用

调用Methodinvoke方法即可,JDK的文档中对这个方法已经写的非常详细了,各种情况都列举到了,此处不再赘述,贴一下文档

/**
     * Invokes the underlying method represented by this {@code Method}
     * object, on the specified object with the specified parameters.
     * Individual parameters are automatically unwrapped to match
     * primitive formal parameters, and both primitive and reference
     * parameters are subject to method invocation conversions as
     * necessary.
     *
     * <p>If the underlying method is static, then the specified {@code obj}
     * argument is ignored. It may be null.
     *
     * <p>If the number of formal parameters required by the underlying method is
     * 0, the supplied {@code args} array may be of length 0 or null.
     *
     * <p>If the underlying method is an instance method, it is invoked
     * using dynamic method lookup as documented in The Java Language
     * Specification, Second Edition, section 15.12.4.4; in particular,
     * overriding based on the runtime type of the target object will occur.
     *
     * <p>If the underlying method is static, the class that declared
     * the method is initialized if it has not already been initialized.
     *
     * <p>If the method completes normally, the value it returns is
     * returned to the caller of invoke; if the value has a primitive
     * type, it is first appropriately wrapped in an object. However,
     * if the value has the type of an array of a primitive type, the
     * elements of the array are <i>not</i> wrapped in objects; in
     * other words, an array of primitive type is returned.  If the
     * underlying method return type is void, the invocation returns
     * null.
     *
     * @param obj  the object the underlying method is invoked from
     * @param args the arguments used for the method call
     * @return the result of dispatching the method represented by
     * this object on {@code obj} with parameters
     * {@code args}
     *
     * @exception IllegalAccessException    if this {@code Method} object
     *              is enforcing Java language access control and the underlying
     *              method is inaccessible.
     * @exception IllegalArgumentException  if the method is an
     *              instance method and the specified object argument
     *              is not an instance of the class or interface
     *              declaring the underlying method (or of a subclass
     *              or implementor thereof); if the number of actual
     *              and formal parameters differ; if an unwrapping
     *              conversion for primitive arguments fails; or if,
     *              after possible unwrapping, a parameter value
     *              cannot be converted to the corresponding formal
     *              parameter type by a method invocation conversion.
     * @exception InvocationTargetException if the underlying method
     *              throws an exception.
     * @exception NullPointerException      if the specified object is null
     *              and the method is an instance method.
     * @exception ExceptionInInitializerError if the initialization
     * provoked by this method fails.

3.4 泛化的类型

1.5之后可以使用泛型来描述类对象

// int 和Integer的Class对象虽然都是Class<Integer>类型,但他们并不相同
Class<Integer> clazz = int.class;
int.class == Integer.class; // false
int.class == Integer.TYPE; //true

Class 与 Class<?>

使用 Class 比单纯使用 Class 要好,虽然它们是等价的,并且单纯使用 Class 不会产生编译器警告信息。使用 Class 的好处是它表示你并非是碰巧或者由于疏忽才使用了一个非具体的类引用,而是特意为之。
—————— 《OnJava8》
Class 也同样可以使用泛型的上下界修饰符
Class<Number> clazz = int.class
Class<? super Integer> up = int.class.getSuperclass();,感觉没啥卵用,因为取出来是个Object

4 反序列化

反射一个典型的应用就是反序列化,即我们在进行编译时,反序列化并不清楚我们最后会生成什么样的对象,所以必须通过字节流生成Class对象,然后通过newInstance进行初始化并赋值。

不同的序列化形式

除了Java序列化之外,还有一些其他的序列化方式,例如JSON序列化,由于json与Java的类型结构并不相同,所以需要指定一套规范,一般就是Java Bean的规范,这样可以保证我在获取属性值以及赋值时都能找到需要的方法,从而进行反射调用。
关于序列化其实还有很多小知识点,有时间再单独写一篇吧。

5 动态代理

动态代理也是一个稍微有些复杂的东西,也会单独再写一篇,这里简单梳理一下:

前提

代理类和真实类实现同一个接口

代理类

如果Class.forName()只是动态的去加载类,那么动态代理更过分,因为根本就没有这个所谓的代理类(连class文件都没有),还得代理工厂自己创建一个包名,Class名称,自己生成一个虚拟机可以认识的字节数组(字节码,相当于没有java文件,直接手写class文件),再通过ClassLoader在运行时动态的装载到虚拟机里面去,老辛苦了(还好有缓存,每个接口只需要创建一次对象),感兴趣可以去看下ProxyClassFactory(Proxy 中的一个内部类)中的apply方法,
代理对象自然也需要通过new instance()来创建啦

代理方法

动态代理对象创建出来之后也不会自己好好去执行方法,而是利用被代理对象(realObject)去进行调用。
实现的方式:

调用动态代理对象方法 -》 转发到InvocationHandler(某个调用处理程序),在这里搞搞日志啥的 -》 利用真实的对象进行方法调用,进行核心方法调用(就是利用的反射调用方法)

你问为什么代理对象可以转发到InvocationHandler? 你以为动态代理对象的方法会是规规矩矩的正经方法吗?
可以参考这篇文章 不学无数——Java动态代理
这里简单截取部分内容:

public final class $Proxy0 extends Proxy implements Homeowner {
    private static Method m4;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final void LeaseHouse(Home var1) throws  {
        try {
            // 这里的h就是创建InvocationHandler,从父类Proxy继承的
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m4 = Class.forName("Practice.Day06.Homeowner").getMethod("LeaseHouse", Class.forName("Practice.Day06.Home"));
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

写到这里,整个动态代理的流程就比较清晰了
感觉好像把大部分会的内容都写完了,还能单独再写一篇动态代理吗。。。。

完。

posted @ 2021-03-24 23:01  calmbook  阅读(59)  评论(0)    收藏  举报