类加载器是怎么加载包下的类呢?
《深入理解Java虚拟机》中有提到,只有在同一个类加载器加载出来的类,才具有类之间比较的价值。所以本文用一个简单的累加器例子来了解类加载器是怎么加载类。
在此,分为三个部分。
A:获取类加载器;
B:加载类;
C 获取指定包下的所有类。
public class ClassUtil { private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class); /** * 获取类加载器 * @return */ public static ClassLoader getClassLoader() { //A } /** * 加载类 * @param className * @param isInitialized * @return */ public static Class<?> loadClass(String className, boolean isInitialized){ //B } /** * 获取指定包名下的所有类 * @param packageName * @return */ public static Set<Class<?>> getClassSet(String packageName){ //C }
}
先有鸡才有蛋,所以在加载类之前,得先有类加载器。类加载器可以用JDK自带的,也可以自定义一个ClassLoader,此处使用当前线程中的ClassLoader,即A部分:
public static ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); }
其中,Thread.currentThread()可以获取到当前线程并对其进行操作,如suspend()。
接着我们可以加载类了,而加载类我们此处通过Class.forName()反射得到。
public static Class<?> loadClass(String className, boolean isInitialized){ Class<?> cls; try { cls = Class.forName(className, isInitialized, getClassLoader()); ClassLoader.getSystemClassLoader().loadClass(className); }catch(ClassNotFoundException e) { LOGGER.error("load class failure", e); throw new RuntimeException(e); } return cls; }
此处可能会有小伙伴问,Class.forName()和ClassLoader.loadClass()加载类有什么区别呢?
下面是本人的浅见。(此部分,须了解过Java虚拟机哦,不感兴趣的小伙伴可以略过~)
分为两部分:
1. 我们先来了解一下Class.forName(String name)和ClassLoader.loadClass(String name)的区别,可以参考此文:https://www.cnblogs.com/zabulon/p/5826610.html。简单来说就是,这两个方法在类加载时期的初始化、链接的操作不同,导致它们在静态资源加载方面产生不同的结果。那么如果我们将这一层的不同进行透明化呢?其实还是不同,因为他们加载的机制(或者说是算法)不一样。
2. Class.forName()和ClassLoader.loadClass()机制不同在哪儿呢?我们先看Class.forName()。
/** * Returns the {@code Class} object associated with the class or * interface with the given string name, using the given class loader. * Given the fully qualified name for a class or interface (in the same * format returned by {@code getName}) this method attempts to * locate, load, and link the class or interface. The specified class * loader is used to load the class or interface. If the parameter * {@code loader} is null, the class is loaded through the bootstrap * class loader. The class is initialized only if the * {@code initialize} parameter is {@code true} and if it has * not been initialized earlier.*/ @CallerSensitive public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { ....return forName0(name, initialize, loader, caller); } /** Called after security check for system loader access checks have been made. */ private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) throws ClassNotFoundException;
此处截取了一部分关于Class.forName()的注释,加粗部分的意思是“如果传入的ClassLoader为null的话,就直接使用Bootstrap类加载器”。 那么ClassLoader.loadClass()是怎么样的呢? ClassLoader.loadClass()最后也可能会用到Bootstrap类加载器,但是还会经过两步才会到使用启动类加载器,第一步是先找是否已经加载过此类,第二步是在双亲委派模型的基础上查找父类,使用父类加载器加载类。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } ...... } }
在ClassLoader.loadClass()中,会先去查找是否这个类已经被加载过了,findLoadedClass()方法是本地方法。如果没找到,再加载。假设大家知道类加载器的双亲委派模型,即如果一个加载器接收到类加载的请求,它会先将这个请求交给父类加载器去完成,各层次的类加载器都是这么处理请求的,只有当父加载器反馈自己无法完成这个加载请求的时候,子加载器才会尝试自己去加载。 所以此处也是先去查找此类的父类加载器对这个类进行加载。
/** * Returns a class loaded by the bootstrap class loader; * or return null if not found. */ private Class<?> findBootstrapClassOrNull(String name) { if (!checkName(name)) return null; return findBootstrapClass(name); }
如果该加载器没有父加载器,才会将请求直接交给Bootstrap加载器。
综上,Class,forName()和ClassLoader.loadClass()的不同之处有二,一是对于类加载过程中的把控部分不同;二是加载类选择类加载器的方式不同。
续上加载类的部分,接下来是怎么获取指定包名下的所有类。
/** * 获取指定包名下的所有类 * @param packageName * @return */ public static Set<Class<?>> getClassSet(String packageName){ Set<Class<?>> classSet = new HashSet<Class<?>>(); try { //将包名转化为文件路径 Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/")); while(urls.hasMoreElements()) { URL url = urls.nextElement(); if(url != null) { //获取文件的类型 String protocol = url.getProtocol(); //如果是"file"类型,就拿到这个文件里面的class文件 if(protocol.equals("file")) { String packagePath = url.getPath().replaceAll("%20", " "); addClass(classSet, packagePath, packageName); //如果是'jar'类型的,先读取这个包里面的文件,如果jar中有'class'文件,则读取出来 }else if(protocol.equals("jar")) { JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection(); if(jarURLConnection != null) { JarFile jarFile = jarURLConnection.getJarFile(); if(jarFile != null) { Enumeration<JarEntry> jarEntries = jarFile.entries(); while(jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); String jarEntryName = jarEntry.getName(); if(jarEntryName.endsWith(".class")) { String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", "."); doAddClass(classSet, className); } } } } } } } }catch(Exception e) { LOGGER.error("get class set failure", e); throw new RuntimeException(e); } return classSet; } private static void doAddClass(Set<Class<?>> classSet, String className) { Class<?> cls = loadClass(className, false); classSet.add(cls); } private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) { File[] files = new File(packagePath).listFiles(new FileFilter() { @Override public boolean accept(File file) { return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory(); } }); for(File file: files) { String fileName = file.getName(); if(file.isFile()) { String className = fileName.substring(0, fileName.lastIndexOf(".")); if(StringUtil.isNotEmpty(packageName)) { className = packageName + "." + className; } doAddClass(classSet, className); }else { String subPackagePath = fileName; if(StringUtil.isNotEmpty(packagePath)) { subPackagePath = packagePath + "/" + subPackagePath; } String subPackageName = fileName; if(StringUtil.isNotEmpty(packageName)) { subPackageName = packageName + "." +subPackageName; } } } }
此部分,概括起来就是,通过反射的方式,将这个包下的file类型文件和jar类型文件分来加载,如果是jar类型的文件,则将jar中的class文件加载出来。
总结一下哈,本文通过一个简单的类加载器工具类,讲述了加载包中的类的过程:1. 先将包中的文件分为两种,一种是file类型的文件,这种直接加载,另一种是jar类型的文件,这种还需要将文件包打开,将其中的,class文件通过加载类方法将类加载出来。而且本文还稍稍提及了一下Class.forName()和ClassLoader.loadClass()这两种加载类的方法的不同之处。