本文主要内容
- 类加载器根本概念
- 自定义类加载器
- 类的阻隔
- Android类加载器事例
虚拟机类加载机制 文中现已对类加载机制详细论述了,这两天对类的阻隔,损坏双亲托付机制等内容有了新的理解,同时论述下Android上类加载器事例。
以双亲托付机制图镇楼:
类加载器根本概念
望文生义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。
有了Class类实例,就能够经过newInstance办法创建该类的目标。
一般来说,默许类加载器为当时类的类加载器。比方A类中引证B类,A的类加载器为C,那么B的类加载器也为C。
1、ClassLoader
ClassLoader类是一个抽象类,它定义了类加载器的根本办法。
办法 | 阐明 |
---|---|
getParent() | 回来该类加载器的父类加载器。 |
loadClass(String name) | 加载称号为 name的类,回来的结果是 java.lang.Class类的实例。 |
findClass(String name) | 查找称号为 name的类,回来的结果是 java.lang.Class类的实例。 |
findLoadedClass(String name) | 查找称号为 name的现已被加载过的类,回来的结果是 java.lang.Class类的实例。 |
defineClass(String name, byte[] b, int off, int len) | 把字节数组 b中的内容转换成 Java 类,回来的结果是 java.lang.Class类的实例。这个办法被声明为 final的。 |
来看看 loadClass 办法的代码:
protected Class<?> loadClass(String name, boolean resolve){
Class c = findLoadedClass(name);
if (c == null) {
if (parent != null) {
//运用父加载器加载此类
c = parent.loadClass(name, false);
}
if (c == null) {
// 假如父加载器没有成功加载,则自己测验加载
c = findClass(name);
}
}
return c;
}
这段代码定义了双亲托付模型。所以自定义类加载器尽量不要去重写 loadClass ,而应该重写 findClass 办法。下边让我们来完结一个自定义类加载器。
自定义类加载器
自定义类加载器还是很有必要的,尤其是在web服务器上,比方tomcat,自定义加载器能够指定本身加载类的范围,乃至经过承继联系,到达类的阻隔意图。
Android上也有自定义类加载器,Android上的策略是,每个apk都由不同的类加载器实例来加载。思考一下,假如两个apk中有相同姓名的类,假如由同一个类加载器实例来加载,那肯定会混淆。另一方面也是安全问题。
经过前一章的学习,可知自定义类加载器一般只重写findClass办法即可。真实完结类的加载工作是经过调用 defineClass来完结的;而发动类的加载进程是经过调用 loadClass来完结的。
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}
类的阻隔
类的阻隔主要有以下两点:
- 不同的类加载器为相同称号的类创建了额定的称号空间,不同类加载器加载的类是不兼容的。
- 为了安全或其它意图,使某个模块无法加载某个类。
第1点比较简单,网上有许多这方面的文章,在此不再论述。
第2点稍等复杂点,以tomcat为例,tomcat服务器所用到的jar包不希望web运用运用,所以tomcat设计了以下经典的类加载模型:
其中webApp运用的类加载器为 WebApp类加载器,而tomcat服务器类加载器为 Catalina类加载器,假如webApp想加载tomcat所运用的jar包,它先会托付它的父加载器去加载,依据上图所示,它的父加载器也无法加载(因为tomcat所引证jar包全由Catalina加载,而Catalina并不是WebApp的父加载器),因为它自己也无法加载,所以完结阻隔。
在双亲托付机制下,同级的类加载器,能够完结类的阻隔。
别的,顶层类加载器约束较大,有时它无法加载类也无法托付子类加载器去加载,也会导致阻隔。
1、线程上下文类加载器
JDBC是Java开发者经常遇到的内容,它的核心类为java.sql.DriverManager,检查它的包名,就知道此类是由 发动类加载器(Bootstrap ClassLoader)加载,但JDBC的详细驱动完结是由各个厂商自己完结的。DriverManager需求调用由厂商自己完结的接口,这些接口是由 用户程序类加载器(Application ClassLoader)加载。
由前文知,DriverManager由Bootstrap加载,当DriverManager引证厂商完结的JDBC接口时,DriverManager仍然会运用自己的类加载器,也便是Bootstrap去加载,但Bootstrap只能加载JAVA_HOME下的class文件,厂商完结的JDBC接口,Bootstrap无法加载。这种问题,双亲托付模型现已无法处理了。
为了处理此问题,Java开了后门,也便是添加了线程上下文类加载器,Java为每个线程设置了默许的线程上下文类加载器:Application ClassLoader,当呈现上述情况时,直接运用线程上下文类加载器加载。
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
}
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
在JDBC的比如中,DriverManager不再运用自己的类加载器(Bootstrap)去加载,而是运用线程上下文类加载器去加载,而线程上下文类加载器便是 用户程序类加载器(Application ClassLoader),这当然能加载类成功。
这种父类加载器请求子类加载器去加载类的行为,实质上现已损坏了双亲托付模型。
2、Class.forName
JDBC在运用之前,一定要调用一句话,Class.forName,许多人告诉我,这是要去加载驱动类。
Class.forName("com.mysql.jdbc.Driver");
仔细想一想,这不对,假如代码引证了某个类,会去主动加载类,不需求用户手动加载,除非是当时的类加载器无法加载此类。
检查Driver类的源码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
类的加载,会有一个初始化阶段,在初始化阶段会履行类的 clinit 办法,也便是会履行类的静态句子块。
经过以上头绪发现,其实调用 Class.forName并不是要加载驱动类,而是调用驱动类的静态句子块,向DriverManager注册自己而已。
Android类加载器事例
Android apk动态加载研讨 文中提到了Context的类加载器与当时apk的类加载器相同,其实这句话是不对的,只是Context类重写了getClassLoader办法。
public ClassLoader getClassLoader() {
return mPackageInfo != null ?
mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}
并不是Context的类加载器不同,而是经过LoadedApk获取的类加载器不同。
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
libraryPermittedPath, mBaseClassLoader);
LoadedApk的getClassLoader办法中,依据apk的途径等参数,生成了新的ClassLoader,以确保不同的apk对应着不同的ClassLoader。
PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(
zip,
librarySearchPath,
libraryPermittedPath,
parent,
targetSdkVersion,
isBundled);
比方说,在ActivityThread类中,生成新的Activity时,就运用了新生成的Classloader,确保不同apk的Activity是由不同Classloader生成的。
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
。。。
}
newActivity的代码如下:
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
其实便是调用ClassLoader 加载详细Activity全类名,然后调用newInstance生成一个新的目标。
阅览源码往往有意想不到的收成,当时怀疑为啥Context的类加载器不一样,一读源码,收成还挺多的,我们遇到疑问多多读源码吧。