tomcat源码分析(5) ParallelWebappClassLoader类加载器

java的类加载器我们这里就不做详细讲解了,百度一堆(启动类加载器BootstrapClassLoader,扩展类加载器ExtensionClassLoader,系统类加载器App ClassLoader,线程上下文类加载器),我们这里主要讲一下tomcat自定义类加载ParallelWebappClassLoader如何做到webapps(Host)中不同的文件夹(context)的类隔离

1.创建TestTomcatClassLoader 类,路径C:/Users/paul/Desktop/ROOT为tomcat默认的ROOT站点

public class TestTomcatClassLoader {
    public static void main(String[] args) throws Exception {
        Tomcat tomcat = new Tomcat();
        tomcat.setPort(8080);
        tomcat.setBaseDir("/tmp/tomcat");
        tomcat.addWebapp("", "C:\\Users\\paul\\Desktop\\ROOT");
        tomcat.start();
        tomcat.getServer().await();
    }
}

2.在TestTomcatClassLoader 工程的classpath中创建两个类Hello和HelloServlet,这2个类编译后System.out.println 为CLASSPATH放到与EmbedTomcatHttp同项目中,System.out.println 为ROOT放到C:/Users/paul/Desktop/ROOT/WEB-INF/classes/test目录下

package test;
public class Hello {
    public void print() {
        System.out.println("=======类加载器  CLASSPATH");
      //System.out.println("=======类加载器  ROOT");
        System.out.println(this.getClass().getClassLoader());
        //System.out.println(Thread.currentThread().getContextClassLoader());
    }
}

package test;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloServlet extends GenericServlet{
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException var6) {
            throw new ServletException("non-HTTP request or response");
        }
        response.setStatus(200);
        response.getWriter().append("Hello World...CLASSPATH");
        //response.getWriter().append("Hello World...ROOT");
        new Hello().print();
    }
}

3.配置ROOT/WEB-INF/web.xml 如下:

 
    HelloServlet
    test.HelloServlet


    HelloServlet
    /hello/*

4.访问http://127.0.0.1:8080/hello
返回:Hello World...ROOT ,控制台打印:

=======类加载器  ROOT
ParallelWebappClassLoader
  context: ROOT
  delegate: false
----------> Parent Classloader:sun.misc.Launcher$AppClassLoader@14dad5dc

CLASSPATH中的test.Hello 为A1 ,test.HelloServlet 为B1,
C:/Users/paul/Desktop/ROOT/WEB-INF/classes/ 中的test.Hello 为A2 ,test.HelloServlet 为B2

A1 A2 B1 B2 浏览器响应 控制台
Y Y Y Y ? ?
Y N Y N ? ?
N Y N Y ? ?
Y N N Y ? ?
N Y Y N ? ?

(读者可以组合查看结果,理解类加载器)

下面详细给大家讲解ParallelWebappClassLoader 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
            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache
            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
            //       the webapp from overriding Java SE classes. This implements
            //       SRV.10.7.2
            String resourceName = binaryNameToPath(name, false);
            
            ClassLoader javaseLoader = getJavaseClassLoader();//ExtensionClassLoader
            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);
                    }
                }
            }

            boolean delegateLoad = delegate || filter(name, true);

            // (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
            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);
    }

new Hello().print();加载流程:


Hello类加载流程.png

1.由于classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入,HelloServlet是由WebappClassLoaderBase加载的,那么Hello也由WebappClassLoaderBase,可以自行打断点验证
2.findLoadedClass0("test.Hello")查看当前类加载器resourceEntries是否缓存

protected Class findLoadedClass0(String name) {
        String path = binaryNameToPath(name, true);
        ResourceEntry entry = resourceEntries.get(path);
        if (entry != null) {
            return entry.loadedClass;
        }
        return null;
    }

3.findLoadedClass("test.Hello") 从native方法中查看类缓存

protected final Class findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }
private native final Class findLoadedClass0(String name);

4.ClassLoader javaseLoader = getJavaseClassLoader() 得到的sun.misc.LauncherExtClassLoader ,防止覆盖rt.jar和ext的扩展jar 5.boolean delegateLoad = delegate || filter(name, true) 对于特定名称的类需要由父类加载器加载(sun.misc.LauncherAppClassLoader)clazz = Class.forName(name, false, parent);
比如以javax开头的类
6.clazz = findClass(name); 由当前类加载WebappClassLoaderBase加载,从/WEB-INF/classes/test/Hello.class进行查找文件将文件放入byte[],transformer.transform()进行插桩改造byte[],最终defineClass生成class
7.WebappClassLoaderBase加载不到的类由父类加载器AppClassLoader 记载clazz = Class.forName(name, false, parent);
8.throw new ClassNotFoundException(name); 第7步中加载不到就抛异常啦

你可能感兴趣的:(tomcat源码分析(5) ParallelWebappClassLoader类加载器)