6 类的加载、RTTI以及动态代理

类的加载、RTTI以及动态代理


类的加载:

双亲委派模型:

  1. 双亲委派:除非父加载器找不到相应的类,否则尽量将这个任务代理给当前类加载器的父加载器去做,用双亲委派模型的目的是为了避免重复加载类;
  2. 类加载器分类:
    1. 启动类加载器(Bootstrap Class Loader):加载jre/lib下的jar文件,即便开启了Security Manager的时候,JDK仍赋予了它加载程序的AllPermission;

    2. 扩展类加载器(Extension Class Loader):负责加载jre/lib/ext/下面的jar包,该目录可以通过设置java.ext.dirs来覆盖

       java -Djava.ext.dirs=your_ext_dir HelloWorld
      
    3. 应用类加载器(Application Class Loader):加载我们最熟悉的classpath的内容,也有人叫系统类加载器;可以通过下面的参数修改:

       java -Djava.system.class.loader.=com.yourcorp.YourClassLoader HelloWorld
      
    如果我们指定了这个参数,JDK内建的应用类加载器就会成为定制类加载器的父亲,这种方式通常用在类似需要改变双亲委派模型的场景;
    1. 图示例:


      类加载器图.png

      类加载器示意图

    2. 注意情况:

      1. 不是所有情况都遵循双亲委派模型,有时候系统类加载器需要加载用户代码,如JDBC,这时候不会使用双亲委派模型去加载,而是使用上下文加载器;
      2. 可见性:子类加载器可以访问父类加载器加载的,但反过来是不允许的;
      3. 单一性:由于父加载器加载的类对子类是可见的,所以父加载器加载过的类子加载器是不会再加载的,但是注意,同级的加载器,同一类型仍然可能被加载多次,因为互相并不可见;
  3. 类加载器优先级:(待完善)

类加载过程分为加载、链接及初始化:

  1. 加载:由类加载器执行,查找字节码,并创建一个Class对象;

  2. 链接:

    1. 验证:校验字节信息是符合JVM虚拟机规范的,验证阶段有可能会触发更多的class加载(JVM基础中同样提到类的加载,并提到触发类的加载是在解析阶段,对符号引用的解析会触发更多的类加载);
    2. 准备:为静态域分配空间,并清空内存,值为0、false或者null之类;
    3. 解析:解析这个类创建的对其他类的所有符号引用;
  3. 初始化:执行静态初始化器和静态初始化块;初始化被延迟到对静态方法(构造器隐式的是静态的)或者非常数静态域进行首次引用时执行,注意访问以下字段会触发初始化,因为需要执行静态初始化器后才有具体的值:

     public static final  int number = new Random(xx).nextInt(100);
    

访问以下字段则不会触发初始化:

    public static final int count = 20;

RTTI:

Class引用的一些基础知识:

  1. Class引用可以引用某个Class对象,如果不指定泛型参数,那么该引用可指向任意的Class对象,如下代码是可以通过编译的:

     Class ref = int.class;
     ref = double.class;
    
  2. Class<?>与Class是等价的,但通常在不限制引用的Class对象类型时候推荐使用Class<?>,好处在于它表示我们并非是碰巧或者是疏忽,而是我们就是要选择非具体的版本;

  3. 泛型参数可以限定Class引用所引用的对象,让Class引用的类型更加具体,添加泛型语法的原因是为了提供编译器类型检查;如:
    引用单一类型:

     Class<Integer> integerRef = int.class;
    

引用某一类型的子类:

    Class<? extends Number> numberRef = int.class;
  1. 不能将Class<Integer>引用赋值给Class<Number>的引用,虽然Integer和Number之间存在继承关系,但Class<Integer>和Class<Number>之间并不存在继承关系,他们都是Class引用,泛型参数的作用是限定引用范围,需要仔细体会他们的区别,如下代码是错误实例

     Class<Number> numberRef = int.class;
    
  2. Class引用可通过newInstance方法来创建一个对象,需要该类有无参构造函数,且当前代码位置可访问该构造函数,否则没有访问权限时将抛出IllegalAccessException,如果该Class不能被实例化将抛出(Class是接口、抽象类、原始数据类型或者类本身没有无参构造函数等情况)InstantiationException,他们均是ReflectiveOperationException的子类;如果Class引用指定了泛型参数,则newInstance的返回值会自动转换为泛型参数的类型;
    带泛型参数的Class引用创建对象:

     Integer i = int.class.newInstance();
    

如果泛型参数指定上界则返回值转换为上界:

    Class<? extends Number> ref = int.class;
    Number number = ref.newInstance();

如果泛型参数指定的是下界,则返回值只能是Object,因为唯一确定的是返回值一定是Object或其子类;

    Class<? super String> ref = String.class;
    Object o = ref.newInstance();

获取Class对象引用的方式有三种:

  1. 通过Class类的public static Class<?> forName(String className)方法获取Class对象的引用,该方法需要传入一个String类型的参数,该参数指明需要获取的类对象的名字,注意className需要包含包名,返回Class对象(是Class对象,不是平时new出来的对象),如果找不到要加载的类则会抛出ClassNotFoundException;注意,返回值是Class<?>,泛型参数是?,代表任意类型,这意味着,即使我们知道forName加载的String类型,也不能直接用Class<String>类型来接收返回值;以下代码无法通过编译

     Class<String> stringClassRef = Class.forName("java.lang.String");
    

正确代码应该为:

    Class<?> ref = Class.forName("java.lang.String");

或者进行强转:

    Class<String> stringClassRef = (Class<String>)Class.forName("java.lang.String");

Class.forName会触发类的加载、链接及初始化

  1. 通过感兴趣的对象引用.getClass()函数来获取Class引用(这是Object的一个成员方法),如(注意泛型参数写法,因为String类型的引用其对象不一定就是String还有可能是String的子类对象):

     String name = "abc";
     Class<? extends String> ref = name.getClass();
    
  2. 通过“类名.class”来获取类对象引用,此时Class引用的泛型参数可填写对应的参数;值得注意的是,该访问方式并不会自动初始化该Class对象(static代码块不会被执行);该方法相比于第一种方法将会得到编译期检查的好处,但同时也变成了硬编码;

     Class<String> ref = String.Class
    

包装类型有一个标准字段TYPE,指向对应的基本数据类型的class对象,即:

    //true
    int.class == Integer.TYPE;

注意,即使是基本类型的Class引用,泛型参数也应该填写包装类,因为泛型参数不能是基本类型,即:

    Class<Integer> intRef = int.class;
    //特别要注意的是int.class != Integer.class,以下代码会输出false;
    System.out.println(int.class == Integer.class);

instanceof 与 isInstance

  1. 介绍:

    • instanceof:instanceof是关键字,使用方法如下:

        Integer i = 1;
        if(i instanceof Integer){
            //do someting
        }
      

    instanceof关键字后面跟随的类名,是对象的类、父类及实现的接口时为true,不能跟Class对象,这意味着如果有多个判断则需要依次编写千篇一律的代码,isInstance可以很好的解决这个问题,但 通常如果程序中编写了许多instanceof或者isInstance,就说明我们的设计可能存在瑕疵,因为程序太过依赖具体的细节而失去泛化能力;

    • isInstance:isInstance是类Class的一个成员函数:

        boolean isInstance(Object o);
      

    使用方法如下:

         Class<Integer> ref = Integer.class;
         if(ref.isInstance(Integer.valueof(1)){
             //do someting
         }
    
  2. 与== 和equals的区别:

    instanceof 和 isInstance的作用完全一致,他会判断是否是该类(接口)或者是否是其子类,但==或者equals则不会判断是否是其子类的情况,如下所示:

     //true
     Number.class.isInstance(Integer.valueof(1));
    
     //false
     Integer i = 1;
     i.getClass().equals(Number.class);
    
     //无法通过编译,提示类型不可比较
     Integer i = 1;
     Number.class == i.getClass();
    

反射:运行时类信息

Class类与java.lang.reflect类库一起对反射的概念进行了支持;

  1. 常用反射相关类的介绍:

    1. Field:提供类或者接口的单个字段的信息,以及对他的动态访问权限,是final class;

       java.lang.Object
           java.lang.reflect.AccessibleObject
               java.lang.reflect.Field
      
    2. Method:提供类或接口的单独某个方法的信息;同样继承自AccessibleObject,是final class

    3. Constructor:提供类的单个构造方法的信息,继承自AccessibleObject,是final class;

  2. 常用反射相关类的获取,以Field为例,其他都是类似的:

     通过Class对象获取Field获取方法有四个:
     //获取该类声明的公有属性,包括继承而来的属性(父类、接口)及静态属性,如果父类、子类存在同名属性,那么他们都会被加入到数组中,返回的数组中的元素没有排序,也没有任何特定的顺序;
     Field[] getFields();
    
     //在该类声明的共有属性中查找名为name的属性,如果找不到则抛出NoSuchFieldException,如果父类和子类存在同名Field,则返回子类的Field;
     //查找顺序为:1.本类中查找。2.按声明顺序查找接口。3.查找超类;
     Field getFiled(String name);
    
     //获取该类的所有的属性(包括protected、private),不包括继承而来的属性
     Field[] getDeclaredFields();
    
     //从所有属性中搜索名为name的属性,找不到则抛出NoSuchFieldException
     Field getDeclaredField(String name);
     
     //如果类型为基本类型、void或者数组类型,则返回长度为0的数组,下面的代码将返回一个空的Field数组;
     int.class.getFields()
    
  3. 常用反射相关类的使用:

    1. Field主要方法为set和get,并针对基本类型提供了多个版本,下面仅列出引用类型版本:

       //o指定需要访问的对象;
       Object get(Object o);
       void set(Object o,Object value)
      
       //背景class声明
       public class ReflectBase {
         public static String name = "Base";
         private static String description = "base option";
         public int n = 2;
         private int m = 3;
       }
       public class Reflect extends ReflectBase implements IReflect2,IReflect{
         public static final int number = 1;
         private static String name = "reflect";
         protected int a = 4;
         public int count = 2;
         private int sum = 3;
       }
      
       //Field访问共有属性;
       Reflect r = new Reflect();
       Field field = Reflect.class.getField("count");
       //输出2
       System.out.println(field.get(r));
       field.setInt(r,10);
       //输出10
       System.out.println(field.get(r));
      
       //Filed访问没有权限的字段:
       ReflectBase base = new ReflectBase();
       Field f = ReflectBase.class.getDeclaredField("m");
       //无法访问该字段,设置访问权限
       f.setAccessible(true);
       //输出3
       System.out.println("before modify sum : " + f.get(base));
       f.setInt(base,100);
       //输出100;
       System.out.println("after modify sum : " + f.get(base));
      
    2. method最常用的方法:

       Object invoke(Object obj, Object... args) 
      

      方法说明:obj指定调用方法的实例,如果是静态方法,可以传null,args为参数,如果没有可以不传或者传null,如果参数是基本类型,则会触发拆箱操作;如果方法返回值为void,则返回null,如果返回值为基本类型,则会触发拆箱,如果是基本类型数组,则数组元素不会被包装在对象中;

    3. Constructor

       T newInstance(Object... initargs) 
      

      T为泛型参数,如果不需要形参,则可以传null或者不传,如果构造方法的声明类是非静态上下文的内部类,则构造方法的第一个参数需要是封闭实例,如果底层构造方法的类是抽象类则会抛出InstantiationException

  4. 反射通常可以使用AccessableObject类的SetAccessable方法来修改权限,让本来是私有的成员变量或方法变得可以访问,JDK8中并没有很好的方法去阻止这件事情的发生,JDK9以后进行了改进;但应该注意,对于final域的修改,系统会在不抛出异常的情况下接受任何修改尝试,并且不会发生任何修改;

JDK动态代理介绍、使用方法及在空对象方面的使用案例

  1. 简介:
    代理是基本的设计模式之一,动态代理能够动态地创建代理并动态的处理对代理方法的调用,可以很方便的为已有的类插入额外逻辑而不必修改原有的代码;JDK动态代理使用反射机制来完成;

  2. 基本使用方法:

     //示例接口
     public interface ICanTalk {
       public void talk(String content);
     }
    
     //被代理的类
     public class People implements ICanTalk {
       @Override
       public void talk(String content) {
         System.out.println("people speak : " + content);
       }
     }
    
     //实现InvocationHandler的类声明
     class PeopleProxy implements InvocationHandler{
       Object proxied;
       Object proxy;
       PeopleProxy(Object proxied){
         this.proxied = proxied;
       }
       /**
         * invoke函数为插入额外逻辑入口
         * proxy为动态生成的代理类实例
         *
         */
       @Override
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         //额外逻辑一:
         System.out.println("**** proxy : " +proxy.getClass() + ", method : " + method + ",args: " + Arrays.toString(args));
         //真正的逻辑调用
         //如果将ivoke函数的proxied换成proxy将会无限递归调用自己,但在JDK8下此处不会造成栈溢出
         //而是持续一段时间后程序退出(Process finished with exit code 1):
         Object result = method.invoke(proxied,args);
         //将其赋值给成员变量proxy,用来验证它就是动态生成的代理类实例;
         this.proxy = proxy;
         //额外逻辑二
         System.out.println("**** proxy end");
         return result;
       }
     }
    
     //测试main函数:
    
       public static void main(String[] args) {
         People p = new People();
         PeopleProxy proxy = new PeopleProxy(p);
         ICanTalk canTalk = (ICanTalk) Proxy.newProxyInstance(People.class.getClassLoader(),People.class.getInterfaces(),proxy);
         p.talk("我困了");
         canTalk.talk("我饿了");
         //true
         System.out.println(canTalk == proxy.proxy);
       }
    
  3. 动态代理在空对象方面的使用:

    1. 空对象的定义

      简单理解为:该对象没有任何有意义的信息,但不将引用设置为null;书面解释:一个对象可以接受传递给它所代表的对象的消息,但是返回表示为:实际上并不存在任何真实对象的值;

    2. 空对象的意义

      如果将引用设置null,那么在每次使用引用时都需要测试其是否为null,并且null在除了在尝试操作它时抛出NullPointerException以外没有任何有效行为,那么我们可以创建一个空对象,能接受发给他的信息,但啥也不干,这样在原本需要将引用赋值为null时,将该空对象赋值给引用,这样客户端就不需要在去检查null,可以假设所有对象都是有效的,直接操作,这样也能简化代码,当然,我们也可能在实际使用中依然要去检测是否为空对象,这个问题使用mark interface即可解决;

    3. 动态代理实现空对象优势(仔细思考后,觉得动态代理实现空对象并没有什么优势)

      1. 手动实现统一的空对象

        我们可以手动实现空对象,如果针对接口实现一个统一的空对象,这样则没有更多的子类信息;如:有一个“动物”接口,有“猪”“猫”“狗”实现类,我们可以统一编写一个动物空对象,实际情况可能够用了,这里我们想讨论另一种情况,如果我们想区分这是一个空的猪对象或者空的猫对象,那么这种方法就无法实现了(此处有误,仔细思考可以在不同实现类中实例化不同的动物空对象,可以实现区分)

      2. 手动为每种动物实现类编写一个空对象类继承自该类

        这样显得很枯燥,为什么要重新编写空对象类,不将对象的属性置为0或者null来实现空与非空的区别呢,如果该对象存在的意义就是属性且正常属性不会出现0
        或者null的的话(否则无法分辨是否是空对象),我们可以这么做;但如果空对象的行为也需要改变,那么这么做可能达不到我们想要的效果;

      3. 使用动态代理实现空对象(仔细思考,并无优势)

        使用动态代理,invoke方法中不实际调用即可;每种实现类中实例化不同的代理实例,仔细思考,这相对于第一种方法并没有太大的优势,好处仅仅在于不需要编写那么的空函数,在IDE能自动生成空的接口方法的情况下,貌似已经没什么优势了,反而反射调用会带来性能开销;代码已实现,但发现并无意义,就不上代码了;

动态代理的JDK实现和cglib两种实现对比

  1. JDK的优势:

    1. 最小化依赖关系
    2. 平滑的JDK版本升级
    3. 代码实现简单
  2. cglib的优势:

    1. 有时候调用目标可能不便于实现额外接口,限定调用者实现接口是有些侵入性的实践,cglib没有这个限制;
    2. 只操作我们关系的类,不必为其他类增加工作量;
    3. 高性能;

因为cglib是创建被代理对象子类的关系,所以几乎没有能力退化,但也带来了一些问题,如被代理对象的final方法无法使用;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,965评论 6 13
  • 对象的创建与销毁 Item 1: 使用static工厂方法,而不是构造函数创建对象:仅仅是创建对象的方法,并非Fa...
    孙小磊阅读 1,982评论 0 3
  • 注:都是在百度搜索整理的答案,如有侵权和错误,希告知更改。 一、哪些情况下的对象会被垃圾回收机制处理掉  当对象对...
    Jenchar阅读 3,224评论 3 2
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,098评论 1 32
  • http://www.importnew.com/20339.html 动态创建代理类 代理模式:代理模式的作用=...
    蜗牛在北京阅读 346评论 0 0