深入剖析 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);
    }
相关推荐
Mintopia1 小时前
别再乱用工具函数:一套可控的 util 设计规则
前端
m0_475064501 小时前
Spring AI文档切片
java·人工智能·spring
我登哥MVP1 小时前
【SpringMVC笔记】 - 1 - SpringMVC入门
java·spring boot·spring·tomcat·maven·intellij-idea·springmvc
光影少年1 小时前
开发RN项目时,如何调试iOS真机、Android真机?常见调试问题排查?
android·前端·react native·react.js·ios
Arva .1 小时前
Spring 事务传播机制 速记
java·数据库·spring
石小石Orz2 小时前
邪修!让显示器支持AI、远程、手势三种控制方式
前端·ai编程
前端 贾公子2 小时前
npm 包 postcss-logical 使用教程
前端
码码哈哈0.02 小时前
IOS26风格的AI前端提示词
前端
0xDevNull2 小时前
Spring Boot 2.0动态多数据源切换实战教程
java·后端