深入剖析 Tomcat 9.0.53 源码:Web 资源管理与类加载机制

在 Tomcat 中,Web 资源的访问和 Web 应用的类加载是两个核心且紧密耦合的子系统。本文基于 Tomcat 9.0.53 的源码,详细分析 WebResourceRootWebResourceSet 以及 WebappClassLoader 的实现原理,揭示 Tomcat 如何高效地管理 /WEB-INF/classes/WEB-INF/lib 下的资源,并实现符合 Servlet 规范的双亲委派类加载模型。

1. 资源集合:allResourcesWebResourceSet

Tomcat 将一个 Web 应用的资源划分为多个逻辑层,每一层由 WebResourceSet 实现。在 StandardRootWebResourceRoot 的标准实现)中,通过一个二维列表维护所有资源集:

java

复制代码
private final List<List<WebResourceSet>> allResources = new ArrayList<>();
{
    allResources.add(preResources);
    allResources.add(mainResources);
    allResources.add(classResources);
    allResources.add(jarResources);
    allResources.add(postResources);
}
  • preResources:最高优先级,通常用于覆盖后续资源。

  • mainResources :代表 Web 应用文档根目录(docBase)。

  • classResources :对应 /WEB-INF/classes 目录。

  • jarResources :对应 /WEB-INF/lib 下每一个 JAR 文件,每个 JAR 会被包装成一个独立的 JarResourceSet

  • postResources:最低优先级,作为最后回退。

当需要获取某个路径的资源时,getResourceInternal 方法会按顺序遍历这五个层级,一旦找到存在的资源即返回:

java

复制代码
for (List<WebResourceSet> list : allResources) {
    for (WebResourceSet webResourceSet : list) {
        result = webResourceSet.getResource(path);
        if (result.exists()) {
            return result;
        }
    }
}

这种设计实现了灵活的覆盖机制:例如开发者可以将静态资源放在 preResources 中覆盖 mainResources 的同名文件。

2. 从 JAR 中读取资源:JarResourceSet 的路径处理

JarResourceSet 负责从 /WEB-INF/lib/*.jar 中读取资源。其 getResource 方法需要处理两个关键点:挂载点(webAppMount)JAR 内部路径转换

java

复制代码
@Override
public WebResource getResource(String path) {
    String webAppMount = getWebAppMount(); // 通常为 "/WEB-INF/lib"
    if (path.startsWith(webAppMount)) {
        String pathInJar = getInternalPath() + path.substring(webAppMount.length());
        // 去掉开头的 '/'
        if (pathInJar.length() > 0 && pathInJar.charAt(0) == '/') {
            pathInJar = pathInJar.substring(1);
        }
        // 查找 JarEntry...
    }
}

例如,请求路径 /WEB-INF/lib/guava.jar/com/google/common/collect/Lists.class,经过转换后 pathInJar 变为 "com/google/common/collect/Lists.class",再通过 JarFile.getJarEntry() 获取条目。

值得注意的是,Tomcat 实现了对 多版本 JAR(Multi-Release) 的支持。如果 JAR 的 MANIFEST.MF 中包含 Multi-Release: true,则调用 getArchiveEntry(pathInJar) 时,底层会优先返回与当前 JRE 版本匹配的版本化条目。

获取 JarEntry 后,JarResourceSet 会创建 JarResource(或 JarResourceRoot 表示目录)并返回。对于不存在的资源,返回 EmptyResource 以统一处理空值。

3. 类加载器的双亲委派与本地查找

WebappClassLoaderBase 是 Tomcat Web 应用类加载器的基类,其 loadClass 方法实现了 Servlet 规范要求的类加载顺序(不同于 JVM 默认的双亲委派):

  1. 检查本地已加载类缓存findLoadedClass0 / findLoadedClass)。

  2. 尝试从 Java SE 类加载器加载 ------防止 Web 应用覆盖 JDK 核心类。通过 JavaseClassLoader.getResource(resourceName) 试探资源是否存在,避免触发昂贵的 ClassNotFoundException

  3. SecurityManager 包访问权限检查

  4. 如果配置了 delegate=true 或当前类属于包过滤列表,先委派给父类加载器。

  5. 从本地仓库查找 :调用 findClass(name),实际会从 /WEB-INF/classes/WEB-INF/lib 中加载类。

  6. 最后再次委派给父类加载器(如果第4步未执行)。

这种顺序保证了 Web 应用优先加载自己提供的类(如 Spring、业务代码),同时防止覆盖 java.lang.String 等系统类。

4. findClassInternal:从字节码到 Class 对象

loadClass 进入本地查找阶段时,最终由 findClassInternal 完成实际加载。其核心步骤如下:

java

复制代码
String path = binaryNameToPath(name, true); // "com.example.MyClass" -> "/com/example/MyClass.class"
ResourceEntry entry = resourceEntries.get(path);
if (entry == null) {
    WebResource resource = resources.getClassLoaderResource(path);
    if (!resource.exists()) return null;
    // 创建 ResourceEntry 并缓存
}

resources.getClassLoaderResource(path) 实际上映射到 getResource("/WEB-INF/classes" + path, true, true),即优先从 classes 目录加载,若不存在则从 JAR 中加载。

获得 WebResource 后,调用 resource.getContent() 读取字节码。如果注册了 ClassFileTransformer(例如通过 Java Instrumentation 或 JSP 编译器),会对字节码进行转换。

接着处理 Package 定义与密封检查

  • 根据类名提取包名,若包未定义则调用 definePackage(从 JAR 的 Manifest 中获取规范信息)。

  • 如果包被标记为 sealed,校验当前类的 CodeSource 是否与包密封的 URL 匹配,否则抛出 SecurityException

最后通过 defineClass(name, binaryContent, ...) 完成类加载,并将结果存入 entry.loadedClass 以避免重复解析。

5. 动态处理 /WEB-INF/lib 下的 JAR

Tomcat 在启动时以及运行时(如果启用了 JAR 扫描)会调用 processWebInfLib 方法:

java

复制代码
protected void processWebInfLib() throws LifecycleException {
    WebResource[] possibleJars = listResources("/WEB-INF/lib", false);
    for (WebResource possibleJar : possibleJars) {
        if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
            createWebResourceSet(ResourceSetType.CLASSES_JAR,
                    "/WEB-INF/classes", possibleJar.getURL(), "/");
        }
    }
}

该方法遍历 /WEB-INF/lib 下的所有文件,对每个 .jar 文件创建一个 JarResourceSet 并添加到 jarResources 列表中。注意第二个参数 "/WEB-INF/classes" 表示这些 JAR 中的资源在虚拟路径上被挂载到 /WEB-INF/classes 下,从而使得 getClassLoaderResource("/com/example/MyClass.class") 能够从 JAR 中正确找到类文件。

Tomcat 还会记录每个 JAR 的最后修改时间(jarModificationTimes),用于后续的热部署检测。

6. 总结

通过上述源码分析,我们可以清晰看到 Tomcat 9.0.53 在资源管理和类加载方面的精巧设计:

  • 分层资源集合 提供了灵活的覆盖和组合能力,支持从目录、JAR、外部位置等多种来源透明地获取资源。

  • 统一的 WebResource 抽象 屏蔽了文件系统、JAR、内存等不同存储方式的差异。

  • 定制类加载器 既遵循 Servlet 规范的要求(允许 Web 应用覆盖部分类,同时保护 JDK 类),又通过缓存、字节码转换等特性提升了性能和可扩展性。

理解这些底层机制,不仅有助于诊断应用部署中的 ClassNotFoundException、资源找不到等问题,也为编写高性能、高可靠性的 Tomcat 应用打下坚实基础。如果你正在开发一个需要深入 Tomcat 集成的框架或工具,这些源码细节将是不可或缺的参考。

##源码

复制代码
private final List<List<WebResourceSet>> allResources =
            new ArrayList<>();
    {
        allResources.add(preResources);
        allResources.add(mainResources);
        allResources.add(classResources);
        allResources.add(jarResources);
        allResources.add(postResources);
    }

@Override
    public void start() throws LifecycleException {

        state = LifecycleState.STARTING_PREP;

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

        state = LifecycleState.STARTED;
    }

@Override
    public void start() throws LifecycleException {

        state = LifecycleState.STARTING_PREP;

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

        state = LifecycleState.STARTED;
    }

@Override
    public WebResource getResource(String path) {
        checkPath(path);
        String webAppMount = getWebAppMount();
        WebResourceRoot root = getRoot();
        if (path.startsWith(webAppMount)) {
            File f = file(path.substring(webAppMount.length()), false);
            if (f == null) {
                return new EmptyResource(root, path);
            }
            if (!f.exists()) {
                return new EmptyResource(root, path, f);
            }
            if (f.isDirectory() && path.charAt(path.length() - 1) != '/') {
                path = path + '/';
            }
            return new FileResource(root, path, f, isReadOnly(), getManifest());
        } else {
            return new EmptyResource(root, path);
        }
    }

protected void processWebInfLib() throws LifecycleException {
        WebResource[] possibleJars = listResources("/WEB-INF/lib", false);

        for (WebResource possibleJar : possibleJars) {
            if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
                createWebResourceSet(ResourceSetType.CLASSES_JAR,
                        "/WEB-INF/classes", possibleJar.getURL(), "/");
            }
        }
    }

protected final WebResource getResourceInternal(String path,
            boolean useClassLoaderResources) {
        WebResource result = null;
        WebResource virtual = null;
        WebResource mainEmpty = null;
        for (List<WebResourceSet> list : allResources) {
            for (WebResourceSet webResourceSet : list) {
                if (!useClassLoaderResources &&  !webResourceSet.getClassLoaderOnly() ||
                        useClassLoaderResources && !webResourceSet.getStaticOnly()) {
                    result = webResourceSet.getResource(path);
                    if (result.exists()) {
                        return result;
                    }
                    if (virtual == null) {
                        if (result.isVirtual()) {
                            virtual = result;
                        } else if (main.equals(webResourceSet)) {
                            mainEmpty = result;
                        }
                    }
                }
            }
        }
        
@Override
    public final WebResource getResource(String path) {
        checkPath(path);
        String webAppMount = getWebAppMount();
        WebResourceRoot root = getRoot();

        /*
         * If jarContents reports that this resource definitely does not contain
         * the path, we can end this method and move on to the next jar.
         */
        if (jarContents != null && !jarContents.mightContainResource(path, webAppMount)) {
            return new EmptyResource(root, path);
        }

        /*
         * Implementation notes
         *
         * The path parameter passed into this method always starts with '/'.
         *
         * The path parameter passed into this method may or may not end with a
         * '/'. JarFile.getEntry() will return a matching directory entry
         * whether or not the name ends in a '/'. However, if the entry is
         * requested without the '/' subsequent calls to JarEntry.isDirectory()
         * will return false.
         *
         * Paths in JARs never start with '/'. Leading '/' need to be removed
         * before any JarFile.getEntry() call.
         */

        // If the JAR has been mounted below the web application root, return
        // an empty resource for requests outside of the mount point.

        if (path.startsWith(webAppMount)) {
            String pathInJar = getInternalPath() + path.substring(
                    webAppMount.length());
            // Always strip off the leading '/' to get the JAR path
            if (pathInJar.length() > 0 && pathInJar.charAt(0) == '/') {
                pathInJar = pathInJar.substring(1);
            }
            if (pathInJar.equals("")) {
                // Special case
                // This is a directory resource so the path must end with /
                if (!path.endsWith("/")) {
                    path = path + "/";
                }
                return new JarResourceRoot(root, new File(getBase()),
                        baseUrlString, path);
            } else {
                JarEntry jarEntry = null;
                if (isMultiRelease()) {
                    // Calls JarFile.getJarEntry() which is multi-release aware
                    jarEntry = getArchiveEntry(pathInJar);
                } else {
                    Map<String,JarEntry> jarEntries = getArchiveEntries(true);
                    if (!(pathInJar.charAt(pathInJar.length() - 1) == '/')) {
                        if (jarEntries == null) {
                            jarEntry = getArchiveEntry(pathInJar + '/');
                        } else {
                            jarEntry = jarEntries.get(pathInJar + '/');
                        }
                        if (jarEntry != null) {
                            path = path + '/';
                        }
                    }
                    if (jarEntry == null) {
                        if (jarEntries == null) {
                            jarEntry = getArchiveEntry(pathInJar);
                        } else {
                            jarEntry = jarEntries.get(pathInJar);
                        }
                    }
                }
                if (jarEntry == null) {
                    return new EmptyResource(root, path);
                } else {
                    return createArchiveResource(jarEntry, path, getManifest());
                }
            }
        } else {
            return new EmptyResource(root, path);
        }
    }    
    
@Override
    protected JarEntry getArchiveEntry(String pathInArchive) {
        JarFile jarFile = null;
        try {
            jarFile = openJarFile();
            return jarFile.getJarEntry(pathInArchive);
        } catch (IOException ioe) {
            // Should never happen
            throw new IllegalStateException(ioe);
        } finally {
            if (jarFile != null) {
                closeJarFile();
            }
        }
    } 
    
    /**
     * Returns the <code>JarEntry</code> for the given entry name or
     * <code>null</code> if not found.
     *
     * @param name the jar file entry name
     * @return the <code>JarEntry</code> for the given entry name or
     *         <code>null</code> if not found.
     *
     * @throws IllegalStateException
     *         may be thrown if the jar file has been closed
     *
     * @see java.util.jar.JarEntry
     */
    public JarEntry getJarEntry(String name) {
        return (JarEntry)getEntry(name);
    }
    
public ZipEntry getEntry(String name) {
        ZipEntry ze = super.getEntry(name);
        if (ze != null) {
            return new JarFileEntry(ze);
        }
        return null;
    }        

public ZipEntry getEntry(String name) {
        if (name == null) {
            throw new NullPointerException("name");
        }
        long jzentry = 0;
        synchronized (this) {
            ensureOpen();
            jzentry = getEntry(jzfile, zc.getBytes(name), true);
            if (jzentry != 0) {
                // If no entry is found for the specified 'name' and
                // the 'name' does not end with a forward slash '/',
                // the implementation tries to find the entry with a
                // slash '/' appended to the end of the 'name', before
                // returning null. When such entry is found, the name
                // that actually is found (with a slash '/' attached)
                // is used
                // (disabled if jdk.util.zip.ensureTrailingSlash=false)
                ZipEntry ze = ensuretrailingslash ? getZipEntry(null, jzentry)
                                                  : getZipEntry(name, jzentry);
                freeEntry(jzfile, jzentry);
                return ze;
            }
        }
        return null;
    }
       
@Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (JreCompat.isGraalAvailable() ? this : 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 = JreCompat.isGraalAvailable() ? null : 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();
            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
                URL url;
                if (securityManager != null) {
                    PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                    url = AccessController.doPrivileged(dp);
                } else {
                    url = javaseLoader.getResource(resourceName);
                }
                tryLoadingFromJavaseLoader = (url != 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 = sm.getString("webappClassLoader.restrictedPackage", 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);
    }
    
@Override
    public Class<?> findClass(String name) throws ClassNotFoundException {

        if (log.isDebugEnabled()) {
            log.debug("    findClass(" + name + ")");
        }

        checkStateForClassLoading(name);

        // (1) Permission to define this class when using a SecurityManager
        if (securityManager != null) {
            int i = name.lastIndexOf('.');
            if (i >= 0) {
                try {
                    if (log.isTraceEnabled()) {
                        log.trace("      securityManager.checkPackageDefinition");
                    }
                    securityManager.checkPackageDefinition(name.substring(0,i));
                } catch (Exception se) {
                    if (log.isTraceEnabled()) {
                        log.trace("      -->Exception-->ClassNotFoundException", se);
                    }
                    throw new ClassNotFoundException(name, se);
                }
            }
        }

        // Ask our superclass to locate this class, if possible
        // (throws ClassNotFoundException if it is not found)
        Class<?> clazz = null;
        try {
            if (log.isTraceEnabled()) {
                log.trace("      findClassInternal(" + name + ")");
            }
            try {
                if (securityManager != null) {
                    PrivilegedAction<Class<?>> dp =
                        new PrivilegedFindClassByName(name);
                    clazz = AccessController.doPrivileged(dp);
                } else {
                    clazz = findClassInternal(name);
                }
            } catch(AccessControlException ace) {
                log.warn(sm.getString("webappClassLoader.securityException", name,
                        ace.getMessage()), ace);
                throw new ClassNotFoundException(name, ace);
            } catch (RuntimeException e) {
                if (log.isTraceEnabled()) {
                    log.trace("      -->RuntimeException Rethrown", e);
                }
                throw e;
            }
            if ((clazz == null) && hasExternalRepositories) {
                try {
                    clazz = super.findClass(name);
                } catch(AccessControlException ace) {
                    log.warn(sm.getString("webappClassLoader.securityException", name,
                            ace.getMessage()), ace);
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    if (log.isTraceEnabled()) {
                        log.trace("      -->RuntimeException Rethrown", e);
                    }
                    throw e;
                }
            }
            if (clazz == null) {
                if (log.isDebugEnabled()) {
                    log.debug("    --> Returning ClassNotFoundException");
                }
                throw new ClassNotFoundException(name);
            }
        } catch (ClassNotFoundException e) {
            if (log.isTraceEnabled()) {
                log.trace("    --> Passing on ClassNotFoundException");
            }
            throw e;
        }

        // Return the class we have located
        if (log.isTraceEnabled()) {
            log.debug("      Returning class " + clazz);
        }

        if (log.isTraceEnabled()) {
            ClassLoader cl;
            if (Globals.IS_SECURITY_ENABLED){
                cl = AccessController.doPrivileged(
                    new PrivilegedGetClassLoader(clazz));
            } else {
                cl = clazz.getClassLoader();
            }
            log.debug("      Loaded by " + cl.toString());
        }
        return clazz;

    }    
    
protected Class<?> findClassInternal(String name) {

        checkStateForResourceLoading(name);

        if (name == null) {
            return null;
        }
        String path = binaryNameToPath(name, true);

        ResourceEntry entry = resourceEntries.get(path);
        WebResource resource = null;

        if (entry == null) {
            resource = resources.getClassLoaderResource(path);

            if (!resource.exists()) {
                return null;
            }

            entry = new ResourceEntry();
            entry.lastModified = resource.getLastModified();

            // Add the entry in the local resource repository
            synchronized (resourceEntries) {
                // Ensures that all the threads which may be in a race to load
                // a particular class all end up with the same ResourceEntry
                // instance
                ResourceEntry entry2 = resourceEntries.get(path);
                if (entry2 == null) {
                    resourceEntries.put(path, entry);
                } else {
                    entry = entry2;
                }
            }
        }

        Class<?> clazz = entry.loadedClass;
        if (clazz != null) {
            return clazz;
        }

        synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
            clazz = entry.loadedClass;
            if (clazz != null) {
                return clazz;
            }

            if (resource == null) {
                resource = resources.getClassLoaderResource(path);
            }

            if (!resource.exists()) {
                return null;
            }

            byte[] binaryContent = resource.getContent();
            if (binaryContent == null) {
                // Something went wrong reading the class bytes (and will have
                // been logged at debug level).
                return null;
            }
            Manifest manifest = resource.getManifest();
            URL codeBase = resource.getCodeBase();
            Certificate[] certificates = resource.getCertificates();

            if (transformers.size() > 0) {
                // If the resource is a class just being loaded, decorate it
                // with any attached transformers

                // Ignore leading '/' and trailing CLASS_FILE_SUFFIX
                // Should be cheaper than replacing '.' by '/' in class name.
                String internalName = path.substring(1, path.length() - CLASS_FILE_SUFFIX.length());

                for (ClassFileTransformer transformer : this.transformers) {
                    try {
                        byte[] transformed = transformer.transform(
                                this, internalName, null, null, binaryContent);
                        if (transformed != null) {
                            binaryContent = transformed;
                        }
                    } catch (IllegalClassFormatException e) {
                        log.error(sm.getString("webappClassLoader.transformError", name), e);
                        return null;
                    }
                }
            }

            // Looking up the package
            String packageName = null;
            int pos = name.lastIndexOf('.');
            if (pos != -1) {
                packageName = name.substring(0, pos);
            }

            Package pkg = null;

            if (packageName != null) {
                pkg = getPackage(packageName);

                // Define the package (if null)
                if (pkg == null) {
                    try {
                        if (manifest == null) {
                            definePackage(packageName, null, null, null, null, null, null, null);
                        } else {
                            definePackage(packageName, manifest, codeBase);
                        }
                    } catch (IllegalArgumentException e) {
                        // Ignore: normal error due to dual definition of package
                    }
                    pkg = getPackage(packageName);
                }
            }

            if (securityManager != null) {
                // Checking sealing
                if (pkg != null) {
                    boolean sealCheck = true;
                    if (pkg.isSealed()) {
                        sealCheck = pkg.isSealed(codeBase);
                    } else {
                        sealCheck = (manifest == null) || !isPackageSealed(packageName, manifest);
                    }
                    if (!sealCheck) {
                        throw new SecurityException
                            ("Sealing violation loading " + name + " : Package "
                             + packageName + " is sealed.");
                    }
                }
            }

            try {
                clazz = defineClass(name, binaryContent, 0,
                        binaryContent.length, new CodeSource(codeBase, certificates));
            } catch (UnsupportedClassVersionError ucve) {
                throw new UnsupportedClassVersionError(
                        ucve.getLocalizedMessage() + " " +
                        sm.getString("webappClassLoader.wrongVersion",
                                name));
            }
            entry.loadedClass = clazz;
        }

        return clazz;
    }  

@Override
    public WebResource getClassLoaderResource(String path) {
        return getResource("/WEB-INF/classes" + path, true, true);
    }
相关推荐
用户1563068103515 小时前
Day01 | Java 基础(Java SE)
java
Pedantic5 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘5 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆5 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师6 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
行者全栈架构师6 小时前
Maven dependency:tree 的 8 个高级用法
java·后端
雨季mo浅忆6 小时前
VSCode自动格式化三要素
前端
爱勇宝7 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen7 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程