`
hideto
  • 浏览: 2649893 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

深入了解Java ClassLoader、Bytecode 、ASM、cglib

    博客分类:
  • Java
阅读更多
一、Java ClassLoader

1,什么是ClassLoader
与 C 或 C++ 编写的程序不同,Java 程序并不是一个可执行文件,而是由许多独立的类文件组成,每一个文件对应于一个 Java 类。
此外,这些类文件并非立即全部都装入内存,而是根据程序需要装入内存。ClassLoader 是 JVM 中将类装入内存的那部分。
而且,Java ClassLoader 就是用 Java 语言编写的。这意味着创建您自己的 ClassLoader 非常容易,不必了解 JVM 的微小细节。

2,一些重要的方法
A)loadClass
ClassLoader.loadClass() 是ClassLoader的入口点。该方法的定义为:Class loadClass( String name, boolean resolve );
name:JVM 需要的类的名称,如 Foo 或 java.lang.Object。
resolve:参数告诉方法是否需要解析类。

B)defineClass
defineClass方法是ClassLoader的主要诀窍。该方法接受由原始字节组成的数组并把它转换成Class对象。

C)findSystemClass
findSystemClass方法从本地文件系统中寻找类文件,如果存在,就使用defineClass将原始字节转换成Class对象,以将该文件转换成类。

D)resolveClass
可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的loadClass时可以调用resolveClass,这取决于loadClass的resolve参数的值。

E)findLoadedClass
findLoadedClass充当一个缓存:当请求loadClass装入类时,它调用该方法来查看ClassLoader是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。

3,Java2中ClassLoader的变动
1)loadClass的缺省实现
在Java2中loadClass的实现嵌入了大多数查找类的一般方法,并使您通过覆盖findClass方法来定制它,在适当的时候findClass会调用loadClass。
这种方式的好处是可能不一定要覆盖loadClass,只要覆盖findClass就行了,这减少了工作量。

2)新方法:findClass
loadClass的缺省实现调用这个新方法。

3)新方法:getSystemClassLoader
如果覆盖findClass或loadClass,getSystemClassLoader让我们以实际ClassLoader对象来访问系统ClassLoader,而不是固定的从findSystemClass 调用它。

4)新方法:getParent
为了将类请求委托给父ClassLoader,这个新方法允许ClassLoader获取它的父ClassLoader。

4,定制ClassLoader
其实我们或多或少都使用过定制的ClassLoader,因为Applet查看器中就包含一个定制的ClassLoader。
它不在本地文件系统中寻找类,而是访问远程服务器上的 Web 站点,经过 HTTP 装入原始的字节码文件,并把它们转换成JVM 内的类。
Applet查看器中的ClassLoader还可以做其它事情:它们支持安全性以及使不同的Applet在不同的页面上运行而互不干扰。
我们将写一个自己的ClassLoader实现示例,它将实现如下步骤,这也是ClassLoader的工作原理:
# 调用 findLoadedClass 来查看是否存在已装入的类。
# 如果没有,那么采用那种特殊的神奇方式来获取原始字节。
# 如果已有原始字节,调用defineClass将它们转换成Class对象。
# 如果没有原始字节,然后调用findSystemClass查看是否从本地文件系统获取类。
# 如果resolve参数是true,那么调用resolveClass解析Class对象。
# 如果还没有类,返回ClassNotFoundException。
# 否则,将类返回给调用程序。
话不多说,看看代码先:
FileClassLoader.java:
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class FileClassLoader extends ClassLoader {
  public Class findClass(String name) {
    byte[] data = loadClassData(name);
    return defineClass(name, data, 0, data.length);
  }
  
  private byte[] loadClassData(String name) {
    FileInputStream fis = null;
    byte[] data = null;
    try {
      fis = new FileInputStream(new File("D:\\project\\test\\" + name + ".class"));
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      int ch = 0;
      while ((ch = fis.read()) != -1) {
        baos.write(ch);
      }
      data = baos.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return data;
  }
}

MyApp.java:
public class MyApp {
  public static void main(String[] args) throws Exception {
    FileClassLoader loader = new FileClassLoader();
    Class objClass = loader.findClass("MyApp");
    Object obj = objClass.newInstance();
    System.out.println(objClass.getName());
    System.out.println(objClass.getClassLoader());
    System.out.println(obj);
  }
}

编译并运行MyApp类,结果为:
MyApp
FileClassLoader@757aef
MyApp@9cab16
分享到:
评论
21 楼 ytzhsh 2008-07-04  
当我对字节码编译不知所措的时候,发现了这个帖子
谢谢楼主,目前正在学习ASM CGLIB
20 楼 javaeyename 2008-06-14  
@hideto  以后有长篇的话,能不能放在一个帖子里面,不要搞到评论里面,看着难受,估计你写的也难受吧!呵呵!你这几个东西到ibm的开发论坛上有比较详细的介绍!你介绍的不全!
19 楼 天门冬 2008-04-01  
支持,哈哈
18 楼 luopan68 2008-03-31  
不是解析resolve的事情
loader.findClass("MyApp")是该loader载入的MyApp
该loader载入的MyApp在你的代码中强转是强转成system class loader加载的那个MyApp,classloader不同
loader.loadClass ("MyApp")使用双亲委托模式,先由他的parent找,
你的MyApp是由该loader的parent找到了返回的。loader相同,你强转是可以的
17 楼 yujiang 2007-12-17  
理解ClassLoader还是有点难度的,
一个比较不错的练习就是去看看OSGI(core)
规范中[Module Layer]这一部分.
16 楼 leadyu 2007-12-16  
引用
代码
FileClassLoader loader = new FileClassLoader();  
Class objClass = loader.findClass("MyApp");  
Object obj = objClass.newInstance();  


请问用Object obj = objClass.newInstance();得到的对象是不是只包括该MyApp的基本信息,它是不是不能向下转型到MyApp呢?
我在列子中直接用loader.findClass("MyApp")得到的Class对象在用newInstance()方法得到一个实例,把这个实例向下转型到MyApp时报类型转化异常.
而我在列子中直接用loader.loadClass ("MyApp")得到的Class对象在用newInstance()方法得到一个实例,把这个实例向下转型到MyApp时可以正确转化.
我的理解是用loader.findClass("MyApp")得到的Class对象并没有真正加载,但可以用它得到类的一些信息,但是用loader.loadClass ("MyApp")得到的Class对象是真正加载到JVM中的.
不知道我的理解对不对?



楼主的回答是不准确的,这和resolve八杆子打不上边。class有没加载,看的是defindClass()。

再看loadClass为什么能够正确,其实也不是必然的,仅仅表明MyApp并不是由FileClassLoader加载的,可见它的内部实现把加载的工作委托给了父ClassLoader。

好,就算FileClassLoader自己加载了MyApp,转型的时候也可能ClassCastException,因为每一个ClassLoader在虚拟机内部都有自己的命名空间,除非调用者的类也是FileClassLoader加载的,否则转型的时候会使用调用者的ClassLoader加载MyApp,此MyApp非FileClassLoader加载的MyApp,转型也会报错。


其实,这里有很多种情况,由于时间原因我的解释可能比较乱,没有说很明白,我觉得能够把一些关键的基本问题:defindClass,父子委托,命名空间 & method space,Class.forName和Thread.currentThread.getContextClassLoader(),resolveClass()解释明白,应该能够解释大多数人在部署,调试中遇到的大部分问题。
15 楼 lkl_1981 2007-12-14  
谢谢搂主,太博学了
14 楼 javalu 2007-07-11  
学习中。。。。
13 楼 dennis_zane 2007-07-08  
这帖子不应该评精华,只是简单的资料罗列,而且有错的地方,比如
A)loadClass
ClassLoader.loadClass() 是ClassLoader的入口点。该方法的定义为:Class loadClass( String name, boolean resolve );
name:JVM 需要的类的名称,如 Foo 或 java.lang.Object。
resolve:参数告诉方法是否需要解析类。

这个说法完全是错的,resolve是告诉是否要链接类,而不是解析类。就算resolve是false,返回的也可能是链接过的类,因为在第一次链接后,以后都是直接返回已经链接的类。同样,resolveClass方法是链接类,而不是解析类,解析是可选的,根据JDK实现不同而不同,这一点看JDK文档都应该知道,这么明显的错误怎么评精华呢?关于ClassLoader网上充斥了太多似是而非的资料,还是自己看vmspec或者《深入java虚拟机》为好。

12 楼 hover1215 2007-07-07  
# 调用 findLoadedClass 来查看是否存在已装入的类。
# 如果没有,那么采用那种特殊的神奇方式来获取原始字节。

请问:神奇的方式获取原始字节,到底是怎么回事?能解释一下吗?
11 楼 hideto 2007-07-07  
我觉得应该与Class loadClass( String name, boolean resolve )方法中的resolve属性有关:
引用

D)resolveClass
可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的loadClass时可以调用resolveClass,这取决于loadClass的resolve参数的值。


但不知道是参考资料比较老还是什么原因,我现在在JDK6.0上手动调用resolveClass后仍然报ClassCastException,api文档也不够详细,暂时没找到原因,大家一起探讨吧。
10 楼 tangyi198388 2007-07-07  
FileClassLoader loader = new FileClassLoader();
Class objClass = loader.findClass("MyApp");
Object obj = objClass.newInstance();

请问用Object obj = objClass.newInstance();得到的对象是不是只包括该MyApp的基本信息,它是不是不能向下转型到MyApp呢?
我在列子中直接用loader.findClass("MyApp")得到的Class对象在用newInstance()方法得到一个实例,把这个实例向下转型到MyApp时报类型转化异常.
而我在列子中直接用loader.loadClass ("MyApp")得到的Class对象在用newInstance()方法得到一个实例,把这个实例向下转型到MyApp时可以正确转化.
我的理解是用loader.findClass("MyApp")得到的Class对象并没有真正加载,但可以用它得到类的一些信息,但是用loader.loadClass ("MyApp")得到的Class对象是真正加载到JVM中的.
不知道我的理解对不对?
9 楼 voff12 2007-07-06  
very good!谢谢分享!
8 楼 zjumty 2007-07-06  
不错,Mark
7 楼 笨鸟先飞 2007-07-06  
不错!可谓精华帖啊!
6 楼 younggun 2007-07-06  
真不错,hideto可以深入再继续
5 楼 那年夏天 2007-07-06  
收藏了,最近正打算研究这些东西的呢
4 楼 youlq 2007-07-06  
写得真好,言简意赅,参考资料也很有帮助。
3 楼 hideto 2007-07-05  
四、cglib
cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.
cglib是Code Generation Library的缩写。
cglib依赖于ASM库。
Hibernate主要是利用cglib生成pojo的子类并override get方法来实现lazy loading机制,Spring则是利用cglib来实现动态代理。
而JDK的动态代理机制要求有接口才行,这样就强制我们的pojo实现某个接口。

这里还是提供一个cglib的入门级的示例:
MyClass.java:
public class MyClass {

  public void print() {
    System.out.println("I'm in MyClass.print!");
  }

}

Main.java:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class Main {

  public static void main(String[] args) {

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(MyClass.class);
    enhancer.setCallback(new MethodInterceptorImpl());
    MyClass my = (MyClass) enhancer.create();
    my.print();
  }

  private static class MethodInterceptorImpl implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args,
        MethodProxy proxy) throws Throwable {
      // log something
      System.out.println(method + " intercepted!");

      proxy.invokeSuper(obj, args);
      return null;
    }
  }
}

打印结果为:
public void MyClass.print() intercepted!
I'm in MyClass.print!

这个示例就基本上实现了日志AOP的功能,很简单吧。

参考资料
CLR和JRE的运行机制的初步总结
Java虚拟机
了解Java ClassLoader
Java Virtual Machine Specification
Java bytecode
解读字节码文件
Java Bytecode Specification and Verification
ASM User Guide
Hello, ASM
cglig指南
Java下的框架编程--cglib的应用
AOP = Proxy Pattern + Method Reflection + Aspect DSL + 自动代码生成
深入浅出Spring AOP
2 楼 hideto 2007-07-05  
三、ASM
我们知道Java是静态语言,而python、ruby是动态语言,Java程序一旦写好很难在运行时更改类的行为,而python、ruby可以。
不过基于bytecode层面上我们可以做一些手脚,来使Java程序多一些灵活性和Magic,ASM就是这样一个应用广泛的开源库。

ASM is a Java bytecode manipulation framework. It can be used to dynamically generate stub classes or other proxy classes,
directly in binary form, or to dynamically modify classes at load time, i.e., just before they are loaded into the Java
Virtual Machine.

ASM完成了BCELSERP同样的功能,但ASM
只有30多k,而后两者分别是350k和150k。apache真是越来越过气了。

让我们来看一个ASM的简单例子Helloworld.java,它生成一个Example类和一个main方法,main方法打印"Hello world!"语句:
import java.io.FileOutputStream;
import java.io.PrintStream;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

public class Helloworld extends ClassLoader implements Opcodes {

  public static void main(final String args[]) throws Exception {

    // creates a ClassWriter for the Example public class,
    // which inherits from Object

    ClassWriter cw = new ClassWriter(0);
    cw.visit(V1_1, ACC_PUBLIC, "Example", null, "java/lang/Object", null);
    MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null,
        null);
    mw.visitVarInsn(ALOAD, 0);
    mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
    mw.visitInsn(RETURN);
    mw.visitMaxs(1, 1);
    mw.visitEnd();
    mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main",
        "([Ljava/lang/String;)V", null, null);
    mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
    mw.visitLdcInsn("Hello world!");
    mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
        "(Ljava/lang/String;)V");
    mw.visitInsn(RETURN);
    mw.visitMaxs(2, 2);
    mw.visitEnd();
    byte[] code = cw.toByteArray();
    FileOutputStream fos = new FileOutputStream("Example.class");
    fos.write(code);
    fos.close();
    Helloworld loader = new Helloworld();
    Class exampleClass = loader
        .defineClass("Example", code, 0, code.length);
    exampleClass.getMethods()[0].invoke(null, new Object[] { null });

    // ------------------------------------------------------------------------
    // Same example with a GeneratorAdapter (more convenient but slower)
    // ------------------------------------------------------------------------

    cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    cw.visit(V1_1, ACC_PUBLIC, "Example", null, "java/lang/Object", null);
    Method m = Method.getMethod("void <init> ()");
    GeneratorAdapter mg = new GeneratorAdapter(ACC_PUBLIC, m, null, null,
        cw);
    mg.loadThis();
    mg.invokeConstructor(Type.getType(Object.class), m);
    mg.returnValue();
    mg.endMethod();
    m = Method.getMethod("void main (String[])");
    mg = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC, m, null, null, cw);
    mg.getStatic(Type.getType(System.class), "out", Type
        .getType(PrintStream.class));
    mg.push("Hello world!");
    mg.invokeVirtual(Type.getType(PrintStream.class), Method
        .getMethod("void println (String)"));
    mg.returnValue();
    mg.endMethod();
    cw.visitEnd();
    code = cw.toByteArray();
    loader = new Helloworld();
    exampleClass = loader.defineClass("Example", code, 0, code.length);
    exampleClass.getMethods()[0].invoke(null, new Object[] { null });
  }
}

我们看到上面的例子分别使用ASM的MethodVisitor和GeneratorAdapter两种方式来动态生成Example类并调用打印语句。

相关推荐

Global site tag (gtag.js) - Google Analytics