读书人

自定义的类装载器-从DB装载class(附下

发布时间: 2012-09-10 11:02:32 作者: rapoo

自定义的类装载器-从DB装载class(附上对类装载器的分析)
代码才是最实在的,先从代码开始,然后再一步一步分析:
第一步:写个类用来装载

public class MyClassLoaderTest implements MyClassLoaderTestInterface {    public MyClassLoaderTest() {        System.out.println("MyClassLoaderTest构造函数被调用了...");    }        public void sayHello(String name) {        System.out.println("Hello " + name + " 我是sayHello函数,我被调用了...");    }        public class InnerClassTest {        public InnerClassTest() {}        public void print() {            System.out.println("内部类");        }    }}

package com.jvm.one.loadclassfromdb;public interface MyClassLoaderTestInterface {    void sayHello(String name);}


第二步:把编译后的class文件插入DB(MySql)
1.单独写个连接DB的类
package com.jvm.one.loadclassfromdb;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;public class ConnectDB {        /**     * 驱动程序名     */    private static final String driver = "com.mysql.jdbc.Driver";        /**     * URL指向要访问的数据库名     */    private static final String url = "jdbc:mysql://localhost:3306/classloader_test?useUnicode=true&characterEncoding=UTF-8";        /**     * 用户名     */    private static final String user = "root";        /**     * 密码     */    private static final String password = "141421";    public static Connection getConnnection() {                Connection conn = null;        try {            Class.forName(driver);            //连接数据库            conn = DriverManager.getConnection(url, user, password);            if (!conn.isClosed())                System.out.println("数据库连接成功...");                    } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SQLException e) {            try {                if (null != conn) conn.close();            } catch (SQLException e1) {                e1.printStackTrace();            }            e.printStackTrace();        }        return conn;    }        public static void closeConnection(Connection conn) {        try {            if (null != conn) {                conn.close();                System.out.println("数据库连接关闭...");            } else {                System.out.println("数据库连接不存在...");            }        } catch (SQLException e1) {            e1.printStackTrace();        }    }}

这里插一句,由于Class.forName函数也跟ClassLoader有关,这里有点问题也解释下
平常我们都习惯使用Class.forName(driver)这种方式装载JDBC驱动,但是Class.forName只是返回Class对象,并没有返回driver的实例,也就是说driver并没有被实例化,为什么不需要实例化呢,很多人有这样的困惑,所以他们这么写代码:
Class.forName(driver).newInstance()

其实这样写是没有必要的,代码是最真实的,我们看看MySql的jdbc驱动源码片段:
代码来自com.mysql.jdbc.Driver
static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");}}

下面代码来自java.lang.Class
public static Class<?> forName(String className)                 throws ClassNotFoundException {        return forName0(className, true, ClassLoader.getCallerClassLoader());    }

    private static native Class forName0(String name, boolean initialize,    ClassLoader loader)throws ClassNotFoundException;

boolean initialize参数为true的时候,在装载类的同时会初始化(注意不是调用构造函数),类被初始化时类中的静态语句块会被执行,所以Class.forName(driver);执行的时候已经有一个驱动的实例被注册到DriverManager中了,好了,这个问题告一段落,接着贴代码

2.建表
CREATE TABLE `classfile` (
`classname` varchar(200) NOT NULL,
`classcode` blob NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gbk;

3.将class文件的内容插入DB
package com.jvm.one.loadclassfromdb;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.SQLException;public class InsertClassToDB {    /**     * 将class文件内容存入DB     * @param args     * @return void     */    public static void main(String[] args) {        FileInputStream fis = null;        PreparedStatement ps = null;        Connection conn = ConnectDB.getConnnection();;        try {            //编译好的class文件            fis = new FileInputStream(                    "D:\\Workspace\\MyEclipse\\workspace\\jvm-study\\bin\\com\\jvm\\one\\loadclassfromdb\\MyClassLoaderTest.class");            //类的全限定名称            String className = "com.jvm.one.loadclassfromdb.MyClassLoaderTest";            String sql = "insert into classfile(classname, classcode) values (?, ?)";            ps = conn.prepareStatement(sql);            ps.setString(1, className);            ps.setBinaryStream(2, fis, fis.available());            ps.executeUpdate();            //处理内部类            fis = new FileInputStream(            "D:\\Workspace\\MyEclipse\\workspace\\jvm-study\\bin\\com\\jvm\\one\\loadclassfromdb\\MyClassLoaderTest$InnerClassTest.class");            //类的全限定名称            className = "com.jvm.one.loadclassfromdb.MyClassLoaderTest$InnerClassTest";            sql = "insert into classfile(classname, classcode) values (?, ?)";            ps = conn.prepareStatement(sql);            ps.setString(1, className);            ps.setBinaryStream(2, fis, fis.available());            ps.executeUpdate();                System.out.println("插入二进制文件到DB成功");            ps.clearParameters();            ps.close();                        fis.close();        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            try {                if (null != fis) fis.close();            } catch (IOException e1) {                e1.printStackTrace();            }            e.printStackTrace();        } catch (SQLException e) {            try {                if (null != ps) ps.close();            } catch (SQLException e1) {                e1.printStackTrace();            }            e.printStackTrace();        } finally {            ConnectDB.closeConnection(conn);        }    }}

上面这段简单的代码不多说了,不是今天的重点

第三步:装载
1.负责从DB载入class文件的类
package com.jvm.one.loadclassfromdb;import java.sql.Blob;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;public class GetClassFromDB {    public static Blob getBinary(String className) {        Blob blob = null;        Connection conn = ConnectDB.getConnnection();        PreparedStatement ps = null;        try {            ps = conn.prepareStatement("select classcode from classfile where classname=?");            ps.setString(1, className);                        ResultSet rs = ps.executeQuery();            if (rs.next())                blob = rs.getBlob("classcode");            else                System.out.println("数据不存在...");                    } catch (SQLException e) {            try {                if (null != ps)  ps.close();            } catch (SQLException e1) {                e1.printStackTrace();            }            e.printStackTrace();        }        ConnectDB.closeConnection(conn);        return blob;    }}

上面这段代码也不需要解释,不是重点

2.自定义的类装载器
package com.jvm.one.loadclassfromdb;import java.lang.reflect.Method;import java.sql.Blob;import java.sql.SQLException;/** * 从DB载入class文件 * @author fengjc * @version 1.00 Nov 29, 2011 */public class LoadClassFromDB extends ClassLoader {        @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        System.out.println("LoadClassFromDB.findClass is called");        Blob blob = GetClassFromDB.getBinary(name);        try {            byte[] bytes;            if (null != blob) {                bytes = blob.getBytes(1, (int) blob.length());                //通过调用defineClass(String name, byte[] b, int off, int len)方法来定义一个类:                return defineClass(name, bytes, 0, bytes.length);            }            else {                return null;            }        } catch (SQLException e) {            e.printStackTrace();        }        return null;    }        public static void main(String[] args) {        try {            LoadClassFromDB loadClassFromDB = new LoadClassFromDB();                        /**             输出:                com.jvm.one.loadclassfromdb.LoadClassFromDB@c17164                sun.misc.Launcher$AppClassLoader@19821f                sun.misc.Launcher$ExtClassLoader@addbf1                null                                第一个是当前的自定义类加载器LoadClassFromDB                第二个是AppClassLoader,它是LoadClassFromDB的双亲,主要装载ClassPath下的字节码                第三个是ExtClassLoader,它是AppClassLoader的双亲,主要装载Jdk安装路径/jre/lib/ext下的字节码                第四个为null,代表bootstrap启动类装载器(由C++语言编写,固化在jvm上),它是ExtClassLoader的双亲,主要装载rt.jar             */            ClassLoader classLoader = loadClassFromDB;            System.out.println(classLoader);            do {                classLoader = classLoader.getParent();                //如果打印出null代表为启动类装载器                System.out.println(classLoader);            } while (null != classLoader);                        //没有经过loadClass()方法中的双亲委派模型来装载类,跳出了委派链            Class<?> outerC = loadClassFromDB.findClass("com.jvm.one.loadclassfromdb.MyClassLoaderTest");                        /*下面两种方法更好,这两种方法没有跳出双亲委派模式             *但是比如现在我的bin目录下存在com.jvm.one.loadclassfromdb.MyClassLoaderTest的class文件,             *那么AppClassLoader会装载过它,所以LoadClassFromDB再去装载的时候会委派给它的双亲AppClassLoader             *AppClassLoader发现这个类自己已经装载了,就会直接返回给LoadClassFromDB,这样LoadClassFromDB就没有机会从DB中装载了             *如果想从DB中装载class文件,删除bin目录下的对应class文件即可             *///            Class<?> outerC = loadClassFromDB.loadClass("com.jvm.one.loadclassfromdb.MyClassLoaderTest");//            Class<?> outerC = Class.forName("com.jvm.one.loadclassfromdb.MyClassLoaderTest", false, loadClassFromDB);            System.out.println("装载MyClassLoaderTest类的装载器为: " + outerC.getClassLoader());                        Object outer = null;            Object inner = null;            if (null != outerC) {                /*注意这里不能用类型转换                 *java怎么比较两个类型是否相同呢?首先第一点就是看这两个类是不是同一个类装载器装载的                 *所以下面代码是一定会抛出java.lang.ClassCastException的                 *MyClassLoaderTest o = (MyClassLoaderTest) c.newInstance();                 */                outer = outerC.newInstance();                                Method outerM = outerC.getMethod("sayHello", new Class<?>[] {String.class});                outerM.invoke(outer, "童鞋们");                                //可以定义一个接口在客户端,这样就可以不用反射了                MyClassLoaderTestInterface oInterface = (MyClassLoaderTestInterface) outerC.newInstance();                oInterface.sayHello("接口");                                Class<?> innerC = loadClassFromDB.findClass("com.jvm.one.loadclassfromdb.MyClassLoaderTest$InnerClassTest");                if (null != innerC) {                    /*                        这里为什么取得内部类的构造函数时有一个参数(就是它的外部类)呢?                    javap -verbose MyClassLoaderTest$InnerClassTest看看javac生成的字节码指令                        我们会看到如下内容片段:                    public com.jvm.one.loadclassfromdb.MyClassLoaderTest$InnerClassTest(com.jvm.one.loadclassfromdb.MyClassLoaderTest);                        这就是javac为内部类生成的构造函数,带上了一个参数是它的外部类MyClassLoaderTest(java代码中内部类的构造函数是无参的)                     */                    inner = innerC.getConstructor(outer.getClass()).newInstance(outer);                                        Method innerM = innerC.getMethod("print");                    innerM.invoke(inner);                }            }        } catch (Exception e) {            e.printStackTrace();        }    }}


输出:
com.jvm.one.loadclassfromdb.LoadClassFromDB@c17164
sun.misc.Launcher$AppClassLoader@19821f
sun.misc.Launcher$ExtClassLoader@addbf1
null
LoadClassFromDB.findClass is called
数据库连接成功...
数据库连接关闭...
装载MyClassLoaderTest类的装载器为: com.jvm.one.loadclassfromdb.LoadClassFromDB@c17164
MyClassLoaderTest构造函数被调用了...
Hello 童鞋们 我是sayHello函数,我被调用了...
MyClassLoaderTest构造函数被调用了...
Hello 接口 我是sayHello函数,我被调用了...
LoadClassFromDB.findClass is called
数据库连接成功...
数据库连接关闭...
内部类


最后程序的输出我们可以看到 类装载器双亲委派模式的委派链为:
null(启动类装载器)->ExtClassLoader->AppClassLoader->自定义类装载器
(链子中前面的是后面的双亲(注意这不是继承))

下面介绍下类装载器的一些概念:
类装载器负责装载类。在JVM中是通过类装载器调用loadClass方法来装载类对象。
双亲委派模型就在ClassLoader.loadClass方法中体现,下面会详细解释

类装载器层次体系:




类装载器结构:
1. 引导类装载器(bootstrap class loader):它用来装载 Java 的核心库,是用本地代码来实现的,java中看不到它[null]
2. 扩展类装载器(extensions class loader):它用来装载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类装载器在此目录里面查找并装载 Java 类[ExtClassLoader]
3. 系统类装载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来装载 Java 类。一般来说,Java 应用的类都是由它来完成装载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它[AppClassLoader]

类装载器结构图:




双亲委派模型(装载class的过程):
1.检查此Class是否载入过(即在cache中是否有此Class),如果有到8
2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
3.请求parent classloader载入,如果成功到8,不成功到5
4.请求jvm从bootstrap classloader中载入,如果成功到8
5.寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
6.从文件中载入Class,到8.
7.抛出ClassNotFoundException.
8.返回Class.

实现这个双亲委派模型的代码:
代码来自java.lang.ClassLoader,AppClassLoader、ExtClassLoader均继承自URLClassLoader,URLClassLoader继承自SecureClassLoader,SecureClassLoader继承自ClassLoader,所以AppClassLoader、ExtClassLoader都是ClassLoader的子类,都继承了loadClass方法
代码不必太详细解释了吧,就是实现了上面描述的8步操作
    protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException    {// First, check if the class has already been loaded    //检查此Class是否载入过Class c = findLoadedClass(name);if (c == null) {    try {if (parent != null) {            //请求parent classloader载入    c = parent.loadClass(name, false);} else {            //如果parent classloader不存在请求jvm从bootstrap classloader中载入    c = findBootstrapClassOrNull(name);}    } catch (ClassNotFoundException e) {                // ClassNotFoundException thrown if class not found                // from the non-null parent class loader            }            if (c == null) {        // If still not found, then invoke findClass in order        // to find the class.            //一直向上委托,如果自己的双亲(层层向上)找不到该类,自己寻找Class文件(从与此classloader相关的类路径中寻找)        c = findClass(name);    }}if (resolve) {    resolveClass(c);}return c;    }


下面我们来反编译下rt.jar看看sun.misc.Launcher中的部分代码,ExtClassLoader和AppClassLoader均是Launcher的内部类
  public Launcher()  {    ExtClassLoader localExtClassLoader;    try    {      localExtClassLoader = ExtClassLoader.getExtClassLoader();    } catch (IOException localIOException1) {      throw new InternalError("Could not create extension class loader");    }    try    {      this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);    } catch (IOException localIOException2) {      throw new InternalError("Could not create application class loader");    }......

启动类装载器启动后的一个重要任务就是装载sun.misc.Launcher来找到ExtClassLoader和AppClassLoader两个类装载器

再来看看sun.misc.Launcher内部类ExtClassLoader中的部分代码:
    public static ExtClassLoader getExtClassLoader()      throws IOException    {      File[] arrayOfFile = getExtDirs();      try      {        return (ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction(arrayOfFile)        {          public Object run() throws IOException {            int i = this.val$dirs.length;            for (int j = 0; j < i; j++) {              MetaIndex.registerDirectory(this.val$dirs[j]);            }            return new Launcher.ExtClassLoader(this.val$dirs);          } } );      } catch (PrivilegedActionException localPrivilegedActionException) {      }      throw ((IOException)localPrivilegedActionException.getException());    }......    private static File[] getExtDirs() {      String str = System.getProperty("java.ext.dirs");      File[] arrayOfFile;......

我们注意到System.getProperty("java.ext.dirs"),这就是ExtClassLoader要装载class的所有路径
System.out.println(System.getProperty("java.ext.dirs"));

我电脑上的输出:
C:\jdk1.6.0_21\jre\lib\ext;C:\Windows\Sun\Java\lib\ext

继续看看sun.misc.Launcher内部类AppClassLoader中的部分代码:
  static class AppClassLoader extends URLClassLoader  {    public static ClassLoader getAppClassLoader(ClassLoader paramClassLoader)      throws IOException    {      String str = System.getProperty("java.class.path");      File[] arrayOfFile = str == null ? new File[0] : Launcher.access$200(str);      return (AppClassLoader)AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader)      {        public Object run() {          URL[] arrayOfURL = this.val$s == null ? new URL[0] : Launcher.access$300(this.val$path);          return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);        }      });    }

同理System.getProperty("java.class.path")的值也是AppClassLoader要装载class的所有路径
System.out.println(System.getProperty("java.class.path"));

我电脑上的输出:
D:\Workspace\MyEclipse\workspace\jvm-study\bin;E:\软件备份\开发工具\Java工具\JDBC-Driver\mysql-connector-java-3.1.13-bin.jar

注意到这个函数了吗?
public static synchronized URLClassPath getBootstrapClassPath()  {    if (bootstrapClassPath == null) {      String str = (String)AccessController.doPrivileged(new GetPropertyAction("sun.boot.class.path"));......

这个函数能取得Bootstrap ClassLoader要装载的类路径
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();        for (URL url : urls)            System.out.println(url.toExternalForm());                //这么做也可以,参照getBootstrapClassPath函数代码就知道        System.out.println(System.getProperty("sun.boot.class.path"));

我电脑上的输出:
file:/C:/jdk1.6.0_21/jre/lib/resources.jar
file:/C:/jdk1.6.0_21/jre/lib/rt.jar
file:/C:/jdk1.6.0_21/jre/lib/jsse.jar
file:/C:/jdk1.6.0_21/jre/lib/jce.jar
file:/C:/jdk1.6.0_21/jre/lib/charsets.jar
C:\jdk1.6.0_21\jre\lib\resources.jar;C:\jdk1.6.0_21\jre\lib\rt.jar;C:\jdk1.6.0_21\jre\lib\jsse.jar;C:\jdk1.6.0_21\jre\lib\jce.jar;C:\jdk1.6.0_21\jre\lib\charsets.jar

读书人网 >其他数据库

热点推荐