仍是Tomcat,關於類加載器的趣味實驗

1、前言

類加載器,實際上是很複雜一個東西,想等到我徹底什麼都弄明白了再寫出來,估計不太現實。。。如今只能是知道多少寫多少吧。html

首先,我提一個問題:在咱們本身的servlet中(好比ssm中,controller的代碼),能夠訪問 tomcat 安裝目錄下 lib 中的類嗎?(servlet-api.jar包中的不算)java

好好思考一下再回答。若是你說不能夠,那可能接下來會有點尷尬。。。web

 

2、測試

一、tomcat 類加載器結構複習

我們看圖說話,應用程序類加載器,主要加載classpath路徑下的類,在tomcat 的啓動腳本里,最終會設置爲 bin 目錄下的bootstrap.jar 和tomcat-juli.jar:spring

 

 common類加載器主要用於加載 tomcat 中間件自身、webapp 均可以訪問的類;apache

 catalina 類加載器,主要用於加載 tomcat 自身的類, webapp 不能訪問;bootstrap

 共享類(shared)類加載器, 主要是用於加載 webapp 共享的類,好比你們都用 spring 開發,該類加載器的初衷就是加載 共用的 spring 相關的jar包。 api

這三者的加載路徑,能夠查看 Tomcat (我這邊是Tomcat 8)安裝目錄下,conf / catalina.properties:緩存

 

 1 #
 2 #
 3 # List of comma-separated paths defining the contents of the "common"
 4 # classloader. Prefixes should be used to define what is the repository type.
 5 # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
 6 # If left as blank,the JVM system loader will be used as Catalina's "common"
 7 # loader.
 8 # Examples:
 9 #     "foo": Add this folder as a class repository
10 #     "foo/*.jar": Add all the JARs of the specified folder as class
11 #                  repositories
12 #     "foo/bar.jar": Add bar.jar as a class repository
13 #
14 # Note: Values are enclosed in double quotes ("...") in case either the
15 #       ${catalina.base} path or the ${catalina.home} path contains a comma.
16 #       Because double quotes are used for quoting, the double quote character
17 #       may not appear in a path.
18 common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
19 
20 #
21 # List of comma-separated paths defining the contents of the "server"
22 # classloader. Prefixes should be used to define what is the repository type.
23 # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
24 # If left as blank, the "common" loader will be used as Catalina's "server"
25 # loader.
26 # Examples:
27 #     "foo": Add this folder as a class repository
28 #     "foo/*.jar": Add all the JARs of the specified folder as class
29 #                  repositories
30 #     "foo/bar.jar": Add bar.jar as a class repository
31 #
32 # Note: Values may be enclosed in double quotes ("...") in case either the
33 #       ${catalina.base} path or the ${catalina.home} path contains a comma.
34 #       Because double quotes are used for quoting, the double quote character
35 #       may not appear in a path.
36 server.loader=
37 
38 #
39 # List of comma-separated paths defining the contents of the "shared"
40 # classloader. Prefixes should be used to define what is the repository type.
41 # Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
42 # the "common" loader will be used as Catalina's "shared" loader.
43 # Examples:
44 #     "foo": Add this folder as a class repository
45 #     "foo/*.jar": Add all the JARs of the specified folder as class
46 #                  repositories
47 #     "foo/bar.jar": Add bar.jar as a class repository
48 # Please note that for single jars, e.g. bar.jar, you need the URL form
49 # starting with file:.
50 #
51 # Note: Values may be enclosed in double quotes ("...") in case either the
52 #       ${catalina.base} path or the ${catalina.home} path contains a comma.
53 #       Because double quotes are used for quoting, the double quote character
54 #       may not appear in a path.
55 shared.loader=

 

可是,應該是從 tomcat 7開始, common.loader 和 shared.loader 已經默認置空了。 爲何留空的緣由,這裏先不詳細講述。(由於我也不徹底懂啊,哈哈哈)tomcat

 

Webapp 類加載器就不用說了, 主要是加載自身目錄下的 WEB-INF/classes、 WEB-INF/lib 中的類。app

對這部分感興趣的,能夠再看看個人另外一篇文章:實戰分析Tomcat的類加載器結構(使用Eclipse MAT驗證)

 

咱們再回頭看看,文章開頭的圖裏,清晰地展現了: webapp的類加載器的parent,即爲 common 類加載器。 那麼,只要咱們在 業務代碼裏進行以下調用,應該就獲取到了 common 類加載器,因而就能夠愉快地加載 Tomcat 安裝目錄下的 lib目錄的jar了:

        ClassLoader classLoader = this.getClass().getClassLoader();
        ClassLoader directparent = classLoader.getParent();

 

二、驗證程序

我這邊建了個簡單的web程序,只有一個servlet。

MyServlet .java:
 1 import javax.servlet.*;
 2 import java.io.IOException;
 3 import java.lang.reflect.InvocationTargetException;
 4 import java.lang.reflect.Method;
 5 import java.net.URL;
 6 import java.net.URLClassLoader;
 7 
15 public class MyServlet implements Servlet {
16     @Override
17     public void init(ServletConfig config) throws ServletException {
18 
19     }
20 
21     @Override
22     public ServletConfig getServletConfig() {
23         return null;
24     }
25 
26 
27     @Override
28     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
29         ClassLoader classLoader = this.getClass().getClassLoader();
30         System.out.println("當前類加載器(webapp加載器):" + classLoader);
31         printPath(classLoader);
32 
33         ClassLoader directparent = classLoader.getParent();
34         System.out.println("父加載器(tomcat 自身的加載器):" + directparent);
35         printPath(directparent);
36 
37         // 從父加載器開始循環,應該會按順序取到:應用類加載器--ext類加載器--bootstrap加載器
38         classLoader = directparent;
39         while (classLoader != null){
40             ClassLoader parent = classLoader.getParent();
41             System.out.println("當前類加載器爲:" + parent);
42             printPath(parent);
43             classLoader = parent;
44         }
45 
46         if (directparent != null) {
47             try {
48                 Class<?> loadClass = directparent.loadClass("org.apache.catalina.core.StandardEngine");
49                 Object instance = loadClass.newInstance();
50                 Method[] methods = loadClass.getMethods();
51                 System.out.println("如下爲StandardEngine的全部方法.................");
52                 for (Method method : methods) {
53                     System.out.println(method);
54                 }
55 
56                 System.out.println("反射調用方法測試............................");
57                 Method getDefaultHostMethod = loadClass.getMethod("getDefaultHost");
58                 Object result = getDefaultHostMethod.invoke(instance);
59                 System.out.println("before:" + result);
60 
61                 Method setDefaultHostMethod = loadClass.getMethod("setDefaultHost", String.class);
62                 setDefaultHostMethod.invoke(instance,"hahaha...");
63 
64                 Object afterResult = getDefaultHostMethod.invoke(instance);
65                 System.out.println("after:" + afterResult);
66 
67             } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) {
68                 e.printStackTrace();
69             }
70         }
71     }
72 
73     private void printPath(ClassLoader directparent) {
74         if (directparent instanceof URLClassLoader){
75             URLClassLoader urlClassLoader = (URLClassLoader) directparent;
76             URL[] urLs = urlClassLoader.getURLs();
77             for (URL urL : urLs) {
78                 System.out.println(urL);
79             }
80         }
81     }
82 
83     @Override
84     public String getServletInfo() {
85         return null;
86     }
87 
88     @Override
89     public void destroy() {
90 
91     }
92 }

 

加入到 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:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 
 3 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5   <modelVersion>4.0.0</modelVersion>
 6 
 7   <groupId>com.ckl</groupId>
 8   <artifactId>tomcatclassloader</artifactId>
 9   <version>1.0-SNAPSHOT</version>
10   <packaging>war</packaging>
11 
12   <name>tomcatclassloader Maven Webapp</name>
13   <!-- FIXME change it to the project's website -->
14   <url>http://www.example.com</url>
15 
16   <properties>
17     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
18     <maven.compiler.source>1.7</maven.compiler.source>
19     <maven.compiler.target>1.7</maven.compiler.target>
20   </properties>
21 
22   <dependencies>
23 
24       <dependency>
25           <groupId>javax.servlet</groupId>
26           <artifactId>javax.servlet-api</artifactId>
27           <version>3.1.0</version>
28           <scope>provided</scope>
29       </dependency>
30 
31   </dependencies>
32 
33   <build>
34     <finalName>tomcatclassloader</finalName>
35     <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
36       <plugins>
37         <plugin>
38           <artifactId>maven-clean-plugin</artifactId>
39           <version>3.1.0</version>
40         </plugin>
41         <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
42         <plugin>
43           <artifactId>maven-resources-plugin</artifactId>
44           <version>3.0.2</version>
45         </plugin>
46         <plugin>
47           <artifactId>maven-compiler-plugin</artifactId>
48           <version>3.8.0</version>
49         </plugin>
50         <plugin>
51           <artifactId>maven-surefire-plugin</artifactId>
52           <version>2.22.1</version>
53         </plugin>
54         <plugin>
55           <artifactId>maven-war-plugin</artifactId>
56           <version>3.2.2</version>
57         </plugin>
58         <plugin>
59           <artifactId>maven-install-plugin</artifactId>
60           <version>2.5.2</version>
61         </plugin>
62         <plugin>
63           <artifactId>maven-deploy-plugin</artifactId>
64           <version>2.8.2</version>
65         </plugin>
66       </plugins>
67     </pluginManagement>
68   </build>
69 </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,是徹底沒問題的。

 

2、可怕的參數傳遞實驗

一、實驗思路

不太好描述,直接碼,咱們首先定義一個測試類,

# 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下的類都由該類加載。

咱們要作的試驗有兩個:

一、若是傳入的參數對象,由另一個類加載器加載的,能調用成功嗎,若是成功,結果是什麼?

二、若是由兩個相同類加載器的不一樣實例,來加載 TestSample ,而後反射獲取對象,那麼其中一個能做爲另外一個對象的 printClassLoader 的參數嗎?

 

開始以前,先準備好咱們自定義的類加載器,

 1 import java.io.ByteArrayOutputStream;
 2 import java.io.FileInputStream;
 3 import java.io.UnsupportedEncodingException;
 4 
 5 /**
 6  * desc:
 7  *
 8  * @author : caokunliang
 9  * creat_date: 2019/6/13 0013
10  * creat_time: 10:19
11  **/
12 public class MyClassLoader extends ClassLoader {
13     private String classPath;
14     private String className;
15 
16 
17     public MyClassLoader(String classPath, String className) {
18         this.classPath = classPath;
19         this.className = className;
20     }
21 
22     @Override
23     protected Class<?> findClass(String name) throws ClassNotFoundException {
24         byte[] data = getData();
25         try {
26             String string = new String(data, "utf-8");
27             System.out.println(string);
28         } catch (UnsupportedEncodingException e) {
29             e.printStackTrace();
30         }
31 
32         return defineClass(className,data,0,data.length);
33     }
34 
35     private byte[] getData(){
36         String path = classPath;
37 
38         try {
39             FileInputStream inputStream = new FileInputStream(path);
40             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
41             byte[] bytes = new byte[2048];
42             int num = 0;
43             while ((num = inputStream.read(bytes)) != -1){
44                 byteArrayOutputStream.write(bytes, 0,num);
45             }
46 
47             return byteArrayOutputStream.toByteArray();
48         } catch (Exception e) {
49             e.printStackTrace();
50         }
51 
52         return null;
53     }
54 }

 

使用方法就像下面這樣: 

        MyClassLoader classLoader = new MyClassLoader("F:\\\\ownprojects\\\\test\\\\out\\\\TestSample.class", className);
        Class<?> loadClass = classLoader.findClass(className);

 

二、實驗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) ); // error: 這裏會報錯哦 TestSample instance1 = (TestSample) instance; sample.printClassLoader(instance1); } public static void main(String[] args) throws Exception { testMyClassLoaderAndAppClassloader(); } }

 

執行結果以下,在上圖標紅行,會報錯,錯誤爲轉型錯誤:

[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加載,一個是自定義加載器加載的。

下面,咱們將進一步驗證這個結論。

 

三、實驗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();

        Method method = instance.getClass().getMethod("printClassLoader", new Class[]{TestSample.class});
        method.invoke(instance,instance);

    }

 

上圖紅色處,會報錯,報錯以下,緣由是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)

 

好了,作了這麼多實驗,想必大概都瞭解了吧,參數類型不僅是徹底限定類名要一致,並且還須要類加載器一致才行。

簡單的參數傳遞,實際上隱藏瞭如此之多的東西。參數要傳對,看來不能拼人品啊,仍是得靠知識。

 

3、關於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那行):

1       <dependency>
2           <groupId>javax.servlet</groupId>
3           <artifactId>javax.servlet-api</artifactId>
4           <version>3.1.0</version>
5           <!--<scope>provided</scope>-->
6       </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):

  1 synchronized (getClassLoadingLockInternal(name)) {
  2             if (log.isDebugEnabled())
  3                 log.debug("loadClass(" + name + ", " + resolve + ")");
  4             Class<?> clazz = null;
  5     
  6     
  7             // (0) Check our previously loaded local class cache   // 檢查本加載器是否加載過了,本地有個map
  8             clazz = findLoadedClass0(name);
  9             if (clazz != null) {
 10                 if (log.isDebugEnabled())
 11                     log.debug("  Returning class from cache");
 12                 if (resolve)
 13                     resolveClass(clazz);
 14                 return (clazz);
 15             }
 16     
 17             // (0.1) Check our previously loaded class cache  // 調用了本加載器的本地方法,查看是否加載過了
 18             clazz = findLoadedClass(name);
 19             if (clazz != null) {
 20                 if (log.isDebugEnabled())
 21                     log.debug("  Returning class from cache");
 22                 if (resolve)
 23                     resolveClass(clazz);
 24                 return (clazz);
 25             }
 26     
 27             // (0.2) Try loading the class with the system class loader, to prevent  // 先交給 擴展類加載器,省得把 jre/ext下面的類本身加載了出大事  28             //       the webapp from overriding J2SE classes
 29             try {
 30                 clazz = j2seClassLoader.loadClass(name);
 31                 if (clazz != null) {
 32                     if (resolve)
 33                         resolveClass(clazz);
 34                     return (clazz);
 35                 }
 36             } catch (ClassNotFoundException e) {
 37                 // Ignore
 38             }
 39     
 40             // (0.5) Permission to access this class when using a SecurityManager  這個無論,咱們這邊是null
 41             if (securityManager != null) {
 42                 int i = name.lastIndexOf('.');
 43                 if (i >= 0) {
 44                     try {
 45                         securityManager.checkPackageAccess(name.substring(0,i));
 46                     } catch (SecurityException se) {
 47                         String error = "Security Violation, attempt to use " +
 48                             "Restricted Class: " + name;
 49                         log.info(error, se);
 50                         throw new ClassNotFoundException(error, se);
 51                     }
 52                 }
 53             }
 54     
 55             boolean delegateLoad = delegate || filter(name);  //默認爲false,能夠配置,若是爲true,表示應該交給 tomcat 的common類加載器先加載  56     
 57             // (1) Delegate to our parent if requested
 58             if (delegateLoad) {
 59                 if (log.isDebugEnabled())
 60                     log.debug("  Delegating to parent classloader1 " + parent);
 61                 try {
 62                     clazz = Class.forName(name, false, parent);
 63                     if (clazz != null) {
 64                         if (log.isDebugEnabled())
 65                             log.debug("  Loading class from parent");
 66                         if (resolve)
 67                             resolveClass(clazz);
 68                         return (clazz);
 69                     }
 70                 } catch (ClassNotFoundException e) {
 71                     // Ignore
 72                 }
 73             }
 74     
 75             // (2) Search local repositories   // 若是 tomcat 的common類加載器 加載失敗,則有本身加載
 76             if (log.isDebugEnabled())
 77                 log.debug("  Searching local repositories");
 78             try {
 79                 clazz = findClass(name);
 80                 if (clazz != null) {
 81                     if (log.isDebugEnabled())
 82                         log.debug("  Loading class from local repository");
 83                     if (resolve)
 84                         resolveClass(clazz);
 85                     return (clazz);
 86                 }
 87             } catch (ClassNotFoundException e) {
 88                 // Ignore
 89             }
 90     
 91             // (3) Delegate to parent unconditionally  // 若是本身加載失敗了,別說了,都甩給 tomcat 的common類加載器吧
 92             if (!delegateLoad) {
 93                 if (log.isDebugEnabled())
 94                     log.debug("  Delegating to parent classloader at end: " + parent);
 95                 try {
 96                     clazz = Class.forName(name, false, parent);
 97                     if (clazz != null) {
 98                         if (log.isDebugEnabled())
 99                             log.debug("  Loading class from parent");
100                         if (resolve)
101                             resolveClass(clazz);
102                         return (clazz);
103                     }
104                 } catch (ClassNotFoundException e) {
105                     // Ignore
106                 }
107             }

 

簡單概括下:

一、webappclassloader 加載時,先看本加載器的緩存,看看是否加載過了,加載過了直接返回,不然進入2;

二、先給 jdk 的jre/ext 類加載器加載, jre/ext 若是加載不了,會丟給 Bootstrap 加載器,若是加載到了,則返回,不然進入3;

三、判斷delegate 屬性,若是爲true,則進入3.1,爲false,則進入 3.2

    3.1  丟給tomcat 的common 類加載器,加載成功則返回,不然本加載器真正嘗試加載,成功則返回,不然拋異常:加載失敗。

    3.2 先讓本身類加載器嘗試,成功則返回,不然丟給 tomcat 加載,成功則返回,不然拋異常:加載失敗。

 

 

4、總結

對象,由類生成,類,由類加載器加載而來。 對象的方法參數的類型,也和類加載器息息相關, 這個參數是 類加載器 A 加載的class B類型,你必須也傳一個這樣的給我,我才認啊。

舉個例子,假設你前後有過兩個女友,前女朋友給你送了個iphone 8,現女朋友也送了你一個iphone 8, 這兩個iphone 8 都是同一個地方買的,那這兩個iPhone 8 能同樣嗎?要不問問你現女朋友去?

 

因此說啊,java這東西,他麼的易學難精。。。繼續努力吧。 下篇能夠寫寫熱部署、OSGI的問題,(半桶水,我本身也要去研究下,哈哈)。。

相關文章
相關標籤/搜索