1. 程式人生 > >類載入器--Tomcat--ParallelWebappClassLoader

類載入器--Tomcat--ParallelWebappClassLoader

首先是jvm自帶的三個類載入器的關係圖:
在這裡插入圖片描述
系統類載入器在載入一個類時,會先查詢已經載入的類,如果沒找到,再委託父載入器(父載入器不是父類,這是2個概念),父載入器沒找到就繼續委託父載入器,直到所有的父載入器都沒有找到,並且都載入失敗之後,就自己載入,如果自己載入也失敗了,就拋異常。

父類載入過,而且還嘗試載入失敗,那麼就自己來

    c = findClass(name);

這個方法在urlClassLoader中實現。自定義類載入器,一般覆蓋這個方法。呼叫protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)方法即可。

protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }
 protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

直接繼承ClassLoader的類載入器,需要重寫findClass方法,而且這樣產生的類載入器依然是雙親委託的。

如果想破壞雙親委託機制,則還需要額外重寫loadClass方法。

tomcat 擁有不同的自定義類載入器,以實現對各種資源庫的控制。一般來說,tomcat主要用類載入器解決以下4個問題:

  1. 同一個web伺服器裡,各個web專案之間各自使用的Java類庫要相互隔離
  2. 同一個web伺服器中,各個web專案之間可以提供共享的Java類庫
  3. 為了使伺服器不受web專案的影響,應該使伺服器的類庫與應用程式的類庫相互獨立
  4. 對於jsp的web伺服器,應該支援熱插拔功能

tomcat的類載入器定義:
在這裡插入圖片描述
common類載入器負責載入%catalina_home%/lib,%catalina_base%/lib兩個目錄下的class檔案和jar檔案。
在這裡插入圖片描述在這裡插入圖片描述tomcat 7中預設左邊兩個類載入器都是common。

tomcat 7中對這些類載入器的初始化,根據catalina.properties中的server.loader和share.loader屬性來判斷要不要建立新的類載入器。
在這裡插入圖片描述
tomcat中有多少個web應用,就有多少個webappclassLader。
它使用ClassLoaderFactory 構建類載入器,common類載入器new URLClassLoader(array, parent);實際上就是urlclassloader。
父載入器是應用類載入器,tomcat會在啟動時把當前執行緒類載入器設定為common類載入器。

public static ClassLoader createClassLoader(File[] unpacked, File[] packed, final ClassLoader parent) throws Exception

public static ClassLoader createClassLoader(List<ClassLoaderFactory.Repository> repositories, final ClassLoader parent) throws Exception
public static enum RepositoryType {
        DIR,   //載入目錄下所有資源
        GLOB, //整個目錄下所有的jar包資源
        JAR, //單個jar包資源
        URL; //從url上獲取的jar包資源

        private RepositoryType() {
        }
    }

ParallelWebappClassLoader這類的主要邏輯是在父類中實現的,子類只有一個方法。用於熱部署時重新載入所有的類,重新載入其實只需要新建一個類載入器把類再載入一遍就可以了,這裡還保留了載入器之前的狀態。

public ParallelWebappClassLoader copyWithoutTransformers() {
        ParallelWebappClassLoader result = new ParallelWebappClassLoader(this.getParent());
        super.copyStateWithoutTransformers(result);

        try {
            result.start();
            return result;
        } catch (LifecycleException var3) {
            throw new IllegalStateException(var3);
        }
    }
protected void copyStateWithoutTransformers(WebappClassLoaderBase base) {
        base.resources = this.resources;
        base.delegate = this.delegate; //預設一般是false
        base.state = LifecycleState.NEW;
        base.clearReferencesStopThreads = this.clearReferencesStopThreads;
        base.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
        base.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
        base.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
        base.jarModificationTimes.putAll(this.jarModificationTimes);
        base.permissionList.addAll(this.permissionList);
        base.loaderPC.putAll(this.loaderPC);
    }

delegate 為true時,這裡的父類載入器 不是Java語法中的父類,只是單純的父載入器。parent屬性指定的。
在這裡插入圖片描述

為false時
在這裡插入圖片描述

再看核心方法start(),很明顯,在重新建立一個新的類載入器後,會呼叫這個方法:

public void start() throws LifecycleException {
        this.state = LifecycleState.STARTING_PREP;
        WebResource classes = this.resources.getResource("/WEB-INF/classes");
        if(classes.isDirectory() && classes.canRead()) {
            this.localRepositories.add(classes.getURL());
        }

        WebResource[] jars = this.resources.listResources("/WEB-INF/lib");
        WebResource[] var3 = jars;
        int var4 = jars.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            WebResource jar = var3[var5];
            if(jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
                this.localRepositories.add(jar.getURL());
                this.jarModificationTimes.put(jar.getName(), Long.valueOf(jar.getLastModified()));
            }
        }

        this.state = LifecycleState.STARTED;
    }

父類WebappClassLoaderBase的核心程式碼是下面的部分,讀取位元組碼,呼叫轉換器,載入類。這些步驟和普通載入器區別不大。

 	 byte[] binaryContent = resource.getContent();
          Manifest manifest = resource.getManifest();
        URL codeBase = resource.getCodeBase();
         Certificate[] certificates = resource.getCertificates();
          String packageName;
       if(this.transformers.size() > 0) {
                packageName = path.substring(1, path.length() - ".class".length());
                 Iterator var12 = this.transformers.iterator();

               while(var12.hasNext()) {
                          ClassFileTransformer transformer = (ClassFileTransformer)var12.next();

                           try {
                                    byte[] transformed = transformer.transform(this, packageName, (Class)null, (ProtectionDomain)null, binaryContent);
                                 if(transformed != null) {
                                     binaryContent = transformed;
                                }
                         } catch (IllegalClassFormatException var18) {
                                        log.error(sm.getString("webappClassLoader.transformError", new Object[]{name}), var18);
                                        return null;
                                    }
                                }
                            }

                            packageName = null;
                            int pos = name.lastIndexOf(46);
                            if(pos != -1) {
                                packageName = name.substring(0, pos);
                            }

                            Package pkg = null;
                            if(packageName != null) {
                                pkg = this.getPackage(packageName);
                                if(pkg == null) {
                                    try {
                                        if(manifest == null) {
                                            this.definePackage(packageName, (String)null, (String)null, (String)null, (String)null, (String)null, (String)null, (URL)null);
                                        } else {
                                            this.definePackage(packageName, manifest, codeBase);
                                        }
                                    } catch (IllegalArgumentException var17) {
                                        ;
                                    }

                                    pkg = this.getPackage(packageName);
                                }
                            }

                            if(this.securityManager != null && pkg != null) {
                                boolean sealCheck = true;
                                if(pkg.isSealed()) {
                                    sealCheck = pkg.isSealed(codeBase);
                                } else {
                                    sealCheck = manifest == null || !this.isPackageSealed(packageName, manifest);
                                }

                                if(!sealCheck) {
                                    throw new SecurityException("Sealing violation loading " + name + " : Package " + packageName + " is sealed.");
                                }
                            }

                            try {
                                clazz = this.defineClass(name, binaryContent, 0, binaryContent.length, new CodeSource(codeBase, certificates));
                            } catch (UnsupportedClassVersionError var16) {
                                throw new UnsupportedClassVersionError(var16.getLocalizedMessage() + " " + sm.getString("webappClassLoader.wrongVersion", new Object[]{name}));
                            }

                            entry.loadedClass = clazz;
                            return clazz;
                        }