自定义类加载器

来源:互联网 发布:程序员的统计学 编辑:程序博客网 时间:2024/05/21 20:10

 创建并使用自定义的类加载器

JVM中除了根加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器

ClassLoader类有如下三个关键方法:

loaderClass(String name,Boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象

findClass(String name):根据二进制名称来查找类
如果需要实现自定义的ClassLoader,可以通过重写以上两个方法来实现,推荐重写findClass()方法,而不是重写loadClass()方法,因为loadClass()方法的执行步骤如下:

1.       用findLoadedClass(String )来检查是否已经加载类,如果已经加载则直接返回。

2.       在父类加载器上调用loadClass方法,如果父类加载器为null,则使用根类加载器来加载

3.       调用findClass(String)方法查找类

从上面方法可以看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托,缓冲机制两种策略,如果重写loadClass()方法,则实现逻辑更为复杂

ClassLoader类还有一个核心方法:Class defineClass(String name,byte[] b,int off,int len)该方法负责将指定类的字节码文件(即Class文件)读入字节数组:byte[]b,并把它转化为Class对象,该字节码文件可以来源于文件,网络等。

 findSystemClass(String name)从本地文件系统中装入文件,在本地文件中寻找类文件,如果存在,就用defineClass将原始字节转化成Class对象,以将该文件转化成类

static getSystemClassloader():这是一个静态方法,用于返回系统类加载器

getParent():获取该类加载器的父类加载器

resolveClass(Class<?> c):链接指定的类,类加载器可以使用此方法来链接类c.

findLoadedClass(String name):如果此Java虚拟机已经装在了名为name的类,则直接返回该类对应的Class实例,否则,返回null,该方法是java类加载里缓存机制的体现

 

 

自定义类加载器:

Public class CompileClassLoader extends ClassLoader

{

   //读取一个文件的内容

   Private byte[] getBytes(String filename) throws IOException

{

  File file = new File(filename);

  Long len = file.length();

  Byte[] raw = new byte[(int)len];

 FileInputStream fin = new FileInuptStream(file);

 //一次读取class文件的全部二进制数据

Int r = fin.read(raw);

If(r!=len)

Throw new IOException(“无法读取全部文件”+r+”!=”+len);

Fin.close();

Return raw;

}

//定义编译指定java文件的方法

Private Boolean compile(String javaFile) throws IOException

{

 System.out.println(“CompileClassLoader:正在编译”+javaFile+”……..”);

 //调用系统的javac命令

Process p = Runtime.getRuntime().exec(“javac”+javaFile);

Try{

      //其它线程都等待这个线程完成

      p.waitFor();

}catch(InterruptedException ie)

{

 System.out.println(ie);

}

//获取javac 的线程的退出值

Int ret =p.exitValue();

//返回编译是否成功

Return  ret==0;

}

//重写Classloader的findCLass方法

Protected Class<?> findClass(String name)throws ClassNotFoundException

{

   Class clazz = null;

   //将包路径中的.替换成斜线/

String fileStub = name.replace(“.”,”/”);

String javaFilename = fileStub+”.java”;

String classFilename = fileStub+”.class”;

File javaFile= new File(javaFilename);

File classFile = new File(classFilename);

//当指定Java源文件存在,且class文件不存在,或者Java源文件的修改时间比class文件//修改时间晚时,重新编译

   If(javaFile.exists()&&(!classFIle.exists())||javaFIle.lastModified()>classFile.lastModified())

{

 

     Try{

  //如果编译失败,或该Class文件不存在

  If(!compile(javaFilename)||!classFile.exists())

{

 Throw new ClassNotFoundException(“ClassNotFoundException:”+javaFilename);

}

}catch(IOException ex)

  {

     Ex.printStackTrace();

}

}           

//如果class文件存在,系统负责将该文件转化成class对象

If(classFile.exists())

{

  Try{

     //将class文件的二进制数据读入数组

    Byte[] raw= getBytes(classFilename);

   //调用Classloader的defineClass方法将二进制数据转换成class对象

   Clazz = defineClass(name,raw,0.raw.length);

}catch(IOException ie)

{

  Ie.printStackTrace();

}

}

//如果claszz为null,表明加载失败,则抛出异常

If(clazz==null){

   Thow new ClassNotFoundException(name);

}

Return clazz;

}

 

//定义一个主方法

Public static void main(String [] args )throws Exception

{

//如果运行该程序时没有参数,即没有目标类

  If(args.length<1){

      System.out.println(“缺少运行的目标类,请按如下格式运行java源文件:”);

      System.out.println(“java CompileClassLoader ClassName”);

}

//第一个参数是需要运行的类

String progClass = args[0];

//剩下的参数将作为运行目标类时的参数,所以将这些参数复制到一个新数组中

String progargs[] = new String [args.leng-1];

Sytem.arraycopy(args,1,progargs,0,progArgs.length);

CompileClassLoader cl = new CompileClassLoader();

//加载需要运行的类

Class<?> clazz = cl.loadClass(progClass);

//获取需要运行的类的主方法

Method main= clazz.getMethod(“main”,(new String[0]).getClass());

Object argsArray[] = {progargs};

Main.invoke(null,argsArray);

}

}

自定义加载器实现如下功能:

执行代码前自动验证数字签名

根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译class文件

根据用户需求来动态的加载类

根据应用需求把其他数据以字节码的形式加载到应用中

URLClassLoader类

Java为ClassLoader提供了一个URLClassLoader实现类,该类也是系统类加载器和扩展类加载器的父类(此处是父类,而不是父类加载器,这里是类与类之间的继承关系),URLClassLoader功能比较强大,既可以从本地文件系统中获取二进制文件来加载类,也可以从远程主机上获取二进制文件来加载类

URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的系列路径来查询并加载类

URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父类加载器创建一个Classloader对象,其它功能与前一个构造器相同

一旦得到URLClassloader对象之后,就可以调用该对象的loadClass方法来加载指定的类

下列程序示范了如何直接从文件系统中加载Mysql驱动,并使用该驱动获取数据库连接,通过这种方式取得数据库连接就可以无需将mysql驱动添加到CLASSPATH环境变量中

Public class URLClassLoaderTest

{

  Private static Connection conn;

  //定义一个获取数据库连接方法

  Public static Connection getConn(String url,String user,String pass) throws Exception

  {

If(conn == null){

  //创建一个URL数组

URL[]  urls = {new URL(“file:mysql-connector-java-3.1.10-bin.jar”)};

//以默认的ClassLoader作为父ClassLoader,创建URLClassLoader

URLClassLoader myClassLoader= new URLClassLoader(urls);

//加载mysql的JDBC驱动,并创建默认实例

Driver driver = (Driver)myClassLoader.loadClass(“com.mysql.jdbc.Driver”).newInstance();

//创建一个设计JDBC连接属性的Properties对象

Properties props = new Properties();

//至少需要为该对象传入user和password两个属性

Props.setProperty(“user”,user);

Props.setProperty(“password”,pass);

//调用Driver 对象的connect方法来取得数据库连接

Conn = driver.connect(“jdbc:mysql://localhost:3306/mysql”,props);

 

}

Return conn;

Public static void main(String[] args)throws Exception

{

  System.out.println(getConn(“jdbc:mysql///j2ee”,”root”,”root”));

}

}

 

}

上面程序创建了URLCLassLoader对象,该对象使用默认的父类加载器,该类加载器的类加载路径是当前路径下的mysql-connector-java-3.1.10-bin.jar文件,我们将mysql的驱动复制到该路径下,这样保证该ClassLoader可以正常加载到com.mysql.jdbc.Driver类

使用ClassLoader的loadClass加载指定类,并调用Class对象的newInstance()方法来创建一个该类的实例----也就是得到com,mysql.jdbc.Driver类的对象,当然该对象的实现类实现类java.sql.Driver接口,所以程序将其强制类型转化为Driver,最后程序通过Driver而不是DriverManager来获取数据库连接,当我们创建URLClassLoader时传入了一个URL数组参数,则该ClassLoader就可以从这系列URL指定的资源中加载指定的类,这里的URL可以以file:为前缀,表明从本地文件系统加载,可以以http:为前缀,表明从互联网通过http访问来加载,也可以以ftp:为前缀,表明从互联网通过FTP访问来加载