自定义的类装载器-从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);}
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(driver).newInstance()
static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");}}
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;
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); } }}
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; }}
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(); } }}
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; }
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"); }......
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.out.println(System.getProperty("java.ext.dirs"));
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.out.println(System.getProperty("java.class.path"));
public static synchronized URLClassPath getBootstrapClassPath() { if (bootstrapClassPath == null) { String str = (String)AccessController.doPrivileged(new GetPropertyAction("sun.boot.class.path"));......
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"));