A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 黎志文 于 2013-7-23 11:25 编辑

视频中说道,一般情况下我们自己编写这样一个类 java.lang.System,系统是无法去加载的,因为BootStrap会去加载rt.jar中的这个System类,但是我们自定义一个
类加载器的话,却可以去加载这个自定义的System类,这是否表示,自定义的类加载器不遵循委托机制?如果遵循的话,我自定义的类加载器也是要先逐级委托给
父级类加载器,最后还是会委托给最上端的BootStrap,而这个类加载器在它的范围内还是可以找到System这个类,如此一来,加载的不仍然是jdk自带的System这个类么?
自定义的System类同样无法加载。

评分

参与人数 1技术分 +1 收起 理由
杜光 + 1 每天提问并回答问题,是对知识的复习和积累.

查看全部评分

14 个回复

倒序浏览
我貌似听视频说的是,用特殊的方法让自定义的类加载器逃避委托机制,这样就可以加载自定义的类了。
回复 使用道具 举报
本帖最后由 郭俊 于 2013-7-22 23:04 编辑

类加载器的委托机制:
强调一下:自定义的加载器必须继承ClassLoader。
当Java虚拟机要加载一个类时,到底该派哪个类加载器去加载呢?
类加载器的委托机制:
1.首先是当前线程的类加载器去加载线程中的第一个类。
2.如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。
3.还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChlid方法,即使有,那么有多个儿子,找哪一个呢?
自己写个类叫java.lang.System?一般情况下不能,因为类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。因为“每个类加载器加载类时,又先委托给其上级类加载器”,java.lang.System在BootStrap中最先加载。但是我们可以写一个类加载器来加载我们自己写的java.lang.System类。注:自定义的类加载器通常用于解密自己写的已加密的class字节码,否则即使别人拥有该class文件也无法被系统的类加载器正常加载。

评分

参与人数 1技术分 +1 收起 理由
杜光 + 1 每天提问并回答问题,是对知识的复习和积累.

查看全部评分

回复 使用道具 举报
自定义类加载器是完全遵循委托机制的。这样可以提高软件系统的安全性,因为这种机制下,用户自定义的类加载器不可能加载应该由父加载器加载的可靠类,从而防止不可靠甚至恶意的代码代替由父加载器加载的可靠代码。比如,java.lang.Object类总是由根类加载器加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类。
每个类加载器都从指定的目录中加载类库。根类加载器从系统属性sun.boot.class.path指定的目录;扩展类加载器从java.ext.dirs系统属性所指定的目录或JDK的安装目录的jre\lib\ext子目录下加载;系统类加载器从环境变量classpath或者系统属性java.class.path所指定的目录加载。用户自定义的类加载器也可以有指定的加载目录。我们把自定义的System类放在这个目录下,就可以用自定义的类加载器加载它了。

评分

参与人数 1技术分 +1 收起 理由
杜光 + 1 每天提问并回答问题,是对知识的复习和积累.

查看全部评分

回复 使用道具 举报
郭俊 发表于 2013-7-22 23:03
类加载器的委托机制:
(强调一下:自定义的加载器必须继承ClassLoader。)
当Java虚拟机要加载一个类时, ...

哎,这个在视频里面都有,我要是能够明白的话,还用得着在这提问么? 那我问一下:当前线程的类加载器去加载线程中的第一个类,请问,当前线程的类加载器是哪个加载器?
回复 使用道具 举报
本帖最后由 toShareBeauty 于 2013-7-23 01:23 编辑

自己定义一个l类 extends ClassLoader ,如果不是直接重写 findClass,而是重写 loadClass 那么就不遵守了。loadClass 函数做两个事情,一个是委托上级类加载器,一个是调用 findClass 函数,只有在上级没有加载成功才会调用 findClass。如果你直接重写 loadClass ,不实现委托,直接调用 findClass ,并且和以前一样重写 findClass ,这样自定义的 ClassLoader 就不会有委托行为。
回复 使用道具 举报
本帖最后由 黎志文 于 2013-7-23 02:09 编辑
toShareBeauty 发表于 2013-7-23 01:12
自己定义一个l类 extends ClassLoader ,如果不是直接重写 findClass,而是重写 loadClass 那么就不遵守了 ...


好像不是这样的!请你去试试,自定义一个类,继承ClassLoader,然后只重写findClass方法,不重写loadClass方法,findClass方法里面,写简单些,不去整那些加密和解密,就直接去读取classpath中的一个class文件,然后写入到ByteArrayOutputStream这个字节数组流中,再将此数组流中的数据存储到一个byte数组里面,最后通过defineClass方法将这个byte数组中的数据转成字节码,将defineClass方法的返回值返回给findClass。
在一个主函数里面,用自定义的类加载器去加载classpath中的文件,试试最后加载这个文件的类加载器是自定义的那个,还是系统类加载器,我测试的结果是前者。

  1. <P>package cn.itcast.day3.classloader;

  2. import java.io.ByteArrayOutputStream;
  3. import java.io.FileInputStream;
  4. import java.io.FileNotFoundException;
  5. import java.io.InputStream;

  6. //使用自定义的加载器加载classpath中的类,看看最后执行加载的类加载器是哪个?是自定义的,还是系统论加载器?
  7. public class TestClassLoader extends ClassLoader
  8. {        
  9.         @Override
  10.         public Class findClass(String name) throws ClassNotFoundException
  11.         {
  12.                 try
  13.                 {
  14.                         String classPath = "bin\\cn\\itcast\\day3\\classloader\\" + name+".class"; // 最前面那个路径,是我的eclipse的classpath
  15.                         InputStream ins = new FileInputStream(classPath); //读取classpath中的一个文件
  16.                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
  17.                         int b = 0;
  18.                         while((b=ins.read())!=-1)
  19.                         {
  20.                                 bos.write(b); //将读取到的文件信息写入到字节输出流
  21.                         }
  22.                         ins.close();
  23.                         byte[] bytes = bos.toByteArray(); //将字节输出流里面的数据存储到一个byte数组
  24.                         return defineClass(null,bytes,0,bytes.length); //最后将byte数组中的数据转成字节码对象。
  25.                 } catch (Exception e)
  26.                 {
  27.                         e.printStackTrace();
  28.                 }        
  29.                 return super.findClass(name);
  30.         }
  31. }

  32. class TestClassLoaderDemo
  33. {
  34.         public static void main(String[] args) throws Exception
  35.         {
  36.                 Class clazz = new TestClassLoader().loadClass("SourceFile"); //用自定义的类加载器去加载classpath中的SourceFile.class文件
  37.                 Object obj = clazz.newInstance(); //用加载好的字节码去创建一个实例对象
  38.                 System.out.println(obj.getClass().getClassLoader().getClass().getName()); //测试该实例对象的类是由哪个加载器加载的。结果是:我自定义的类加载器</P>
  39. <P>
  40.                /*我自定义的类加载器去加载classpath中的数据,如果根据委托机制,自定义的类加载器会委托给父级类加载器,而自定义的类加载器的父级,系统类</P>
  41. <P>               加载器可以在classpath中找到这个SourceFile.class文件,那么不应该是由系统类加载器来加载它么?最后的返回结果应该是AppClassLoader才对?*/

  42.         }
  43. }</P>
复制代码
回复 使用道具 举报
本帖最后由 toShareBeauty 于 2013-7-23 03:07 编辑
黎志文 发表于 2013-7-23 02:07
好像不是这样的!请你去试试,自定义一个类,继承ClassLoader,然后只重写findClass方法,不重写loadClas ...

你做的,结果必须是自定义的类加载器,你的上面代码做法就是用自定义的类加载器加载自定义类,在你的例子里面你把 SourceFile.class 改为 System.class ,java.lang.System。
你最前面的问题不是问怎么用自定义的类加载器加载 rt.jar 中的某个类么?我告诉你的做法就是用自定义的类加载器加载 rt.jar 中的类,而不会让 Bootstrap 加载 rt.jar 中的类。比如 Integer Math 之类的类。

  1. import java.io.ByteArrayOutputStream;
  2. import java.io.FileInputStream;
  3. import java.io.InputStream;

  4. /**
  5. * @author vivianZhao
  6. *
  7. */
  8. public class MyClassLoader extends ClassLoader {

  9.         @Override
  10.         protected Class<?> findClass(String name) throws ClassNotFoundException {
  11.                 // TODO Auto-generated method stub
  12.                 try {
  13.                         String classPath = "bin//java//lang//" + name
  14.                                         + ".class"; // 最前面那个路径,是我的eclipse的classpath
  15.                         InputStream ins = new FileInputStream(classPath); // 读取classpath中的一个文件
  16.                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
  17.                         int b = 0;
  18.                         while ((b = ins.read()) != -1) {
  19.                                 bos.write(b); // 将读取到的文件信息写入到字节输出流
  20.                         }
  21.                         ins.close();
  22.                         byte[] bytes = bos.toByteArray(); // 将字节输出流里面的数据存储到一个byte数组
  23.                         return defineClass(null, bytes, 0, bytes.length); // 最后将byte数组中的数据转成字节码对象。
  24.                 } catch (Exception e) {
  25.                         e.printStackTrace();
  26.                 }
  27.                 return super.findClass(name);
  28.         }

  29. }
复制代码
你自己再定义一个 java.lang 包,定义一个 Math 类,在测试的那个函数里面,传递 Math 这个类名进去,会发现,Java 的安全机制不自己命名 java.lang 包。
所以归根到底,还是不能加载自定义的 java.lang 下面的类。

评分

参与人数 1技术分 +1 收起 理由
杜光 + 1 每天提问并回答问题,是对知识的复习和积累.

查看全部评分

回复 使用道具 举报
郭俊 中级黑马 2013-7-23 09:16:35
9#
   额,刚到公司,一瞅,楼上哥们阐述的很详细了
回复 使用道具 举报
toShareBeauty 发表于 2013-7-23 02:14
你做的,结果必须是自定义的类加载器,你的上面代码做法就是用自定义的类加载器加载自定义类,在你的例子 ...

我试过了,确实如此,系统会出现这个提示。那这样说来,视频里面,张孝祥老师说的:一般情况下不可以自定义java.lang.System这样一个类,但是通过自定义的类加载器可以实现加载自定义的System,这句话是错的咯?
回复 使用道具 举报
黎志文 发表于 2013-7-23 10:08
我试过了,确实如此,系统会出现这个提示。那这样说来,视频里面,张孝祥老师说的:一般情况下不可以自定 ...

反正按他说的不行。这就好像 dll 替换,如果可以那就是个漏洞。
回复 使用道具 举报
toShareBeauty 发表于 2013-7-23 10:17
反正按他说的不行。这就好像 dll 替换,如果可以那就是个漏洞。

那我不重写finadClass方法,而是重写loadClass方法,这样应该可行吧?
回复 使用道具 举报
黎志文 发表于 2013-7-23 10:23
那我不重写finadClass方法,而是重写loadClass方法,这样应该可行吧?

这个问题不像视频说的那么简单,我试过,如果重写 loadClass 不管其他的直接调用 findClass 会有找不到 Object 类的问题,因为这个委托连断了,上级加载的类这个类加载器都不能用了,不可能自己全部重新用自定义的类加载一遍吧。反正很麻烦。
回复 使用道具 举报
toShareBeauty 发表于 2013-7-23 11:54
这个问题不像视频说的那么简单,我试过,如果重写 loadClass 不管其他的直接调用 findClass 会有找不到 Ob ...

谢谢兄弟的耐心答复!类加载器折磨了我几天几夜,今天终于有点思路了,你的答复对我启发较大,谢谢!
回复 使用道具 举报
黎志文 发表于 2013-7-23 12:45
谢谢兄弟的耐心答复!类加载器折磨了我几天几夜,今天终于有点思路了,你的答复对我启发较大,谢谢! ...

:handshake不客气不客气,一起学习进步。
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马