《深入理解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()这两种加载类的方法的不同之处。