zoukankan      html  css  js  c++  java
  • 还是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:

     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 已经默认置空了。 为什么留空的原因,这里先不详细讲述。(因为我也不完全懂啊,哈哈哈)

    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:
     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,是完全没问题的。

    二、可怕的参数传递实验

    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 的参数吗?

    开始之前,先准备好我们自定义的类加载器,

     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);

    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那行):

    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          // 000
     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             }

    说下上面的000处,这里filter(name),会判断要加载的类,是否是javax.servlet这样的包名,比如,servlet-api.jar包的类,就满足这里的条件:

     1 protected boolean filter(String name, boolean isClassName) {
     2 
     3         if (name == null)
     4             return false;
     5 
     6         char ch;
     7         if (name.startsWith("javax")) {
     8             /* 5 == length("javax") */
     9             if (name.length() == 5) {
    10                 return false;
    11             }
    12             ch = name.charAt(5);
    13             if (isClassName && ch == '.') {
    14                 /* 6 == length("javax.") */
    15                 if (name.startsWith("servlet.jsp.jstl.", 6)) {
    16                     return false;
    17                 }
    18                 if (name.startsWith("el.", 6) ||
    19                     name.startsWith("servlet.", 6) ||
    20                     name.startsWith("websocket.", 6) ||
    21                     name.startsWith("security.auth.message.", 6)) {
    22                     return true;
    23                 }
    24             } else if (!isClassName && ch == '/') {
    25                 /* 6 == length("javax/") */
    26                 if (name.startsWith("servlet/jsp/jstl/", 6)) {
    27                     return false;
    28                 }
    29                 if (name.startsWith("el/", 6) ||
    30                     name.startsWith("servlet/", 6) ||
    31                     name.startsWith("websocket/", 6) ||
    32                     name.startsWith("security/auth/message/", 6)) {
    33                     return true;
    34                 }
    35             }
    36         } else if (name.startsWith("org")) {
    37             /* 3 == length("org") */
    38             if (name.length() == 3) {
    39                 return false;
    40             }
    41             ch = name.charAt(3);
    42             if (isClassName && ch == '.') {
    43                 /* 4 == length("org.") */
    44                 if (name.startsWith("apache.", 4)) {
    45                     /* 11 == length("org.apache.") */
    46                     if (name.startsWith("tomcat.jdbc.", 11)) {
    47                         return false;
    48                     }
    49                     if (name.startsWith("el.", 11) ||
    50                         name.startsWith("catalina.", 11) ||
    51                         name.startsWith("jasper.", 11) ||
    52                         name.startsWith("juli.", 11) ||
    53                         name.startsWith("tomcat.", 11) ||
    54                         name.startsWith("naming.", 11) ||
    55                         name.startsWith("coyote.", 11)) {
    56                         return true;
    57                     }
    58                 }
    59             } else if (!isClassName && ch == '/') {
    60                 /* 4 == length("org/") */
    61                 if (name.startsWith("apache/", 4)) {
    62                     /* 11 == length("org/apache/") */
    63                     if (name.startsWith("tomcat/jdbc/", 11)) {
    64                         return false;
    65                     }
    66                     if (name.startsWith("el/", 11) ||
    67                         name.startsWith("catalina/", 11) ||
    68                         name.startsWith("jasper/", 11) ||
    69                         name.startsWith("juli/", 11) ||
    70                         name.startsWith("tomcat/", 11) ||
    71                         name.startsWith("naming/", 11) ||
    72                         name.startsWith("coyote/", 11)) {
    73                         return true;
    74                     }
    75                 }
    76             }
    77         }
    78         return false;
    79     }
    View Code

    简单归纳下:

    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的问题,(半桶水,我自己也要去研究下,哈哈)。。

  • 相关阅读:
    ZooKeeper 到底解决了什么问题?
    10个 Linux 命令,让你的操作更有效率
    你的镜像真的安全吗?
    谁动了我的主机? 之活用History命令
    Linux vs Unix
    Linux应急响应入门——入侵排查
    (一)Sharding-JDBC分库分表概述
    (三)微服务的注册中心—Eureka
    (二)springcloud功能组件
    (一)微服务基础
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/11017253.html
Copyright © 2011-2022 走看看