版本说明
tomcat 8.5.xx
jdk 1.8.xxx
ClassLoader结构引入
ClassLoader cl = this.getClass().getClassLoader();
while(cl != null){
System.out.println(cl.getClass().getCanonicalName());
cl = cl.getParent();
}
将会打印以下内容
org.apache.catalina.loader.ParallelWebappClassLoader
java.net.URLClassLoader
sun.misc.Launcher.AppClassLoader
sun.misc.Launcher.ExtClassLoader
为什么会打印以下的内容呢
AppClassLoader(加载系统配置项 java.class.path) 和 ExtClassLoader(加载系统配置项java.ext.dirs)是jdk自带的类加载器
ClassLoader | 加载类路径 |
---|---|
BootStrapClassLoader | sun.boot.class.path(\jdk1.8.0_131\jre\lib*.jar) |
ExtClassLoader | java.ext.dirs(\jdk1.8.0_131\jre\lib\ext*.jar,C:\WINDOWS\Sun\Java\lib\ext*.jar) |
AppClassLoader | java.class.path |
小提示
AppClassLoader可以在启动java程序的时候通过-classpath或者-cp参数指定
那么BootStrapClassLoader(加载rt.jar)怎么没有了?
这也是一个点
今天看了一篇博文 http://blog.csdn.net/briblue/article/details/54973413#t32
上面说BootstrapClassLoader是C++实现的
Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,我们前面已经分析了,JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的现象。具体是什么原因,很快就知道答案了。
委托机制
先来看看类加载器的委托机制是怎么在java代码之中实现的,先来看看ClassLoader
的loadClass()
方法,
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
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
(1) 在findLoadedClass
方法中,其实findLoadedClass0
的一个包装方法,这个方法是一个native方法,主要作用是查看当前虚拟机中的查看此类是否已经被加载了。
(2)接下来会先从parent链中加载类,先从最顶层加载类,如果加载成功了,就返回。如果没有成功则继续加载。加入加载XXXX类并且XXXX类没有被加载,现在的ClassLoader是AppClassLoader,那么会先从BootStrapClassLoader中加载,如果没有,则会从ExtClasLoader中加载,如果还是没有才从AppClassLoader中加载,否则抛出ClassNotFundException。
以上就是委托机制的实现。
自定类加载器
那我们再接下来想一个问题,我们都知道我们的war包目录结构如下
如果我们自己手动加载一个类呢,从classes中加载和从lib中加载有什么区别呢?
从classes中加载
/**
* 查找class 可以在这个方法之中修改逻辑,从而优先加载自己的类,
* 当然java.lang.String包里面并不可以,因为它在jvm启动的时候已经加载过了
*
* @param name 类名
*
* @return class or null
*
* @throws ClassNotFoundException 类未寻找到异常
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> aClass = findLoadedClass(name);
if (aClass != null) {
return aClass;
}
if (name.startsWith(packageName)) {
byte[] classData = getData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}else{
return super.loadClass(name);
}
}
private byte[] getData(String name) {
String path = classPath + File.separator + name.replace(".", File.separator)+ ".class";
try {
ByteArrayOutputStream result = new ByteArrayOutputStream();
URL url = new URL(path);
InputStream is = url.openStream();
byte[] buffer = new byte[2048];
int num = 0;
while((num = is.read(buffer)) != -1){
result.write(buffer, 0 , num);
}
return result.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
测试程序
String classPath = "file:D:\\classloader\\classes";
String className = "com.wuhulala.future.FutureExample";
NetClassLoader cl = new NetClassLoader(classPath);
cl.loadClass(className);
拿到类就可以根据反射做各种操作了
从jar包中加载类
没有封装,直接使用了URLClassLoader
String jarFilePath = "file:F:\\code\\javase\\java_up\\jar\\commons-beanutils-1.8.3.jar";
URL[] urls = new URL[1];
urls[0] = new URL(jarFilePath);
URLClassLoader classLoader = URLClassLoader.newInstance(urls);
Class<?> fastHashMapClazz = classLoader.loadClass("org.apache.commons.collections.FastHashMap");
Object fastHashMap = fastHashMapClazz.newInstance();
System.out.println(fastHashMap);
其实确实没有什么不同。。。
URLClassLoader相对于ClassLoader封装了很多有效的方法,并且继承了SecureClassLoader,具有了一定的安全性,只不过我还没有用到过这里的特性,Spring中也有相似的处理!!!
那么tomcat相对于我们自己的实现有什么高明之处呢?
tomcat类加载器实现
首先看一下tomcat的类加载器,继承了WebappClassLoaderBase ,这个Base类里面才是真正实现了类加载的功能的覆盖
public class WebappClassLoader extends WebappClassLoaderBase
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 先上个锁,防止并发出现问题
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null;
// Log access to stopped class loader
// 判断当前类是否正在加载,和Spring中的bean创建一样
checkStateForClassLoading(name);
// (0) Check our previously loaded local class cache
// 判断tomcat是否已经加载过,根据resourceEntries这个缓存容器,实现采用的是ConcurrentHashMap
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
// 是否需要link
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.1) Check our previously loaded class cache
// 判断当前jvm中是否已经加载过此类呢?
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
// 尝试开始加载此类
String resourceName = binaryNameToPath(name, false);
// 先从BootstrapClassLoader中尝试加载
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
} catch (Throwable t) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
// The getResource() trick won't work for this class. We have to
// try loading it directly and accept that we might get a
// ClassNotFoundException.
tryLoadingFromJavaseLoader = true;
}
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
// 判断是否有权限操作此类
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = "Security Violation, attempt to use " +
"Restricted Class: " + name;
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
// 判断类是否需要父类加载器,即tomcat内置的类加载器,tomcat8.5.x使用的是一个URLClassLoader去加载,待会分析以下filter方法
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested
// 通过父classLoader去加载
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
try {
clazz = Class.forName(name, false,
);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (2) Search local repositories
// 查询依照/WEB-INF/classes /WEB-INF/lib这个顺序寻找类
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
// (3) Delegate to parent unconditionally
// 无条件的使用委托机制加载
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
下面解释一下为什么是按照/WEB-INF/classes和/WEB-INF/lib的顺序加载的呢?
WebResource classes = resources.getResource("/WEB-INF/classes");
if (classes.isDirectory() && classes.canRead()) {
localRepositories.add(classes.getURL());
}
WebResource[] jars = resources.listResources("/WEB-INF/lib");
for (WebResource jar : jars) {
if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
localRepositories.add(jar.getURL());
jarModificationTimes.put(
jar.getName(), Long.valueOf(jar.getLastModified()));
}
}
上面的localRepositories是一个List的数据结构
还有上面的filter方法,就是判断这些类是不是tomcat的内置了的类,我们可以看看tomcat的包目录,提一句,这些class是由StandardClassLoader加载的,再看下面代码
protected boolean filter(String name, boolean isClassName) {
if (name == null)
return false;
char ch;
if (name.startsWith("javax")) {
/* 5 == length("javax") */
if (name.length() == 5) {
return false;
}
ch = name.charAt(5);
if (isClassName && ch == '.') {
/* 6 == length("javax.") */
if (name.startsWith("servlet.jsp.jstl.", 6)) {
return false;
}
if (name.startsWith("el.", 6) ||
name.startsWith("servlet.", 6) ||
name.startsWith("websocket.", 6) ||
name.startsWith("security.auth.message.", 6)) {
return true;
}
} else if (!isClassName && ch == '/') {
/* 6 == length("javax/") */
if (name.startsWith("servlet/jsp/jstl/", 6)) {
return false;
}
if (name.startsWith("el/", 6) ||
name.startsWith("servlet/", 6) ||
name.startsWith("websocket/", 6) ||
name.startsWith("security/auth/message/", 6)) {
return true;
}
}
} else if (name.startsWith("org")) {
/* 3 == length("org") */
if (name.length() == 3) {
return false;
}
ch = name.charAt(3);
if (isClassName && ch == '.') {
/* 4 == length("org.") */
if (name.startsWith("apache.", 4)) {
/* 11 == length("org.apache.") */
if (name.startsWith("tomcat.jdbc.", 11)) {
return false;
}
if (name.startsWith("el.", 11) ||
name.startsWith("catalina.", 11) ||
name.startsWith("jasper.", 11) ||
name.startsWith("juli.", 11) ||
name.startsWith("tomcat.", 11) ||
name.startsWith("naming.", 11) ||
name.startsWith("coyote.", 11)) {
return true;
}
}
} else if (!isClassName && ch == '/') {
/* 4 == length("org/") */
if (name.startsWith("apache/", 4)) {
/* 11 == length("org/apache/") */
if (name.startsWith("tomcat/jdbc/", 11)) {
return false;
}
if (name.startsWith("el/", 11) ||
name.startsWith("catalina/", 11) ||
name.startsWith("jasper/", 11) ||
name.startsWith("juli/", 11) ||
name.startsWith("tomcat/", 11) ||
name.startsWith("naming/", 11) ||
name.startsWith("coyote/", 11)) {
return true;
}
}
}
}
return false;
}
以上就是整个tomcat的类加载内容了,欢迎拍砖!!!