面试之敌系列 4 JAVA 反射和泛型

面试之敌系列 4 JAVA 反射和泛型

一个对象其实分成三部分,对象头,类型指针和实例数据。其中,对象头主要用于GC,线程控制等。类型指针用于获取这个类的Class 信息。最后的初始化的数据就是放在示例数据中的。

补充信息

运行时数据区域

泛型

泛型,就是允许在定义类,接口,方法的时候使用类型参数,这个参数可以在类型的声明,方法的调用,实例的创建的时候动态的指定。

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。Java语言引入泛型的好处是安全简单。

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。

  1. 使用泛型可以在编译使其进行类型检查,保证不会出现类型转换的错误。
  2. 消除显式的类型转换,类型转换会隐式的自动进行(实际上,泛型会进行擦除,可能擦除到object等。也会自动的类型转换回来。)

通配符T,E,K,V区别

这些全都属于java泛型的通配符,刚开始我看到这么多通配符,一下晕了,这几个其实没什么区别,只不过是一个约定好的代码,也就是说

  • ? 表示不确定的java类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element

除非使用了extends 否则泛型不存在所谓的子类型父类型,他们一定是不一样的。当使用了extends的时候,就可以接受其子类或者父类了。

Class 类是用于管理所有的类元信息的。但是,每个类的Class 实例是不一样的。这里的Class就像Object 一样,是一个基类。Class 也是一个泛型类,通过使用泛型可以避免反射的时候需要使用显式类型转换(强制的类型转换)。

面试之敌系列 4 JAVA 反射和泛型
面试之敌系列 4 JAVA 反射和泛型

由上面的例子可以知道,使用了泛型之后,类型转换可以隐式的进行。而且泛型擦除会保证类型的一致,减少了显式类型转换和Object 带来的一些安全隐患。

明确声明和定义的区别

类的定义的时候,需用的是T E V K 等,声明的时候可以使用?,但是不可以使用T E V K· 也就是说,定义的时候除了?不可以用,其他的都可以,但是在声明和使用的时候,因为需要传递具体的类型进T E K V 但是如果此时不知道,也可以直接使用?

反射

什么是反射:反射提供了一种在运行的时候动态的获取已给对象的所有信息的方法。对于反射机制,可以动态的调用其任意的属性和方法。反射机制的一些日常使用:编译器的智能提示等。java里面使用reflect 和 class 两个技术来实现反射机制。(单纯的reflect 类不是反射机制)。其中最长用的主要有三个类

Method

获得一个类或者接口的方法

Field

获得一个类或者接口的属性

Constructor

获得一个类的构造器。

动态代理

参考文献 Spring 容器AOP的实现原理——动态代理

动态代理和静态代理到底有什么区别,好处在哪里?

Java InvocationHandler 与 Proxy 动态代理详解

CGLib动态代理的介绍及用法(单回调、多回调、不处理、固定值、懒加载)

Java 动态代理详解

动态代理流程

  1. 实现一个InvocationH接口的类,用于实现统一的方法调用。动态代理类中,其实需要传递一个invocationH 接口实现类。而且,通过反编译可以知道,动态代理类中的方法其实都是调用了invocationHandle 实现类中的方法。实现了功能的转移。这样我们直接将逻辑代码维护在invocationHandle实例中即可。
  2. 创建代理对象的时候,需要传递的有
    1. invocationHandle实例的加载器
    2. 代理对象的所有接口
    3. invocationHandle实例。

由于代理的逻辑是在invocationHandle实例中实现,因此这个invocationHandle实例需要传入一个真实的需要代理的类。

创建代理对象的时候,其实已经通过反射机制,获得了真实被代理类的所有的方法和属性。

面试之敌系列 4 JAVA 反射和泛型

由上面可以知道,通反射机制,已经获得了所有的方法,也就是已经有了所有的方法的Method实例。 然后,观察代理类可以知道,代理类重写了所有的方法,并且,每个方法都是通过调用invocationHandle实例的invoke方法来调用到我们自己实现的逻辑,这个invoke函数需要传递一个Method 对象,其实就是静态代码块中已经获取的。invoke中的第一个参数,其实就是这个动态代理实例自身(this)。

面试之敌系列 4 JAVA 反射和泛型
  1. 静态代理:需要自己编写源代码,再进行编译,在运行之前就知道其对应的.class文件动态代理在运行之前不知道自己代理的的是什么。他需要通过运行时的反射机制得到自己的代理接口。在对知道的接口进行实现,从而生产代理实例的字节码文件.class。

  2. 静态代理通常只代理一个类,(也就是接口的一种实现而已)而动态代理类可以代理整个接口的各种不同的实现

  3. 动态代理通过反射机制确实可以知道自己需要代理的所有的接口,这时候可以自动的生产代理类 代理类的生产过程和静态代理类的编写过程基本一样。只是方法的调用采用invocationHa来进行统一的管理invocationHa 实例实现了invoke 方法。因此静态代理类进行方法的调用的时候,其实的转到了invocationHandle 实例中的invoke执行。

在invoke中进行代理业务的编写。 这时候,invocationHandle 实例中有真实类,编写业务即可。总的来时,以前的静态编译中

  1. 创建需要代理的类实例,
  2. 通过类实例进行各种方法的覆写。

而动态代理中,创建的代理类中的业务只是实现了对接口方法的覆写。注意这里没有创建需要代理类的实例。所有的业务都是在invocationHandle中实现的动态代理类中,方法的覆写中,通过传入的 invocationHandle 实例,实现代理方法的实现。

这样做的好处就是可以将需要代理的所有的方法覆写都抽象出来,采用invocationHandle 来统一实现在invocationHandle实现中,需要传入真的类。这点和静态代理不一样,动态代理中的真实类是在invocationHandle而,静态代理中是在代理类中的invocationHandle实例中需要实现代理类中调用的invoke方法。invoke方法中通过反射机制对 方法进行执行method.invoke(real_class,args)。其中的real_class就是真实的类。这样,对接口的不同的实现,就被抽象出来,不用像静态代理一样,每个不同的实现类都编写一个代理类。而且,从invocationHandle的实现中可以知道,我们采用的是Object,是在使用的时候通过构造方法传入真实的类。也就是说我们的invocationHandle 是一个公共的代理业务实现在spring中,这就相当于一个公共服务。系统服务,如果将spring中的每个对象都看成代理对象的话,那么我们就可以为为每一个实例都提供相同的系统服务了。invoke 方法前后的服务是一致的,代理的真实列是Object 也就是可以忍任意性。例如,我们在使用事务的时候,其实是将某个实例的对象看成代理对象。方法的调用转到 invoke中了,而在invoke的前后是可以进行事务的开启的。或者日志的写入等操作。从而实现aop变成。代理是,在实现基础任务的时候,可以加入一些额外的服务。invoke 前和后。

aop 的几种实现机制

CGLib

CGLIB 创建动态代理类的模式是:

  1. 查找目标类上的所有非final 的public类型的方法定义;

  2. 将这些方法的定义转换成字节码;

  3. 将组成的字节码转换成相应的代理的class对象;

  4. 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求

    /**
      * 同样的套路,动态代理类通过反射或者字节码库对真实代理类的方法进行覆写,并返回一个代理类
      * 代理类中需要注册一个方法的统一调用管理函数。用于在代理类进行方法调用的时候的转移。
      * 代理类调用方法的时候,其实就是执行动态生成的代理类中的方法,由于该方法已经被覆写,因此会将功能
      * 转移到写在invocat实例中的invoke方法。
      * */
    复制代码

详解cglib

分析过程:

  1. 创建Enhancer实例

  2. 设置目标类Target为Enhancer的父类,同时为Target的每个非private方法生成两个方法:以g()方法为例,会生成:g()和CGLIB$g$0。测试代码中t.g()调用的是代理类的g()

  3. 设置拦截器

    1. 调用t.g(),g()方法先判断是否已经存在实现了MethodInterceptor接口的拦截对象,如果没有的话就调用CGLIB$BIND_CALLBACKS$方法来获取拦截对象。
    2. CGLIB$BIND_CALLBACKS先从CGLIB$THREAD_CALLBACKS中get拦截对象,如果获取不到的话,再从CGLIB$STATIC_CALLBACKS来获取,如果也没有则认为该方法不需要代理。
    3. 拦截对象是如何设置到CGLIB$THREAD_CALLBACKS 或者 CGLIB$STATIC_CALLBACKS中的呢?即cglib代理是在eh.create()时调用了firstInstance方法来生成代理类实例并设置拦截对象。
  4. 当获取到拦截对象,再调用tmp4_1.intercept(this, CGLIB$g$0$Method,CGLIB$emptyArgs, CGLIB$g$0$Proxy)来实现代理$

  5. tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy)中的tmp4_1为拦截器, 参数解析,第1个:代理对象本身;第2个:被拦截的方法对象;第3个:方法调用入参;第4个:被拦截方法的方法代理对象,此时调用MyInterceptor的intercept方法进行代理

  6. 调用proxy.invokeSuper(obj, args),这里使用的是FastClass机制,FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法,当调用invokeSuper方法时,实际上是调用代理类的CGLIB$g$0方法,CGLIB$g$0直接调用了目标类的g方法

jdk动态代理

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler 详解的介绍上面已经有了

aspectJ 切面机制

www.cnblogs.com/2015110615L…

www.jianshu.com/p/f90e04bcb…

Class 类

每个类加载的时候,除了将二进制的字节码文件加载到内存中,JVM还会实例化一个Class类的对象,用于存储关于该类的元信息。注意,由于每个类的实例不不一样,但是他们指向的类型指针都是同一个的Class对象。而且,Class一般使用泛型,也就是Class的时候,可以传递自己的具体的类型进行Class的获取。这个Class其实就是类似于一个集合。Class+reflection 共同组成了 JDK 的反射机制。

原文 

https://juejin.im/post/5f0d726a6fb9a07e926ceab8

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 面试之敌系列 4 JAVA 反射和泛型

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址