自定义类加载器
1。浅析类加载器工作机制
ClassLoader顾名思义就是类加载器,负责将Class字节码文件加载到JVM中。
2。如何加载class文件
ClassLoader加载一个class文件到JVM时需要经过如下步骤:
- 第一阶段是找到.class文件并把这个文件包含的字节码加载到内存中;
- 第二阶段又可以分为三个步骤,分别是字节码验证、Class类数据结构分析以及相应的内存分配和最后的符号表的链接;
- 第三阶段是类中静态属性和初始化赋值,以及静态块的执行等。
3,加载字节码到内存
通常情况下,ClassLoader加载类时,会首先调用findClass方法Class<?> findClass(String name),找到我们的类的字节码文件,让后将这个文件包含的字节码加载到内存。但是在我们的ClassLoader抽象类中并没有定义如何去加载,即如何去找到指定的类并且把它的字节码加载到内存中。这个在它的子类URLClassLoader中重写了findClass()方法,在URLClassLoader中通过一个URLClassPath类帮助我们得到需要加载的class文件字节流,而这个URLClassPath定义了到哪里去找这个class文件,如果找到了这个class文件,在读取它的byte字节流,通过调用defineClass()方法来创建Class类对象。
我们在看看URLClassLoader类的构造函数,必须要传入一个URLClassPath对象,也就是必须要指定这个ClassLoader默认到哪个目录下去查找class文件。从URLClassPath的名字中可以发现,它是通过URL的形式来表示ClassPath路径的,也就是我们加载类的路径。
那么如何设置不同ClassLoader 的类加载路径的呢?如下图所示是BootStrapClassLoader、ExtClassLoader、AppClassLoader通过参数的形式指定类加载路径的:
<table>
<tr>
<td rowspan=3>BootStrapClassLoader</td>
<td bgcolor="#eeeeee"> -Xbootclasspath/p: </td>
<td bgcolor="#eeeeee">向前追加BootStrap ClassLoader的搜索路径</td>
</tr>
<tr>
<td bgcolor="#eeeeee"> -Xbootclasspath: </td>
<td bgcolor="#eeeeee">设置BootStrap ClassLoader的搜索路径</td>
<tr>
<td bgcolor="#eeeeee"> -Xbootclasspath/a:</td>
<td bgcolor="#eeeeee">向后追加BootStrap ClassLoader的搜索路径</td>
</tr>
<tr>
<td > ExtClassLoader </td>
<td bgcolor="#eeeeee"> -Djava.ext.dirs= </td>
<td bgcolor="#eeeeee">设置ExtClassLoader的搜索路径</td>
</tr>
<tr>
<td >AppClassLoader</td>
<td bgcolor="#eeeeee"> -Djava.class.path=
</td>
<td bgcolor="#eeeeee">设置AppClassLoader的搜索路径</td>
</tr>
</table>
4,验证与解析,
- 字节码验证,类加载器需要对类的字节码进行需要检测,以确保格式正确、行为正确。
- 类准备,这个阶段用于准备类中定义的字段、方法和实现的接口所必须的数据结构。
- 解析,在这个阶段类加载器,加载类所引用(import)的其他所有类,可以用许多方式来引用类,如extends、implements、定义属性、方法类型、方法中使用的变量类型等等。
5,初始化Class对象,
类中包含的静态代码块都会被执行,在这个阶段的末尾静态字段会被初始化成默认值。
6,ClassLoader类结构分析,
我们经常会用到或扩展ClassLoader,主要会用到如下几个方法,以及他们的重载方法:
**Class<?> defineClass(String name, byte[] b, int off, int len)**
用于将byte字节流解析成JVM能够识别的Class对象,有了这个方法意味着我们不仅仅能够通过class文件实例化对象,还可以通过其他方式如我们通过网络传输一个类的字节码流,拿到这个字节流我们可以创建类的Class对象,然后通过Class对象实例化类对象。
**Class<?> findClass(String name)**
defineClass通常会和findClass一起使用,我们通常会直接覆盖ClassLoader父类的findClass方法来实现类的加载规则,从而取得要加载类的字节码。然后调用defineClass来生成类的Class对象。
**resolveClass(Class<?> c)**
如果你想要类被加载到JVM中时就被链接(Linking上文有描述),那么可以主动的调用resolveClass方法。当然我们也可以选择让JVM来决定何时链接这个类。
**Class<?> loadClass(String name)**
如果你不想重新定义类的加载规则,也没有复杂的处理逻辑,只想在运行时加载自己指定的一个类而已,那么我们此时就可以直接调用ClassLoader的loadClass方法以获取类的Class对象。这个loadClass也有重载方法,我们同样可以决定在什么时候解析这个类。
ClassLoader是一个抽象类,我们想要定义自己的ClassLoader,一般不会继承ClassLoader,而是继承URLClassLoader,因为这个类已经帮助我们实现了大部分工作。
7,ClassLoader的等级加载机制,
JVM在加载类时默认采用的是双亲委派(或全盘委托)机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
整个JVM中提供了三个ClassLoader类,这个三个ClassLoader可以分为两种类型:BootStrapClassLoader属于一类,其他的属于一类。
**BootStrap ClassLoader**
它主要加载JVM自身工作需要的类,这个ClassLoader完全是自己控制的,需要加载哪个类,怎么加载都由JVM自己控制,别人也访问不到这个类,所以这个ClassLoader是不遵循上文说的加载规则的,它仅仅就是一个类加载器而已,即没有父加载器,也没有子加载器。
**ExtClassLoader**
这个类加载器有点特殊,它是JVM自身的一部分,但是它的血统不是很纯正,它并不是JVM亲自实现的。它加载的特定类在System.getProperties(“java.ext.dirs”)目录下。
**AppClassLoader**
这个类加载器就是用来专门加载应用类的,它的父类是ExtClassLoader。所有在System.getProperties(“java.class.path”)目录下的所有的类都由它来加载,这个目录就是我们常用的classpath。
注意我们如果要实现自定义的类加载器,不管你是直接继承ClassLoader,还是继承URLClassLoader类,或者其他子类,它的父加载器都是AppClassLoader,因为不管调用哪个父构造器,创建的对象都必须最终调用getSystemClassLoader()来作为我们的父类加载器,而该方法获得的恰好就是AppClassLoader。
注意:很多文章在介绍ClassLoader的时候,将BootStrapClassLoader列为我们ExtClassLoader的父加载器,其实BootStrapClassLoader并不属于JVM的类加载器等级层次,因为它并没有遵守类加载规则,另外它也没有子加载器。
8,自定义类加载器
理解了我们的类加载机制后,接下来我们开发一个“类加载器”来加载我们制定包下的所有类,比如使用了某些注解的类,或者实现了某些接口的类,在或者继承了某父类的所有类等。
## 编写ClassUtil工具类 ##
该工具类提供与类操作相关的方法,比如获取类加载器、加载类、获取制定包下的所有类等,代码如下:
/**
* 类加载器工具类
* @author think
*
*/
public class ClassUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtils.class);
/**
* 获取类加载器
* @return
*/
public static ClassLoader getClassLoader(){
。。。。。。
}
/**
* 加载指定的类
* @param className
* @param isInitialized
* @return
*/
public static Class<?> loadClass(String className,boolean isInitialized){
。。。。。。
}
/**
* 获取指定包下的所有的类
* @param packageName
* @return
*/
public static Set<Class<?>> getClassSet(String packageName){
。。。。。。
}
## 获取类加载器 ##
获取类加载器实现起来最为简单,只需要获取当前线程中的ClassLoader即可,该方法具体实现如下:
/**
* 获取类加载器
* @return
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
## 加载指定类 ##
加载类需要提供类名与是否初始化的标志,这里提供的初始化是指是否执行类的静态代码块,该方法具体实现如下:
/**
* 加载指定的类
* @param className
* @param isInitialized
* @return
*/
public static Class<?> loadClass(String className,boolean isInitialized){
Class<?> cls = null;
try {
cls = Class.forName(className,isInitialized,getClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error("load class failure!",e);
e.printStackTrace();
}
return cls;
}
为了提高类的加载性能,可将loadClass方法isInitialized参数设置为false。
## 获取制定包名下所有类 ##
最为复杂的是获取制定包名下的所有类,我们需要根据包名并将其转换为文件路径,读取class文件或jar包,获取指定的类名去加载类,该方法的具体实现如下:
/**
* 获取指定包下的所有的类
* @param packageName
* @return
*/
public static Set<Class<?>> getClassSet(String packageName){
Set<Class<?>> classSet = new HashSet<Class<?>>();
try {
Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
if(null != urls){
while(urls.hasMoreElements()){
URL url = urls.nextElement();
if(null != url){
String protocol = url.getProtocol();
if("file".equals(protocol)){
String packagePath = url.getPath().replaceAll("%20", " ");
addClass(classSet,packagePath,packageName);
}else if("jar".equals(protocol)){
JarURLConnection urlConnection = (JarURLConnection) url.openConnection();
if(null != urlConnection){
JarFile jarFile = urlConnection.getJarFile();
if(null != jarFile){
Enumeration<JarEntry> entries = jarFile.entries();
while(entries.hasMoreElements()){
JarEntry jarEntry = entries.nextElement();
String jarName = jarEntry.getName();
if(jarName.endsWith(".class")){
String className = jarName.substring(0, jarName.lastIndexOf(".")).replaceAll("/", ".");
doAddClass(classSet,className);
}
}
}
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return classSet;
}
/**
* 加载给的类全路径的类
* @param sets
* @param className
* @throws ClassNotFoundException
*/
private static void doAddClass(Set<Class<?>> sets,String className) throws ClassNotFoundException{
Class<?> cls = Class.forName(className);
sets.add(cls);
}
/**
* 加载指定包下的类
* @param sets
* @param packagePath
* @param packageName
* @throws ClassNotFoundException
*/
private static void addClass(Set<Class<?>> sets,String packagePath,String packageName) throws ClassNotFoundException{
File[] files = new File(packagePath).listFiles(new FileFilter(){
public boolean accept(File pathname) {
return pathname.isFile()&&pathname.getName().endsWith(".class")||pathname.isDirectory();
}});
if(files != null && files.length > 0){
for(File file:files){
String fileName = file.getName();
if(file.isFile()){
String className = fileName.substring(0, fileName.lastIndexOf("."));
if(!StringUtil.isEmpty(className)){
className = packageName+"."+className;
doAddClass(sets,className);
}
}else{
String subPackagePath = fileName;
if(!StringUtil.isEmpty(subPackagePath)){
subPackagePath=packagePath+"/"+subPackagePath;
}
String subPackageName = fileName;
if(!StringUtil.isEmpty(subPackageName)){
subPackageName = packageName+"."+subPackageName;
}
addClass(sets,subPackagePath,subPackageName);
}
}
}
}
|
|