自定义类加载器-从.class和.jar中读取

xiaoxiao2021-02-28  116

https://segmentfault.com/a/1190000008669892

一. 类加载器

JVM中的类加载器:在jvm中,存在两种类加载器,a) Boostrap ClassLoader:这个是由c++实现的,所以在方法区并没有Class对象的实例存在。用于加载JAVA_HOME/bin目录下的jar包

b) 其他类加载器:由java实现,可以在方法区找到其Class对象。这里又细分为几个加载器

扩展类加载器(Extension ClassLoader):它负责用于加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量指定所指定的路径中所有类库,开发者可以直接使用扩展类加载器。java.ext.dirs系统变量所指定的路径的可以通过程序来查看。System.getProperty("java.ext.dirs")

应用程序类加载器(Application ClassLoader):负责加载用户类路径上指定的类库。开发者可以直接使用这个类加载器。ps:在没有指定自定义类加载器的情况下,这就是程序的默认加载器。

自定义类加载器(User ClassLoader):

双亲委派模型:避免由于Class字节码被多次加载。底层类加载器在收到一个类加载的请求的时候,都先把请求转发给其父加载器(并不是一个继承的关系),父类查找不到才会让子类去加载。如果强制只有双亲委派模型,那么,web服务器的隔离是无法实现的。

  由于最近想做一个类似tomcat一样的简易版web服务器来加深理解http请求的处理过程。我们都清楚,每个web应用在tomcat中都可以使用自己版本的jar。除了少量的包,如Servlet-api.jar,还有一些java原生的包之外,tomcat是会为每个不同的应用加载不同的jar包或者class,且彼此之间不会相互影响。这一步是通过自定义类加载器来实现的,在虚拟机层面,判断两个Class是否相等的前提是他们是同一个类加载器加载的,否则就没有意义了。这篇文章简单的实现一个自定义的加载过程,PS:这个例子并没有破坏双亲委派模型,因为例子中依然会查找父类,如果找不到再使用子类加载。接下来笔者会再更新破坏双亲委派模型的博客,这里挖个坑。  首先自定义类加载器,最重要的就是先继承ClassLoader这个类。加载器的加载流程是,给出一个Class文件的全限定名,然后调用loadClass方法,这个方法每部会现在自己已经加载的类中查找,如果找到就返回。找不到则向父类查找,如果父类都找不到这才开始自己加载,调用findClass方法。所以我们只要覆盖findClass方法就可以实现自己定义的加载了。顺带一提,ClassLoader中有一个方法叫defineClass(String, byte[], int, int);这个方法通过传进去一个Class文件的字节数组,就可以方法区生成一个Class对象。所以要实现findClass的目标就很明确了,只要将Class文件读取进来,然后生成byte数组,调用defineClass方法就可以了。

@Override protected Class<?> findClass(String name){ try { byte[] result = getClassFromFileOrMap(name); if(result == null){ throw new FileNotFoundException(); }else{ return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; }

  那么如何找到Class文件呢?手动生成/网络下载我们就暂时不谈论。这里只说两种最常见的,一是直接.class文件中查找,二是从jar包中加载。从class文件中加载非常简单。只要找到相应的文件,就可以通过字节流读取进来。代码如下:

input = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray();

  从jar读取则相对麻烦一点,java给我们提供了一个专门用来读取jar包文件的类,抽象成一个JarFile的对象。通过调用这个对象的getInputStream方法,也是可以获取文件的输入流,从而读取字节数组。笔者做了一点相应的缓存,如果每次查找文件都要先读取jar文件,再遍历查找class文件是非常耗时的操作。于是,笔者选择再加载之前,把所有的jar包中的所有class读取到内存中,保存在一个map对象中。建立一个全限定名和字节数组的映射。这样在加载阶段,就能省下很多的时间了。全部的代码如下

package com.chasel.cloader; package com.chasel.cloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; /**  * 自定义的类加载器【子类优先】  * @author hujiancai  * @description   * @data 2017年3月11日  * @version v_0.1  */ public class MyWebAppLoader extends ClassLoader{     /**      * lib:表示加载的文件在jar包中      * 类似tomcat就是{PROJECT}/WEB-INF/lib/      */     private String lib;     /**      * classes:表示加载的文件是单纯的class文件      * 类似tomcat就是{PROJECT}/WEB-INF/classes/      */     private String classes;     /**      * 采取将所有的jar包中的class读取到内存中      * 然后如果需要读取的时候,再从map中查找      */     private Map<String, byte[]> map;          /**      * 只需要指定项目路径就好      * 默认jar加载路径是目录下{PROJECT}/WEB-INF/lib/      * 默认class加载路径是目录下{PROJECT}/WEB-INF/classes/      * @param webPath      * @throws MalformedURLException       * @throws SecurityException       * @throws NoSuchMethodException       */     public MyWebAppLoader(String webPath) throws NoSuchMethodException, SecurityException, MalformedURLException{         lib = webPath + "WEB-INF/lib/";         classes = webPath + "WEB-INF/classes/";         map = new HashMap<String,byte[]>(64);                  preReadJarFile();     }     /**      * 按照父类的机制,如果在父类中没有找到的类      * 才会调用这个findClass来加载      * 这样只会加载放在自己目录下的文件      * 而系统自带需要的class并不是由这个加载      */     @Override     protected Class<?> findClass(String name){         try {             byte[] result = getClassFromFileOrMap(name);             if(result == null){                 throw new FileNotFoundException();             }else{                 return defineClass(name, result, 0, result.length);             }         } catch (Exception e) {             e.printStackTrace();         }         return null;     }          /**      * 从指定的classes文件夹下找到文件      * @param name      * @return      */     private byte[] getClassFromFileOrMap(String name){         String classPath = classes + name.replace('.', File.separatorChar) + ".class";         File file = new File(classPath);         if(file.exists()){             InputStream input = null;             try {                 input = new FileInputStream(file);                 ByteArrayOutputStream baos = new ByteArrayOutputStream();                  int bufferSize = 4096;                  byte[] buffer = new byte[bufferSize];                  int bytesNumRead = 0;                  while ((bytesNumRead = input.read(buffer)) != -1) {                      baos.write(buffer, 0, bytesNumRead);                  }                 return baos.toByteArray();             } catch (FileNotFoundException e) {                 e.printStackTrace();             } catch (IOException e) {                 e.printStackTrace();             } finally{                 if(input != null){                     try {                         input.close();                     } catch (IOException e) {                         e.printStackTrace();                     }                 }             }                      }else{             if(map.containsKey(name)) {                 //去除map中的引用,避免GC无法回收无用的class文件                 return map.remove(name);             }         }         return null;     }          /**      * 预读lib下面的包      */     private void preReadJarFile(){         List<File> list = scanDir();         for(File f : list){             JarFile jar;             try {                 jar = new JarFile(f);                 readJAR(jar);             } catch (IOException e) {                 e.printStackTrace();             }         }     }          /**      * 读取一个jar包内的class文件,并存在当前加载器的map中      * @param jar      * @throws IOException      */     private void readJAR(JarFile jar) throws IOException{         Enumeration<JarEntry> en = jar.entries();         while (en.hasMoreElements()){             JarEntry je = en.nextElement();             String name = je.getName();             if (name.endsWith(".class")){                 String clss = name.replace(".class", "").replaceAll("/", ".");                 if(this.findLoadedClass(clss) != null) continue;                                  InputStream input = jar.getInputStream(je);                 ByteArrayOutputStream baos = new ByteArrayOutputStream();                  int bufferSize = 4096;                  byte[] buffer = new byte[bufferSize];                  int bytesNumRead = 0;                  while ((bytesNumRead = input.read(buffer)) != -1) {                      baos.write(buffer, 0, bytesNumRead);                  }                 byte[] cc = baos.toByteArray();                 input.close();                 map.put(clss, cc);//暂时保存下来             }         }     }          /**      * 扫描lib下面的所有jar包      * @return      */     private List<File> scanDir() {         List<File> list = new ArrayList<File>();         File[] files = new File(lib).listFiles();         for (File f : files) {             if (f.isFile() && f.getName().endsWith(".jar"))                 list.add(f);         }         return list;     }          /**      * 添加一个jar包到加载器中去。      * @param jarPath      * @throws IOException       */     public void addJar(String jarPath) throws IOException{         File file = new File(jarPath);         if(file.exists()){             JarFile jar = new JarFile(file);             readJAR(jar);         }     } }
转载请注明原文地址: https://www.6miu.com/read-64830.html

最新回复(0)