Spring資源加載基礎ClassLoader

1 ClassLoader工做機制

1.1 ClassLoader做用

尋找類字節碼文件並構造出類在JVM內部表示的組件.負責運行時查找和裝入Class字節碼文件java

1.2 裝載步驟

1.2.1 裝載

查找裝載class字節碼文件數組

1.2.2 連接

執行校驗,準備和解析步驟,其中解析步驟時可選的緩存

1.2.2.1 校驗

檢查裝載Class文件的正確性安全

1.2.2.2 準備

給類的靜態變量分配存儲空間網絡

1.2.2.3 解析

將符號引用轉換爲直接引用app

1.2.3 初始化

對類的靜態變量,方法,代碼塊執行初始化操做jvm

2 JVM中提供的ClassLoader

2.1 Bootstrap ClassLoader(根加載器)

最頂層的裝載器,它不是ClassLoader的子類,採用C++編寫,所以在JAVA中不可見。主要負責裝載JRE核心類庫。能夠經過jvm啓動參數-Xbootclasspath改變該加載器加載的路徑函數

2.2 Extention ClassLoader擴展加載器)

主要負責加載JRE擴展目錄ext下的包,能夠經過-D java.ext.dirs選項指定目錄源碼分析

2.3 App ClassLoader(應用加載器/系統加載器)

負責加載當前工程目錄下,classpath下的包或者class文件post

其中Extention ClassLoader & AppClassLoaderClassLoader的子類,根加載器是擴展加載器的父加載器,擴展加載器是應用加載器的父加載器。在默認狀況下使用應用加載器

3 JVM加載機制

類加載採用「全盤負責委託機制」。

「全盤負責」:在類加載時指定一個ClassLoader,除非顯示聲明其餘的加載器,不然該類所依賴的類以及引用的類都由該加載器加載。

「委託機制」:類加載時優先委託父加載器尋找並加載目標類,只有在父加載器沒有找到的狀況下,才從本身的classpath路徑下查找並加載目標類。該點主要是出於安全的考慮,在classpath路徑下定義JDK中已經存在的類,因爲該機制,JDK中的類都由父加載器開始查找並加載.每一個加載器都有緩存,在委託時優先查找緩存,若是緩存中存在,那麼直接返回,否者才執行查找和加載


4 類實例,類描述對象以及ClassLoader關係

類文件被加載後,在JVM內部對應擁有一個java.lang.Class類描述對象,類的每一個實例擁有類描述對象的引用,類描述對象擁有類加載器的引用


啓動順序

Bootstrap ClassLoader-->Extention ClassLoader-->AppClass Loader即Bootstrap ClassLoader最早啓動,接着是Extenion ClassLoader,最後是AppClass Loader

源碼分析

6.1 JVM入口應用是sun.misc.Launcher

1.定義Bootstrap ClassLoader加載路徑(sun.boot.class.path)

2.建立Extention ClassCloader

3.建立App ClassLoader並設置App ClassLoader的父類:Extention ClassCloader

  1. public class Launcher {
  2. //Bootstrap ClassLoader加載路徑
  3. private static String bootClassPath = System.getProperty("sun.boot.class.path");
  4.  
  5.     //該處定義的App ClassLoader
  6. private ClassLoader loader;
  7.  
  8. public Launcher() {
  9. // Create the extension class loader
  10. ClassLoader extcl;
  11. try {
  12.     //建立Extention ClassLoader
  13. extcl = ExtClassLoader.getExtClassLoader();
  14. } catch (IOException e) {......}
  15.  
  16. // Now create the class loader to use to launch the application
  17. try {
  18.     //建立AppClassLoader並傳入父加載器,由此可看AppClassLoader父加載器是Extention ClassLoader
  19. loader = AppClassLoader.getAppClassLoader(extcl);
  20. } catch (IOException e) {......}
  21.  
  22. //設置AppClassLoader爲當前線程上下問類加載器
  23. // Also set the context class loader for the primordial thread.
  24. Thread.currentThread().setContextClassLoader(loader);
  25. }
  26.  
  27. public ClassLoader getClassLoader() {
  28. return loader;
  29. }
  30.  
  31. static class ExtClassLoader extends URLClassLoader {}
  32.  
  33. static class AppClassLoader extends URLClassLoader {}
  34. }

該類中定義了Bootstrap ClassLoader加載路徑,同時建立了Extention ClassLoader以及AppClassLoader,以下是Bootstrap ClassLoader加載路徑信息

  1. /usr/lib/jvm/java- 8-openjdk-amd64/jre/lib/resources.jar
  2. /usr/lib/jvm/java- 8-openjdk-amd64/jre/lib/rt.jar
  3. /usr/lib/jvm/java- 8-openjdk-amd64/jre/lib/sunrsasign.jar
  4. /usr/lib/jvm/java- 8-openjdk-amd64/jre/lib/jsse.jar
  5. /usr/lib/jvm/java- 8-openjdk-amd64/jre/lib/jce.jar
  6. /usr/lib/jvm/java- 8-openjdk-amd64/jre/lib/charsets.jar
  7. /usr/lib/jvm/java- 8-openjdk-amd64/jre/lib/jfr.jar
  8. /usr/lib/jvm/java- 8-openjdk-amd64/jre/classes

從輸出路徑上看Bootstrap ClassLoader主要加載的是jre/lib目錄下的資源.JVM Runtime核心庫

6.2 ExtClassLoader源碼

1.根據查找路徑得到路徑(jav.ext.dirs)下文件

2.將文件路徑轉換爲URL

3.調用父類構造函數,建立ExtClassLoader並設置父加載器

  1. static class ExtClassLoader extends URLClassLoader {
  2. public static ExtClassLoader getExtClassLoader() throws IOException
  3. {
  4. final File[] dirs = getExtDirs();//得到查找路徑下的全部文件
  5. try {
  6. return AccessController.doPrivileged(
  7. new PrivilegedExceptionAction<ExtClassLoader>() {
  8. public ExtClassLoader run() throws IOException {
  9. ......
  10.     //建立Extention ClassLoader
  11. return new ExtClassLoader(dirs);
  12. }
  13. });
  14. } catch (java.security.PrivilegedActionException e) {......}
  15. }
  1. public ExtClassLoader(File[] dirs) throws IOException {
  2.     //調用父類URLClassLoader的構造函數並傳入父加載器,此時爲null
  3. super(getExtURLs(dirs), null, factory);
  4. }
  1. private static File[] getExtDirs() {
  2.     //Extention ClassLoader加載路徑
  3. String s = System.getProperty( "java.ext.dirs");
  4. File[] dirs;
  5. if (s != null) {
  6. StringTokenizer st = new StringTokenizer(s, File.pathSeparator);
  7. int count = st.countTokens();
  8. dirs = new File[count];
  9. for (int i = 0; i < count; i++) {
  10. dirs[i] = new File(st.nextToken());
  11. }
  12. } else {
  13. dirs = new File[0];
  14. }
  15. return dirs;
  16. }
  17. }
  1. private static URL[] getExtURLs(File[] dirs) throws IOException {
  2. Vector<URL> urls = new Vector<URL>();
  3. for (int i = 0; i < dirs.length; i++) {
  4. String[] files = dirs[i].list();
  5. if (files != null) {
  6. for (int j = 0; j < files.length; j++) {
  7. if (!files[j].equals("meta-index")) {
  8. File f = new File(dirs[i], files[j]);
  9. urls.add(getFileURL(f));
  10. }
  11. }
  12. }
  13. }
  14. URL[] ua = new URL[urls.size()];
  15. urls.copyInto(ua);
  16. return ua;
  17. }
  1. static URL getFileURL(File file) {
  2. try {
  3. file = file.getCanonicalFile();
  4. } catch (IOException e) {}
  5.  
  6. try {
  7. return ParseUtil.fileToEncodedURL(file);
  8. } catch (MalformedURLException e) {
  9. // Should never happen since we specify the protocol...
  10. throw new InternalError(e);
  11. }
  12. }

以下是java.ext.dirs的輸出信息

  1. /usr/lib/jvm/java- 8-openjdk-amd64/jre/lib/ext
  2. /usr/java/packages /lib/ext

6.3 AppClassLoader源碼

1.得到java.classs.path路徑下的全部文件

2.解析文件路徑爲URL

3.調用父類構造函數建立AppClassLoader並設置父類

  1. static class AppClassLoader extends URLClassLoader {
  2. public static ClassLoader getAppClassLoader(final ClassLoader extcl)
  3. throws IOException
  4. {
  5.     //AppClassLoader加載路徑
  6. final String s = System.getProperty("java.class.path");
  7. final File[] path = (s == null) ? new File[0] : getClassPath(s);
  8.  
  9. return AccessController.doPrivileged(
  10. new PrivilegedAction<AppClassLoader>() {
  11. public AppClassLoader run() {
  12. URL[] urls =
  13. (s == null) ? new URL[0] : pathToURLs(path);
  14. return new AppClassLoader(urls, extcl);
  15. }
  16. });
  17. }
  18.  
  19. AppClassLoader(URL[] urls, ClassLoader parent) {
  20. super(urls, parent, factory);
  21. }
  22. }
  23. }
  1. private static URL[] pathToURLs(File[] path) {
  2. URL[] urls = new URL[path.length];
  3. for (int i = 0; i < path.length; i++) {
  4. urls[i] = getFileURL(path[i]);
  5. }
  6. // DEBUG
  7. //for (int i = 0; i < urls.length; i++) {
  8. // System.out.println("urls[" + i + "] = " + '"' + urls[i] + '"');
  9. //}
  10. return urls;
  11. }

Java.class.path輸出信息

/work/new_workspace/aaa/bin

7 ClassLoader繼承關係

java.lang.Object

    java.lang.ClassLoader

        java.security.SecureClassLoader

            java.net.URLClassLoader

                ExtClassLoader

                AppClassLoader

得到加載器的父加載器

能夠經過getParent()得到當前加載器的父加載器

  1. //Main爲自定義Class
  2. System.out.println(Main.class.getClassLoader());
  3. System.out.println(Main.class.getClassLoader().getParent());
  4.  
  5. System.out.println(Main.class.getClassLoader().getParent().getParent());
  6. System.out.println(Boolean.class.getClassLoader());
  1. sun.misc.Launcher$AppClassLoader@330bedb4
  2. sun.misc.Launcher$ExtClassLoader@ 5cad8086
  3.  
  4. null
  5. null

爲何得到ExtClassLoader的父加載器爲null?這和每一個加載器都有一個父加載器違背.這個屬於正常現象.具體緣由以下:

1.從ExtClassLoader構造函數super(getExtURLs(dirs), null, factory);能夠看出此時傳入的就是null

2.Bootstrap ClassLoader是由C/C++編寫的,它自己是虛擬機的一部分,不是一個JAVA類,沒法在java代碼中獲取它的引用,凡是sun.boot.class.path路徑下的包以及類都是由它加載。JVM初始化sun.misc.Launcher建立Extension ClassLoader和AppClassLoader實例。並將ExtClassLoader設置爲AppClassLoader的父加載器。BootstrapClassCloader沒有父加載器,可是它卻能夠做爲一個ClassLoader的父加載器。好比ExtClassLoader。這也能夠解釋以前經過ExtClassLoader的getParent方法獲取爲Null的現象.咱們能夠分析getParent()的源碼

父加載器能夠直接由外界指定,若是外界不指定,那麼採用AppClassLoader做爲父加載器

//在建立ExtClassLoader時,採用super(getExtURLs(dirs), null, factory).因此得到的是null

  1. public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
  2. super(parent);
  3. }
  1. public abstract class ClassLoader {
  2. //父加載器直接由外部指定
  3. protected ClassLoader(ClassLoader parent) {
  4. this(checkCreateClassLoader(), parent);
  5. }
  6. }
  1. //沒有指定父加載器的狀況下將系統加載器設置爲父加載器即AppClassLoader
  2. protected ClassLoader() {
  3. this(checkCreateClassLoader(), getSystemClassLoader());
  4. }
  1. private ClassLoader(Void unused, ClassLoader parent) {
  2. this.parent = parent;
  3. ...
  4. }
  1. public final ClassLoader getParent() {
  2. if (parent == null)
  3. return null;
  4. ..........
  5. return parent;
  6. }
  1. public static ClassLoader getSystemClassLoader() {
  2. initSystemClassLoader();
  3. ......
  4. return scl;
  5. }
  1. private static synchronized void initSystemClassLoader() {
  2. if (!sclSet) {
  3. //經過Launcher獲取ClassLoader,其實就是AppClassLoader
  4. sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
  5.     scl = l.getClassLoader();
  6. }

9 重要方法

9.1 URL getResource(String name)

查找給定名稱的資源。資源的名稱是一個/分隔的路徑名稱標識資源,優先查找父加載器,若是找不到在從Bootstrap ClassLoader路徑中查找並加載

  1. public URL getResource(String name) {
  2. URL url;
  3. if (parent != null) {
  4. url = parent.getResource(name);
  5. } else {
  6. url = getBootstrapResource(name);
  7. }
  8. if (url == null) {
  9. url = findResource(name);
  10. }
  11. return url;
  12. }

9.2 Enumeration<URL> getResources(String name)

查找給定名稱的全部資源。資源的名稱是一個/分隔的路徑名稱標識資源,優先查找父加載器,若是找不到在從Bootstrap ClassLoader路徑中查找並加載,該模式下支持正則匹配

  1. public Enumeration<URL> getResources(String name) throws IOException {
  2. @SuppressWarnings("unchecked")
  3. Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
  4. if (parent != null) {
  5. tmp[ 0] = parent.getResources(name);
  6. } else {
  7. tmp[ 0] = getBootstrapResources(name);
  8. }
  9. tmp[ 1] = findResources(name);
  10.  
  11. return new CompoundEnumeration<>(tmp);
  12. }

9.3 URL getSystemResource(String name)

採用AppClassLoader加載給定名稱的資源

  1. public static URL getSystemResource(String name) {
  2. ClassLoader system = getSystemClassLoader();
  3. if (system == null) {
  4. return getBootstrapResource(name);
  5. }
  6. return system.getResource(name);
  7. }

9.4 Enumeration<URL> getSystemResources(String name)

採用AppClassLoader加載給定名稱的全部資源

  1. public static Enumeration<URL> getSystemResources(String name)
  2. throws IOException
  3. {
  4. ClassLoader system = getSystemClassLoader();
  5. if (system == null) {
  6. return getBootstrapResources(name);
  7. }
  8. return system.getResources(name);
  9. }

9.5 URL findResource(String name)

查找具備給定名稱的資源。類加載器實現應覆蓋此方法以指定在哪裏查找資源

  1. protected URL findResource(String name) {
  2. return null;
  3. }

9.6 Enumeration<URL> findResources(String name)

查找具備給定名稱的全部資源。類加載器實現應覆蓋此方法以指定在哪裏查找資源

  1. protected Enumeration<URL> findResources(String name) throws IOException {
  2. return java.util.Collections.emptyEnumeration();
  3. }

9.7 loadClass

loadClass(String name) & loadClass(String name, boolean resolve)name指定了類裝載器的名字,必須使用全限定名。resolve告訴裝載器是否須要解析該類。在初始化以前,應該考慮進行類解析工做。並非全部的類都須要解析的。若是JVM只須要知道該類是否存在或者找出該類的超類,那麼就不須要解析該類

  1. public abstract class ClassLoader {
  2. protected Class<?> loadClass(String name, boolean resolve)
  3. throws ClassNotFoundException
  4. {
  5. synchronized (getClassLoadingLock(name)) {
  6. // First, check if the class has already been loaded
  7. //是否已經加載
  8. Class<?> c = findLoadedClass(name);
  9. if (c == null) {
  10. try {
  11. if (parent != null) {
  12. //遞歸調用,查找父加載器
  13. c = parent.loadClass(name, false);
  14. } else {
  15. //調用Bootstrap Classloader
  16. c = findBootstrapClassOrNull(name);
  17. }
  18. } catch (ClassNotFoundException e) {......}
  19.  
  20. if (c == null) {
  21. // If still not found, then invoke findClass in order
  22. // to find the class.
  23.     //調用findClass
  24. c = findClass(name);
  25. ....
  26. }
  27. }
  28. if (resolve) {
  29. resolveClass(c);
  30. }
  31. return c;
  32. }
  33. }
  34. }
  1. protected final Class<?> findLoadedClass(String name) {
  2. if (!checkName(name))
  3. return null;
  4. return findLoadedClass0(name);
  5. }
  6.  
  7. private native final Class<?> findLoadedClass0(String name);
  1. private Class<?> findBootstrapClassOrNull(String name)
  2. {
  3. if (!checkName(name)) return null;
  4. return findBootstrapClass(name);
  5. }
  6.  
  7. private native Class<?> findBootstrapClass(String name);
  1. protected Class<?> findClass(String name) throws ClassNotFoundException {
  2. throw new ClassNotFoundException(name);
  3. }

9.8 defineClass(String name,byte[] b, int off,int len)

將類文件字節碼數組裝換成JVM內部的java.lang.Class對象,字節數組能夠從本地系統,遠程網絡獲取

  1. protected final Class<?> defineClass(String name, byte[] b, int off, int len)
  2. throws ClassFormatError
  3. {
  4. return defineClass(name, b, off, len, null);
  5. }
  1. protected final Class<?> defineClass(String name, byte[] b, int off, int len,
  2. ProtectionDomain protectionDomain)
  3. throws ClassFormatError
  4. {
  5. protectionDomain = preDefineClass(name, protectionDomain);
  6. String source = defineClassSourceLocation(protectionDomain);
  7. Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
  8. postDefineClass(c, protectionDomain);
  9. return c;
  10. }
  1. private native Class<?> defineClass1(String name, byte[] b, int off, int len,
  2. ProtectionDomain pd, String source);

9.9 findSystemClass(String)

從本地文件系統載入Class文件,若是本地文件系統中不存在該Class文件,拋出ClassNotFoundError。該方法是JVM默認使用的裝載器

  1. protected final Class<?> findSystemClass(String name)
  2. throws ClassNotFoundException
  3. {
  4. ClassLoader system = getSystemClassLoader();
  5. if (system == null) {
  6. if (!checkName(name))
  7. throw new ClassNotFoundException(name);
  8. Class<?> cls = findBootstrapClass(name);
  9. if (cls == null) {
  10. throw new ClassNotFoundException(name);
  11. }
  12. return cls;
  13. }
  14. return system.loadClass(name);
  15. }

9.10 findLoadedClass(String name)

該方法查看ClassLoader是否已經加載了某個類,若是載入就返回Class對象,不然返回null,若是強行載入已經載入的類會拋出異常

  1. protected final Class<?> findLoadedClass(String name) {
  2. if (!checkName(name))
  3. return null;
  4. return findLoadedClass0(name);
  5. }
  6.  
  7. private native final Class<?> findLoadedClass0(String name);

10 如何自定義ClassLoader

1.編寫一個類繼承ClassLoader

2.重寫findClass 方法

3.在findClass中調用defineClass便可

 

11 Context ClassLoader 線程上下文類加載器


  1. public class Thread implements Runnable {
  2.  
  3. /* The context ClassLoader for this thread */
  4. private ClassLoader contextClassLoader;
  5.  
  6. public void setContextClassLoader(ClassLoader cl) {
  7. SecurityManager sm = System.getSecurityManager();
  8. if (sm != null) {
  9. sm.checkPermission( new RuntimePermission("setContextClassLoader"));
  10. }
  11. contextClassLoader = cl;
  12. }
  13.  
  14. public ClassLoader getContextClassLoader() {
  15. if (contextClassLoader == null)
  16. return null;
  17. SecurityManager sm = System.getSecurityManager();
  18. if (sm != null) {
  19. ClassLoader.checkClassLoaderPermission(contextClassLoader,
  20. Reflection.getCallerClass());
  21. }
  22. return contextClassLoader;
  23. }
  24. }

contextClassLoader只是一個成員變量,經過setContextClassLoader()方法設置,經過getContextClassLoader()得到。 

每一個Thread都有一個相關聯的ClassLoader,默認是AppClassLoader。而且子線程默認使用父線程的ClassLoader除非子線程特別設置

12 自定義ClassLoader的做用

常見的用法是將Class文件按照某種加密手段進行加密,而後按照規則編寫自定義的ClassLoader進行解密,這樣咱們就能夠在程序中加載特定了類,而且這個類只能被咱們自定義的加載器進行加載,提升了程序的安全性。

相關文章
相關標籤/搜索