还是Tomcat,关于类加载器的趣味实验
一、前言
类加载器,其实是很复杂一个东西,想等到我完全什么都弄明白了再写出来,估计不太现实。。。现在只能是知道多少写多少吧。
首先,我提一个问题:在我们自己的servlet中(比如ssm中,controller的代码),可以访问 tomcat 安装目录下 lib 中的类吗?(servlet-api.jar包中的不算)
好好思考一下再回答。如果你说不可以,那可能接下来会有点尴尬。。。
二、测试
1、tomcat 类加载器结构复习
咱们看图说话,应用程序类加载器,主要加载classpath路径下的类,在tomcat 的启动脚本里,最终会设置为 bin 目录下的bootstrap.jar 和tomcat-juli.jar:
common类加载器主要用于加载 tomcat 中间件自身、webapp 都可以访问的类;
catalina 类加载器,主要用于加载 tomcat 自身的类, webapp 不能访问;
共享类(shared)类加载器, 主要是用于加载 webapp 共享的类,比如大家都用 spring 开发,该类加载器的初衷就是加载 共用的 spring 相关的jar包。
这三者的加载路径,可以查看 Tomcat (我这边是Tomcat 8)安装目录下,conf / catalina.properties:
#
#
# List of comma-separated paths defining the contents of the "common"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank,the JVM system loader will be used as Catalina's "common"
# loader.
# Examples:
# "foo": Add this folder as a class repository
# "foo/*.jar": Add all the JARs of the specified folder as class
# repositories
# "foo/bar.jar": Add bar.jar as a class repository
#
# Note: Values are enclosed in double quotes ("...") in case either the
# ${catalina.base} path or the ${catalina.home} path contains a comma.
# Because double quotes are used for quoting, the double quote character
# may not appear in a path.
18 common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" #
# List of comma-separated paths defining the contents of the "server"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank, the "common" loader will be used as Catalina's "server"
# loader.
# Examples:
# "foo": Add this folder as a class repository
# "foo/*.jar": Add all the JARs of the specified folder as class
# repositories
# "foo/bar.jar": Add bar.jar as a class repository
#
# Note: Values may be enclosed in double quotes ("...") in case either the
# ${catalina.base} path or the ${catalina.home} path contains a comma.
# Because double quotes are used for quoting, the double quote character
# may not appear in a path.
36 server.loader= #
# List of comma-separated paths defining the contents of the "shared"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
# the "common" loader will be used as Catalina's "shared" loader.
# Examples:
# "foo": Add this folder as a class repository
# "foo/*.jar": Add all the JARs of the specified folder as class
# repositories
# "foo/bar.jar": Add bar.jar as a class repository
# Please note that for single jars, e.g. bar.jar, you need the URL form
# starting with file:.
#
# Note: Values may be enclosed in double quotes ("...") in case either the
# ${catalina.base} path or the ${catalina.home} path contains a comma.
# Because double quotes are used for quoting, the double quote character
# may not appear in a path.
55 shared.loader=
但是,应该是从 tomcat 7开始, common.loader 和 shared.loader 已经默认置空了。 为什么留空的原因,这里先不详细讲述。(因为我也不完全懂啊,哈哈哈)
Webapp 类加载器就不用说了, 主要是加载自身目录下的 WEB-INF/classes、 WEB-INF/lib 中的类。
对这部分感兴趣的,可以再看看我的另一篇文章:实战分析Tomcat的类加载器结构(使用Eclipse MAT验证)
我们再回头看看,文章开头的图里,清晰地展示了: webapp的类加载器的parent,即为 common 类加载器。 那么,只要我们在 业务代码里进行如下调用,应该就获取到了 common 类加载器,于是就可以愉快地加载 Tomcat 安装目录下的 lib目录的jar了:
ClassLoader classLoader = this.getClass().getClassLoader();
ClassLoader directparent = classLoader.getParent();
2、验证程序
我这边建了个简单的web程序,只有一个servlet。
MyServlet .java:
import javax.servlet.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader; public class MyServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException { } @Override
public ServletConfig getServletConfig() {
return null;
} @Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
ClassLoader classLoader = this.getClass().getClassLoader();
System.out.println("当前类加载器(webapp加载器):" + classLoader);
printPath(classLoader); ClassLoader directparent = classLoader.getParent();
System.out.println("父加载器(tomcat 自身的加载器):" + directparent);
printPath(directparent); // 从父加载器开始循环,应该会按顺序取到:应用类加载器--ext类加载器--bootstrap加载器
classLoader = directparent;
while (classLoader != null){
ClassLoader parent = classLoader.getParent();
System.out.println("当前类加载器为:" + parent);
printPath(parent);
classLoader = parent;
} if (directparent != null) {
try {
Class<?> loadClass = directparent.loadClass("org.apache.catalina.core.StandardEngine");
Object instance = loadClass.newInstance();
Method[] methods = loadClass.getMethods();
System.out.println("以下为StandardEngine的所有方法.................");
for (Method method : methods) {
System.out.println(method);
} System.out.println("反射调用方法测试............................");
Method getDefaultHostMethod = loadClass.getMethod("getDefaultHost");
Object result = getDefaultHostMethod.invoke(instance);
System.out.println("before:" + result); Method setDefaultHostMethod = loadClass.getMethod("setDefaultHost", String.class);
setDefaultHostMethod.invoke(instance,"hahaha..."); Object afterResult = getDefaultHostMethod.invoke(instance);
System.out.println("after:" + afterResult); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
}
} private void printPath(ClassLoader directparent) {
if (directparent instanceof URLClassLoader){
URLClassLoader urlClassLoader = (URLClassLoader) directparent;
URL[] urLs = urlClassLoader.getURLs();
for (URL urL : urLs) {
System.out.println(urL);
}
}
} @Override
public String getServletInfo() {
return null;
} @Override
public void destroy() { }
}
加入到 web.xml中:
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>MyServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.ckl</groupId>
<artifactId>tomcatclassloader</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging> <name>tomcatclassloader Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties> <dependencies> <dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency> </dependencies> <build>
<finalName>tomcatclassloader</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
运行结果如下:
当前类加载器(webapp加载器):ParallelWebappClassLoader
context: tomcatclassloader
delegate: false
----------> Parent Classloader:
java.net.URLClassLoader@1372ed45 file:/F:/ownprojects/tomcatclassloader/target/tomcatclassloader/WEB-INF/classes/ 父加载器(tomcat 自身的加载器):java.net.URLClassLoader@1372ed45 file:/D:/soft/apache-tomcat-8.5.23/lib/
file:/D:/soft/apache-tomcat-8.5.23/lib/annotations-api.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/catalina-ant.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/catalina-ha.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/catalina-storeconfig.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/catalina-tribes.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/catalina.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/ecj-4.6.3.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/el-api.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/jasper-el.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/jasper.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/jaspic-api.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/jsp-api.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/servlet-api.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/tomcat-api.jar
file:/D:/soft/apache-tomcat-8.5.23/lib/tomcat-coyote.jar
。。。此处省略部分。。。 当前类加载器为:sun.misc.Launcher$AppClassLoader@18b4aac2
file:/D:/soft/apache-tomcat-8.5.23/bin/bootstrap.jar
file:/D:/soft/apache-tomcat-8.5.23/bin/tomcat-juli.jar
当前类加载器为:sun.misc.Launcher$ExtClassLoader@43d7741f
file:/C:/Program%20Files/Java/jdk1.8.0_172/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Java/jdk1.8.0_172/jre/lib/ext/cldrdata.jar
。。。此处省略部分。。。 当前类加载器为:null
以下为StandardEngine的所有方法.................
public void org.apache.catalina.core.StandardEngine.setParent(org.apache.catalina.Container)
public org.apache.catalina.Service org.apache.catalina.core.StandardEngine.getService()
。。。此处省略部分。。。 反射调用方法测试............................
before:null
after:hahaha...
由上可知,我们访问tomcat 自身的类,比如 org.apache.catalina.core.StandardEngine,是完全没问题的。
二、可怕的参数传递实验
1、实验思路
不太好描述,直接码,我们首先定义一个测试类,
# TestSample.java
public class TestSample { public void printClassLoader(TestSample testSample) {
System.out.println(testSample.getClass().getClassLoader());
}
}
这个类,足够简单,里面仅一个方法,方法接收一个自己类型的参数,方法体是打印出参数的类加载器。
在测试类中,直接 new 一个该类的对象A,然后调用其 printClassLoader,将对象A自己传入,默认的打印结果是:
TestSample loader = new TestSample();
loader.printClassLoader(loader);
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader 这个类,就是我们的应用类加载器,一般程序里,没有显示定义过类加载器的话,classpath下的类都由该类加载。
我们要做的试验有两个:
1、如果传入的参数对象,由另外一个类加载器加载的,能调用成功吗,如果成功,结果是什么?
2、如果由两个相同类加载器的不同实例,来加载 TestSample ,然后反射获取对象,那么其中一个能作为另一个对象的 printClassLoader 的参数吗?
开始之前,先准备好我们自定义的类加载器,
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException; /**
* desc:
*
* @author : caokunliang
* creat_date: 2019/6/13 0013
* creat_time: 10:19
**/
public class MyClassLoader extends ClassLoader {
private String classPath;
private String className; public MyClassLoader(String classPath, String className) {
this.classPath = classPath;
this.className = className;
} @Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = getData();
try {
String string = new String(data, "utf-8");
System.out.println(string);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} return defineClass(className,data,0,data.length);
} private byte[] getData(){
String path = classPath; try {
FileInputStream inputStream = new FileInputStream(path);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[2048];
int num = 0;
while ((num = inputStream.read(bytes)) != -1){
byteArrayOutputStream.write(bytes, 0,num);
} return byteArrayOutputStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} return null;
}
}
使用方法就像下面这样:
MyClassLoader classLoader = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass = classLoader.findClass(className);
2、实验1:应用默认加载器 && 自定义加载器
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; /**
* desc:
*
* @author : caokunliang
* creat_date: 2019/6/14 0014
* creat_time: 17:04
**/
public class MainTest {
public static void testMyClassLoaderAndAppClassloader()throws Exception{
// TestSample类由sun.misc.Launcher$AppClassLoader 加载,那么 printClassLoader 需要的参数类型应该也是 Launcher$AppClassLoader加载的TestSample类型
// 而这里的 sample 正好满足,所以可以成功
TestSample sample = new TestSample();
sample.printClassLoader(sample); String className = "TestSample";
MyClassLoader classLoader = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass = classLoader.findClass(className);
Object instance = loadClass.newInstance();
// 查看是否能赋值
System.out.println(sample.getClass().isAssignableFrom(loadClass) );
//1 error: 这里会报错哦
TestSample instance1 = (TestSample) instance;
sample.printClassLoader(instance1); } public static void main(String[] args) throws Exception {
testMyClassLoaderAndAppClassloader();
}
}
执行结果如下,在上图1处,会报错,错误为转型错误:
[Loaded TestSample from __JVM_DefineClass__]
Exception in thread "main" java.lang.ClassCastException: TestSample cannot be cast to TestSample
at MainTest.testMyClassLoaderAndAppClassloader(MainTest.java:25)
at MainTest.main(MainTest.java:48)
这里可以看出来,不同类加载器加载的类,即使是同一个类,也是不兼容的。因为这个例子中,一个是由Launcher$AppClassLoader加载,一个是自定义加载器加载的。
下面,我们将进一步验证这个结论。
3、实验2:自定义加载器 && 自定义加载器 (不同实例)
实验 3-1:
public static void testMyClassLoaderAndAnotherMyClassLoader() throws Exception { String className = "TestSample";
MyClassLoader classLoader = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass = classLoader.findClass(className);
Object instance = loadClass.newInstance(); MyClassLoader classLoader1 = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass1 = classLoader1.findClass(className);
Object instance1 = loadClass1.newInstance();
// 1
Method method = instance.getClass().getMethod("printClassLoader", new Class[]{TestSample.class});
method.invoke(instance,instance); }
上图1处,会报错,报错如下,原因是TestSample.class 默认在classpath下,由应用类加载器加载,而 instance 是由 classLoader 加载的,参数类型因此不匹配:
Exception in thread "main" java.lang.NoSuchMethodException: TestSample.printClassLoader(TestSample)
at java.lang.Class.getMethod(Class.java:1786)
at MainTest.testMyClassLoaderAndAnotherMyClassLoader(MainTest.java:43)
at MainTest.main(MainTest.java:49)
实验 3-2:
(改动仅标红处)
public static void testMyClassLoaderAndAnotherMyClassLoader() throws Exception { String className = "TestSample";
MyClassLoader classLoader = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass = classLoader.findClass(className);
Object instance = loadClass.newInstance(); MyClassLoader classLoader1 = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass1 = classLoader1.findClass(className);
Object instance1 = loadClass1.newInstance(); Method method = instance.getClass().getMethod("printClassLoader", new Class[]{loadClass});
method.invoke(instance,instance); }
可以正常执行,结果为:
[Loaded TestSample from __JVM_DefineClass__]
MyClassLoader@41a4555e
实验3-3:
public static void testMyClassLoaderAndAnotherMyClassLoader() throws Exception { String className = "TestSample";
MyClassLoader classLoader = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass = classLoader.findClass(className);
Object instance = loadClass.newInstance(); MyClassLoader classLoader1 = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass1 = classLoader1.findClass(className);
Object instance1 = loadClass1.newInstance(); Method method = instance.getClass().getMethod("printClassLoader", new Class[]{loadClass1});
method.invoke(instance,instance); }
报错,错误和实验3-1差不多:
Exception in thread "main" java.lang.NoSuchMethodException: TestSample.printClassLoader(TestSample)
at java.lang.Class.getMethod(Class.java:1786)
at MainTest.testMyClassLoaderAndAnotherMyClassLoader(MainTest.java:43)
at MainTest.main(MainTest.java:49)
实验3-4:
public static void testMyClassLoaderAndAnotherMyClassLoader() throws Exception { String className = "TestSample";
MyClassLoader classLoader = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass = classLoader.findClass(className);
Object instance = loadClass.newInstance(); MyClassLoader classLoader1 = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
Class<?> loadClass1 = classLoader1.findClass(className);
Object instance1 = loadClass1.newInstance(); Method method = instance.getClass().getMethod("printClassLoader", new Class[]{loadClass});
method.invoke(instance,instance1); }
此时报错和前面不同:
Exception in thread "main" java.lang.IllegalArgumentException: argument type mismatch
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at MainTest.testMyClassLoaderAndAnotherMyClassLoader(MainTest.java:44)
at MainTest.main(MainTest.java:49)
好了,做了这么多实验,想必大概都了解了吧,参数类型不只是完全限定类名要一致,而且还需要类加载器一致才行。
简单的参数传递,实际上隐藏了如此之多的东西。参数要传对,看来不能拼人品啊,还是得靠知识。
三、关于Tomcat 中类加载器的思考
不知道看完了上面的实验,大家有没有想到一个问题,在我们的servlet 开发中,servlet-api.jar 包默认是由 tomcat 提供的,意思也就是,servlet-api.jar中的类应该都是由 tomcat 的common 类加载器加载的。(这个早已验证,可翻我之前的博客)
servlet-api.jar包中,有很多类,大家肯定用过 javax.servlet.Filter#doFilter :
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
我们思考一个问题,假如 我们在我们 web-inf/lib下,自己放上一个 servlet-api.jar,那么加载 web-inf/lib 的自然就是 webappClassloader,那么加载我们的filter的,也就是 webappClassloader。那么我们的filter的参数,默认就应该只接受 webappclassloader 加载的 ServletRequest 、ServletResponse 类。
但是,很显然,因为 Tomcat 的lib下面也有 servlet-api.jar,给我们的filter 传递的 reqeust参数,应该是由其 自己的common 类加载器加载的,问题来了,这样还能调用成功我们的 filter 方法吗?按理说,不可能,应该会报一个参数类型不匹配的错误才对,因为上一章的实验结果就摆在那里。
那就再测试一次吧,事实胜于雄辩,首先,我们将复用第一章的例子的servlet,唯一要改的,只是pom.xml(注释了provided那行):
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
5 <!--<scope>provided</scope>-->
</dependency>
maven打包部署到tomcat,我们启动Tomcat时,可以在catalina.sh/bat 中加一个参数:-XX:+TraceClassLoading,启动后,访问我们的 MyServlet,并没有什么异常(大家可以试试)。
然后我看了下,servletRequest等class,到底从哪加载的,下图可以看出来,都是来自 tomcat 自身的 servlet-api.jar包:
而我们的 web-inf下的 servlet-api 包,完全就是个悲剧,被忽略了啊。。。惨。。。(我要你有何用??)
而且,另外一个层面来说,运行完全没报错,说明 webapp 中 加载servlet-api.jar包的classloader 和 tomcat 加载 servlet-api.jar包的classloader 为同一个,不然早就报错了。那么意思就是说, webapp 中加载 servlet-api.jar ,其实用的 tomcat 的common 类加载器去加载。(我真的柯南附体了。。。) 反证法也可以说明这一点,因为我们在 webapp的lib 下,是可以不放 servlet-api.jar包的,jar包只在 tomcat 有,而 webapp 的类加载器又不能去加载 tomcat 的东西,所以,只能说: webapp 类加载器委托了 tomcat 帮他加载。
我们可以看看 webappclassloader 的实现,我本地源码版本是 tomcat 7的,不过无所谓,都差不多:
org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean):
synchronized (getClassLoadingLockInternal(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null; // (0) Check our previously loaded local class cache // 检查本加载器是否加载过了,本地有个map
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
} // (0.1) Check our previously loaded class cache // 调用了本加载器的本地方法,查看是否加载过了
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 // 先交给 扩展类加载器,免得把 jre/ext下面的类自己加载了出大事
// the webapp from overriding J2SE classes
try {
clazz = j2seClassLoader.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 这个不管,我们这边是null
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);
}
}
}
// 000
boolean delegateLoad = delegate || filter(name); //默认为false,可以配置,如果为true,表示应该交给 tomcat 的common类加载器先加载 // (1) Delegate to our parent if requested
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + 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
}
} // (2) Search local repositories // 如果 tomcat 的common类加载器 加载失败,则有自己加载
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 // 如果自己加载失败了,别说了,都甩给 tomcat 的common类加载器吧
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
}
}
说下上面的000处,这里filter(name),会判断要加载的类,是否是javax.servlet这样的包名,比如,servlet-api.jar包的类,就满足这里的条件:
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;
}
简单归纳下:
1、webappclassloader 加载时,先看本加载器的缓存,看看是否加载过了,加载过了直接返回,否则进入2;
2、先给 jdk 的jre/ext 类加载器加载, jre/ext 如果加载不了,会丢给 Bootstrap 加载器,如果加载到了,则返回,否则进入3;
3、判断delegate 属性,如果为true,则进入3.1,为false,则进入 3.2
3.1 如果要加载的类,包名大概是javax.servlet开头,delegate会为true,就会丢给tomcat 的common 类加载器,加载成功则返回,否则本加载器真正尝试加载,成功则返回,否则抛异常:加载失败。
3.2 先让自己类加载器尝试,成功则返回,否则丢给 tomcat 加载,成功则返回,否则抛异常:加载失败。
四、总结
对象,由类生成,类,由类加载器加载而来。 对象的方法参数的类型,也和类加载器息息相关, 这个参数是 类加载器 A 加载的class B类型,你必须也传一个这样的给我,我才认啊。
举个例子,假设你先后有过两个女朋友,前女友给你送了个iphone 8,现女友也送了你一个iphone 8, 这两个iphone 8 都是同一个地方买的,那这两个iPhone 8 能一样吗?要不问问你现女友去?
所以说啊,java这东西,他么的易学难精。。。继续努力吧。 下篇可以写写热部署、OSGI的问题,(半桶水,我自己也要去研究下,哈哈)。。
还是Tomcat,关于类加载器的趣味实验的更多相关文章
- Tomcat内核之Tomcat的类加载器
跟其他主流的Java Web服务器一样,Tomcat也拥有不同的自定义类加载器,达到对各种资源库的控制.一般来说,Java Web服务器需要解决以下四个问题: ① 同一个Web服务器里,各个Web ...
- Tomcat的类加载器
看完了Java类装载器,我们再来看看应用服务器(Tomcat)对类加载器的使用,每个应用服务器都有一套自己的类加载器体系,从而与Java的类加载器区别开以达到自己与应用程序隔离的目的.Tomcat的类 ...
- 细说tomcat之类加载器
官网:http://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.htmlJava类加载与Tomcat类加载器层级关系对比 Java Clas ...
- 实战分析Tomcat的类加载器结构(使用Eclipse MAT验证)
一.前言 在各种Tomcat相关书籍,书上都提到了其类加载器结构: 在Tomcat 7或者8中,共享类和Catalina类加载器在catalina.properties中都是没配置的,请看: 所以,c ...
- 不吹不黑,关于 Java 类加载器的这一点,市面上没有任何一本图书讲到
类加载器第7弹: 实战分析Tomcat的类加载器结构(使用Eclipse MAT验证) 还是Tomcat,关于类加载器的趣味实验 了不得,我可能发现了Jar 包冲突的秘密 重写类加载器,实现简单的热替 ...
- 【JRebel 作者出品--译文】Java class 热更新:关于对象,类,类加载器
一篇大神的译文,勉强(嗯..相当勉强)地放在类加载器系列吧,第8弹: 实战分析Tomcat的类加载器结构(使用Eclipse MAT验证) 还是Tomcat,关于类加载器的趣味实验 了不得,我可能发现 ...
- Java类加载机制与Tomcat类加载器架构
Java类加载机制 类加载器 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这 ...
- Tomcat源码分析 (五)----- Tomcat 类加载器
在研究tomcat 类加载之前,我们复习一下或者说巩固一下java 默认的类加载器.楼主以前对类加载也是懵懵懂懂,借此机会,也好好复习一下. 楼主翻开了神书<深入理解Java虚拟机>第二版 ...
- Tomcat 类加载器的实现
Tomcat 内部定义了多个 ClassLoader,以便应用和容器访问不同存储库中的类和资源,同时达到应用间类隔离的目的.本文首发于公众号:顿悟源码. 1. Java 类加载机制 类加载就是把编译生 ...
随机推荐
- htmlunit爬取js异步加载后的页面
直接上代码: 一. index.html 调用后台请求获取content中的内容. <html> <head> <script type="text/javas ...
- abp viewmodel的写法
我的写法 public class QuotaCreateOrEditViewModel { public QuotaDto LoanQuota { get; set; } public bool I ...
- Android 使用 adb命令 远程安装apk
Android 使用 adb命令 远程安装apk ./adb devices 列出所有设备 ./adb connect 192.168.1.89 连接到该设备 ./adb logcat 启动logca ...
- CVE-2010-3333
环境 windows xp sp3 office 2003 sp0 windbg ollydbg vmware 12.0 0x00 RTF格式 RTF是Rich TextFormat的缩写,意即富文本 ...
- shell脚本,awk 根据文件某列去重并且统计该列频次。
a文件为 a a a s s d .怎么把a文件变为 a s d .怎么把a文件变为 a a a s s d 解题方法如下: 解题思路 [root@localhost study]# awk 'NR= ...
- 【搜索 ex-BFS】bzoj2346: [Baltic 2011]Lamp
关于图中边权非零即一的宽度优先搜索 Description 译自 BalticOI 2011 Day1 T3「Switch the Lamp On」有一种正方形的电路元件,在它的两组相对顶点中,有一组 ...
- CSS3-transform3D
CSS3 3D位移 在CSS3中3D位移主要包括两种函数translateZ()和translate3d().translate3d()函数使一个元素在三维空间移动.这种变形的特点是,使用三维向量的坐 ...
- 条款39:明智而审慎地使用private继承(use private inheritance judiciously)
NOTE: 1.private 继承意味 is-implemented-in-terms-of(根据某物实现出).它通常比复合(composition)的级别低.但是当derivated class需 ...
- python-数字类型内置方法
数字类型内置方法 为什么要有数据类型? 数据是用来表示状态的,不同的状态就应该用不同的数据类型去表示 整型(int) 用途:年龄.号码.银行卡号等 定义:可以使用int()方法将纯数字的字符串转换为十 ...
- 关于set和multiset的一些用法
set的一些用法 set的特性 set的特性是,所有元素都会根据元素的键值自动排序,set不允许两个元素有相同的键值. set的一些常用操作函数 insert() insert(key_value); ...