探 Tomcat 学 ClassLoader

版本说明

tomcat 8.5.xx
jdk 1.8.xxx

ClassLoader结构引入

这里写图片描述

  ClassLoader cl = this.getClass().getClassLoader();
  while(cl != null){
       System.out.println(cl.getClass().getCanonicalName());
       cl = cl.getParent();
  }

将会打印以下内容
org.apache.catalina.loader.ParallelWebappClassLoader
java.net.URLClassLoader
sun.misc.Launcher.AppClassLoader
sun.misc.Launcher.ExtClassLoader

为什么会打印以下的内容呢

AppClassLoader(加载系统配置项 java.class.path) 和 ExtClassLoader(加载系统配置项java.ext.dirs)是jdk自带的类加载器

ClassLoader 加载类路径
BootStrapClassLoader sun.boot.class.path(\jdk1.8.0_131\jre\lib*.jar)
ExtClassLoader java.ext.dirs(\jdk1.8.0_131\jre\lib\ext*.jar,C:\WINDOWS\Sun\Java\lib\ext*.jar)
AppClassLoader java.class.path

小提示

AppClassLoader可以在启动java程序的时候通过-classpath或者-cp参数指定

那么BootStrapClassLoader(加载rt.jar)怎么没有了?

这也是一个点

今天看了一篇博文 http://blog.csdn.net/briblue/article/details/54973413#t32

上面说BootstrapClassLoader是C++实现的

扫描二维码关注公众号,回复: 238852 查看本文章

Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,我们前面已经分析了,JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的现象。具体是什么原因,很快就知道答案了。

委托机制

先来看看类加载器的委托机制是怎么在java代码之中实现的,先来看看ClassLoaderloadClass()方法,

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

(1) 在findLoadedClass方法中,其实findLoadedClass0的一个包装方法,这个方法是一个native方法,主要作用是查看当前虚拟机中的查看此类是否已经被加载了。
(2)接下来会先从parent链中加载类,先从最顶层加载类,如果加载成功了,就返回。如果没有成功则继续加载。加入加载XXXX类并且XXXX类没有被加载,现在的ClassLoader是AppClassLoader,那么会先从BootStrapClassLoader中加载,如果没有,则会从ExtClasLoader中加载,如果还是没有才从AppClassLoader中加载,否则抛出ClassNotFundException。

以上就是委托机制的实现。

自定类加载器

那我们再接下来想一个问题,我们都知道我们的war包目录结构如下
这里写图片描述

如果我们自己手动加载一个类呢,从classes中加载和从lib中加载有什么区别呢?

从classes中加载

/**
     * 查找class 可以在这个方法之中修改逻辑,从而优先加载自己的类,
     * 当然java.lang.String包里面并不可以,因为它在jvm启动的时候已经加载过了
     *
     * @param name 类名
     *
     * @return class or null
     *
     * @throws ClassNotFoundException 类未寻找到异常
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> aClass = findLoadedClass(name);
        if (aClass != null) {
            return aClass;
        }

        if (name.startsWith(packageName)) {
            byte[] classData = getData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                return defineClass(name, classData, 0, classData.length);
            }
        }else{
            return super.loadClass(name);
        }
    }

    private byte[] getData(String name) {
        String path = classPath + File.separator + name.replace(".", File.separator)+ ".class";
        try {
            ByteArrayOutputStream result = new ByteArrayOutputStream();
            URL url = new URL(path);
            InputStream is = url.openStream();
            byte[] buffer = new byte[2048];
            int num = 0;
            while((num = is.read(buffer)) != -1){
                result.write(buffer, 0 , num);
            }
            return result.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

测试程序

        String classPath = "file:D:\\classloader\\classes";
        String className = "com.wuhulala.future.FutureExample";
        NetClassLoader cl = new NetClassLoader(classPath);
        cl.loadClass(className);

拿到类就可以根据反射做各种操作了

从jar包中加载类

没有封装,直接使用了URLClassLoader

String jarFilePath = "file:F:\\code\\javase\\java_up\\jar\\commons-beanutils-1.8.3.jar";
        URL[] urls = new URL[1];
        urls[0] = new URL(jarFilePath);
        URLClassLoader classLoader = URLClassLoader.newInstance(urls);
        Class<?> fastHashMapClazz = classLoader.loadClass("org.apache.commons.collections.FastHashMap");
        Object fastHashMap = fastHashMapClazz.newInstance();
        System.out.println(fastHashMap);

其实确实没有什么不同。。。

URLClassLoader相对于ClassLoader封装了很多有效的方法,并且继承了SecureClassLoader,具有了一定的安全性,只不过我还没有用到过这里的特性,Spring中也有相似的处理!!!

那么tomcat相对于我们自己的实现有什么高明之处呢?

tomcat类加载器实现

首先看一下tomcat的类加载器,继承了WebappClassLoaderBase ,这个Base类里面才是真正实现了类加载的功能的覆盖
public class WebappClassLoader extends WebappClassLoaderBase

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
       // 先上个锁,防止并发出现问题
        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;

            // Log access to stopped class loader
            // 判断当前类是否正在加载,和Spring中的bean创建一样
            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache
            // 判断tomcat是否已经加载过,根据resourceEntries这个缓存容器,实现采用的是ConcurrentHashMap
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                // 是否需要link
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // (0.1) Check our previously loaded class cache
            // 判断当前jvm中是否已经加载过此类呢?

            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // (0.2) Try loading the class with the system class loader, to prevent
            //       the webapp from overriding Java SE classes. This implements
            //       SRV.10.7.2
            //  尝试开始加载此类
            String resourceName = binaryNameToPath(name, false);
            // 先从BootstrapClassLoader中尝试加载
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                // Use getResource as it won't trigger an expensive
                // ClassNotFoundException if the resource is not available from
                // the Java SE class loader. However (see
                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
                // details) when running under a security manager in rare cases
                // this call may trigger a ClassCircularityError.
                // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
                // details of how this may trigger a StackOverflowError
                // Given these reported errors, catch Throwable to ensure any
                // other edge cases are also caught
                tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
            } catch (Throwable t) {
                // Swallow all exceptions apart from those that must be re-thrown
                ExceptionUtils.handleThrowable(t);
                // The getResource() trick won't work for this class. We have to
                // try loading it directly and accept that we might get a
                // ClassNotFoundException.
                tryLoadingFromJavaseLoader = true;
            }

            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // (0.5) Permission to access this class when using a SecurityManager
            // 判断是否有权限操作此类
            if (securityManager != null) {
                int i = name.lastIndexOf('.');
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        log.info(error, se);
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }

            // 判断类是否需要父类加载器,即tomcat内置的类加载器,tomcat8.5.x使用的是一个URLClassLoader去加载,待会分析以下filter方法
            boolean delegateLoad = delegate || filter(name, true);

            // (1) Delegate to our parent if requested
            // 通过父classLoader去加载
            if (delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader1 " + parent);
                try {
                    clazz = Class.forName(name, false, 
);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // (2) Search local repositories
            // 查询依照/WEB-INF/classes /WEB-INF/lib这个顺序寻找类
            if (log.isDebugEnabled())
                log.debug("  Searching local repositories");
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from local repository");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

            // (3) Delegate to parent unconditionally
            // 无条件的使用委托机制加载
            if (!delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader at end: " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }

        throw new ClassNotFoundException(name);
    }

下面解释一下为什么是按照/WEB-INF/classes和/WEB-INF/lib的顺序加载的呢?

 WebResource classes = resources.getResource("/WEB-INF/classes");
        if (classes.isDirectory() && classes.canRead()) {
            localRepositories.add(classes.getURL());
        }
        WebResource[] jars = resources.listResources("/WEB-INF/lib");
        for (WebResource jar : jars) {
            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
                localRepositories.add(jar.getURL());
                jarModificationTimes.put(
                        jar.getName(), Long.valueOf(jar.getLastModified()));
            }
        }

上面的localRepositories是一个List的数据结构

还有上面的filter方法,就是判断这些类是不是tomcat的内置了的类,我们可以看看tomcat的包目录,提一句,这些class是由StandardClassLoader加载的,再看下面代码
这里写图片描述

protected boolean filter(String name, boolean isClassName) {

        if (name == null)
            return false;

        char ch;
        if (name.startsWith("javax")) {
            /* 5 == length("javax") */
            if (name.length() == 5) {
                return false;
            }
            ch = name.charAt(5);
            if (isClassName && ch == '.') {
                /* 6 == length("javax.") */
                if (name.startsWith("servlet.jsp.jstl.", 6)) {
                    return false;
                }
                if (name.startsWith("el.", 6) ||
                    name.startsWith("servlet.", 6) ||
                    name.startsWith("websocket.", 6) ||
                    name.startsWith("security.auth.message.", 6)) {
                    return true;
                }
            } else if (!isClassName && ch == '/') {
                /* 6 == length("javax/") */
                if (name.startsWith("servlet/jsp/jstl/", 6)) {
                    return false;
                }
                if (name.startsWith("el/", 6) ||
                    name.startsWith("servlet/", 6) ||
                    name.startsWith("websocket/", 6) ||
                    name.startsWith("security/auth/message/", 6)) {
                    return true;
                }
            }
        } else if (name.startsWith("org")) {
            /* 3 == length("org") */
            if (name.length() == 3) {
                return false;
            }
            ch = name.charAt(3);
            if (isClassName && ch == '.') {
                /* 4 == length("org.") */
                if (name.startsWith("apache.", 4)) {
                    /* 11 == length("org.apache.") */
                    if (name.startsWith("tomcat.jdbc.", 11)) {
                        return false;
                    }
                    if (name.startsWith("el.", 11) ||
                        name.startsWith("catalina.", 11) ||
                        name.startsWith("jasper.", 11) ||
                        name.startsWith("juli.", 11) ||
                        name.startsWith("tomcat.", 11) ||
                        name.startsWith("naming.", 11) ||
                        name.startsWith("coyote.", 11)) {
                        return true;
                    }
                }
            } else if (!isClassName && ch == '/') {
                /* 4 == length("org/") */
                if (name.startsWith("apache/", 4)) {
                    /* 11 == length("org/apache/") */
                    if (name.startsWith("tomcat/jdbc/", 11)) {
                        return false;
                    }
                    if (name.startsWith("el/", 11) ||
                        name.startsWith("catalina/", 11) ||
                        name.startsWith("jasper/", 11) ||
                        name.startsWith("juli/", 11) ||
                        name.startsWith("tomcat/", 11) ||
                        name.startsWith("naming/", 11) ||
                        name.startsWith("coyote/", 11)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

以上就是整个tomcat的类加载内容了,欢迎拍砖!!!

猜你喜欢

转载自blog.csdn.net/u013076044/article/details/78858638