1. 程式人生 > >Spring資源載入基礎ClassLoader

Spring資源載入基礎ClassLoader

1 ClassLoader工作機制

1.1 ClassLoader作用

尋找類位元組碼檔案並構造出類在JVM內部表示的元件.負責執行時查詢和裝入Class位元組碼檔案

1.2 裝載步驟

1.2.1 裝載

查詢裝載class位元組碼檔案

1.2.2 連結

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

1.2.2.1 校驗

檢查裝載Class檔案的正確性

1.2.2.2 準備

給類的靜態變數分配儲存空間

1.2.2.3 解析

將符號引用轉換為直接引用

1.2.3 初始化

對類的靜態變數,方法,程式碼塊執行初始化操作

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檔案

其中Extention ClassLoader & AppClassLoaderClassLoader的子類,根載入器是擴充套件載入器的父載入器,擴充套件載入器是應用載入器的父載入器。在預設情況下使用應用載入器

3 JVM載入機制

類載入採用“全盤負責委託機制”。

“全盤負責”:在類載入時指定一個ClassLoader,除非顯示宣告其他的載入器,否則該類所依賴的類以及引用的類都由該載入器載入。

“委託機制”:類載入時優先委託父載入器尋找並載入目標類,只有在父載入器沒有找到的情況下,才從自己的classpath路徑下查詢並載入目標類。該點主要是出於安全的考慮,在classpath路徑下定義JDK中已經存在的類,由於該機制,JDK中的類都由父載入器開始查詢並載入.每個載入器都有快取,在委託時優先查詢快取,如果快取中存在,那麼直接返回,否者才執行查詢和載入


4 類例項,類描述物件以及ClassLoader關係

類檔案被載入後,在JVM

內部對應擁有一個java.lang.Class類描述物件,類的每個例項擁有類描述物件的引用,類描述物件擁有類載入器的引用


啟動順序

Bootstrap ClassLoader-->Extention ClassLoader-->AppClass LoaderBootstrap 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


public class Launcher {
    //Bootstrap ClassLoader載入路徑
    private static String bootClassPath = System.getProperty("sun.boot.class.path");

    //該處定義的App ClassLoader
    private ClassLoader loader;

    public Launcher() {
        // Create the extension class loader
        ClassLoader extcl;
        try {
            //建立Extention ClassLoader
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {......}

        // Now create the class loader to use to launch the application
        try {
            //建立AppClassLoader並傳入父載入器,由此可看AppClassLoader父載入器是Extention ClassLoader
            loader = AppClassLoader.getAppClassLoader(extcl);
        } catch (IOException e) {......}

        //設定AppClassLoader為當前執行緒上下問類載入器
        // Also set the context class loader for the primordial thread.
        Thread.currentThread().setContextClassLoader(loader);
    }

    public ClassLoader getClassLoader() {
        return loader;
    }

    static class ExtClassLoader extends URLClassLoader {}

    static class AppClassLoader extends URLClassLoader {}
}

該類中定義了Bootstrap ClassLoader載入路徑,同時建立了Extention ClassLoader以及AppClassLoader,如下是Bootstrap ClassLoader載入路徑資訊

/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/resources.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/sunrsasign.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jsse.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jce.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/charsets.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jfr.jar
/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並設定父載入器

static class ExtClassLoader extends URLClassLoader {
        public static ExtClassLoader getExtClassLoader() throws IOException
        {
            final File[] dirs = getExtDirs();//獲得查詢路徑下的所有檔案
            try {
                return AccessController.doPrivileged(
                    new PrivilegedExceptionAction<ExtClassLoader>() {
                        public ExtClassLoader run() throws IOException {
                            ......
			    //建立Extention ClassLoader
                            return new ExtClassLoader(dirs);
                        }
                    });
            } catch (java.security.PrivilegedActionException e) {......}
    }
public ExtClassLoader(File[] dirs) throws IOException {
            //呼叫父類URLClassLoader的建構函式並傳入父載入器,此時為null
            super(getExtURLs(dirs), null, factory);
 }
private static File[] getExtDirs() {
            //Extention ClassLoader載入路徑
            String s = System.getProperty("java.ext.dirs");
            File[] dirs;
            if (s != null) {
                StringTokenizer st = new StringTokenizer(s, File.pathSeparator);
                int count = st.countTokens();
                dirs = new File[count];
                for (int i = 0; i < count; i++) {
                    dirs[i] = new File(st.nextToken());
                }
            } else {
                dirs = new File[0];
            }
            return dirs;
        }
}
private static URL[] getExtURLs(File[] dirs) throws IOException {
    Vector<URL> urls = new Vector<URL>();
    for (int i = 0; i < dirs.length; i++) {
        String[] files = dirs[i].list();
        if (files != null) {
            for (int j = 0; j < files.length; j++) {
                if (!files[j].equals("meta-index")) {
                    File f = new File(dirs[i], files[j]);
                    urls.add(getFileURL(f));
                }
            }
        }
   }
   URL[] ua = new URL[urls.size()];
   urls.copyInto(ua);
   return ua;
}
static URL getFileURL(File file) {
    try {
        file = file.getCanonicalFile();
    } catch (IOException e) {}

    try {
        return ParseUtil.fileToEncodedURL(file);
    } catch (MalformedURLException e) {
            // Should never happen since we specify the protocol...
            throw new InternalError(e);
    }
}

如下是java.ext.dirs的輸出資訊

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

6.3 AppClassLoader原始碼

1.獲得java.classs.path路徑下的所有檔案

2.解析檔案路徑為URL

3.呼叫父類建構函式建立AppClassLoader並設定父類

static class AppClassLoader extends URLClassLoader {
        public static ClassLoader getAppClassLoader(final ClassLoader extcl)
            throws IOException
        {
            //AppClassLoader載入路徑
            final String s = System.getProperty("java.class.path");
            final File[] path = (s == null) ? new File[0] : getClassPath(s);

            return AccessController.doPrivileged(
                new PrivilegedAction<AppClassLoader>() {
                    public AppClassLoader run() {
                    URL[] urls =
                        (s == null) ? new URL[0] : pathToURLs(path);
                    return new AppClassLoader(urls, extcl);
                }
            });
        }

        AppClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent, factory);
        }
    }
}
private static URL[] pathToURLs(File[] path) {
        URL[] urls = new URL[path.length];
        for (int i = 0; i < path.length; i++) {
            urls[i] = getFileURL(path[i]);
        }
        // DEBUG
        //for (int i = 0; i < urls.length; i++) {
        //  System.out.println("urls[" + i + "] = " + '"' + urls[i] + '"');
        //}
        return urls;
}

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()獲得當前載入器的父載入器

//Main為自定義Class
System.out.println(Main.class.getClassLoader());
System.out.println(Main.class.getClassLoader().getParent());

System.out.println(Main.class.getClassLoader().getParent().getParent());
System.out.println(Boolean.class.getClassLoader());
[email protected]
su[email protected]

null
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 ClassLoaderAppClassLoader例項。並將ExtClassLoader設定為AppClassLoader的父載入器。BootstrapClassCloader沒有父載入器,但是它卻可以作為一個ClassLoader的父載入器。比如ExtClassLoader。這也可以解釋之前通過ExtClassLoadergetParent方法獲取為Null的現象.我們可以分析getParent()的原始碼

父載入器可以直接由外界指定,如果外界不指定,那麼採用AppClassLoader作為父載入器

//在建立ExtClassLoader時,採用super(getExtURLs(dirs), null, factory).所以獲得的是null

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

9 重要方法

9.1 URL getResource(String name)

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

    public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

9.2 Enumeration<URL> getResources(String name)

查詢給定名稱的所有資源。資源的名稱是一個/分隔的路徑名稱標識資源,優先查詢父載入器,如果找不到在從Bootstrap ClassLoader路徑中查詢並載入,該模式下支援正則匹配

    public Enumeration<URL> getResources(String name) throws IOException {
        @SuppressWarnings("unchecked")
        Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
        if (parent != null) {
            tmp[0] = parent.getResources(name);
        } else {
            tmp[0] = getBootstrapResources(name);
        }
        tmp[1] = findResources(name);

        return new CompoundEnumeration<>(tmp);
    }

9.3 URL getSystemResource(String name)

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

    public static URL getSystemResource(String name) {
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            return getBootstrapResource(name);
        }
        return system.getResource(name);
    }

9.4 Enumeration<URL> getSystemResources(String name)

採用AppClassLoader載入給定名稱的所有資源

    public static Enumeration<URL> getSystemResources(String name)
        throws IOException
    {
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            return getBootstrapResources(name);
        }
        return system.getResources(name);
    }

9.5 URL findResource(String name)

查詢具有給定名稱的資源。類載入器實現應覆蓋此方法以指定在哪裡查詢資源

    protected URL findResource(String name) {
        return null;
    }

9.6 Enumeration<URL> findResources(String name)

查詢具有給定名稱的所有資源。類載入器實現應覆蓋此方法以指定在哪裡查詢資源

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

9.7 loadClass

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

public abstract class ClassLoader {
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) {
                try {
                    if (parent != null) {
                        //遞迴呼叫,查詢父載入器
                        c = parent.loadClass(name, false);
                    } else {
                     	//呼叫Bootstrap Classloader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {......}

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
		    //呼叫findClass
                    c = findClass(name);
                    ....
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}
protected final Class<?> findLoadedClass(String name) {
    if (!checkName(name))
        return null;
    return findLoadedClass0(name);
}

private native final Class<?> findLoadedClass0(String name);
private Class<?> findBootstrapClassOrNull(String name)
{
    if (!checkName(name)) return null;
    return findBootstrapClass(name);
}

private native Class<?> findBootstrapClass(String name);
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

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

將類檔案位元組碼陣列裝換成JVM內部的java.lang.Class物件,位元組陣列可以從本地系統,遠端網路獲取

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }
    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;
    }
private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

9.9 findSystemClass(String)

從本地檔案系統載入Class檔案,如果本地檔案系統中不存在該Class檔案,丟擲ClassNotFoundError。該方法是JVM預設使用的裝載器

protected final Class<?> findSystemClass(String name)
        throws ClassNotFoundException
    {
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            if (!checkName(name))
                throw new ClassNotFoundException(name);
            Class<?> cls = findBootstrapClass(name);
            if (cls == null) {
                throw new ClassNotFoundException(name);
            }
            return cls;
        }
        return system.loadClass(name);
    }

9.10 findLoadedClass(String name)

該方法檢視ClassLoader是否已經載入了某個類,如果載入就返回Class物件,否則返回null,如果強行載入已經載入的類會丟擲異常

    protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }

    private native final Class<?> findLoadedClass0(String name);

10 如何自定義ClassLoader

1.編寫一個類繼承ClassLoader

2.重寫findClass 方法

3.findClass中呼叫defineClass即可

11 Context ClassLoader 執行緒上下文類載入器


public class Thread implements Runnable {

/* The context ClassLoader for this thread */
   private ClassLoader contextClassLoader;

   public void setContextClassLoader(ClassLoader cl) {
       SecurityManager sm = System.getSecurityManager();
       if (sm != null) {
           sm.checkPermission(new RuntimePermission("setContextClassLoader"));
       }
       contextClassLoader = cl;
   }

   public ClassLoader getContextClassLoader() {
       if (contextClassLoader == null)
           return null;
       SecurityManager sm = System.getSecurityManager();
       if (sm != null) {
           ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                  Reflection.getCallerClass());
       }
       return contextClassLoader;
   }
}

contextClassLoader只是一個成員變數,通過setContextClassLoader()方法設定,通過getContextClassLoader()獲得。 

每個Thread都有一個相關聯的ClassLoader,預設是AppClassLoader。並且子執行緒預設使用父執行緒的ClassLoader除非子執行緒特別設定

12 自定義ClassLoader的作用

常見的用法是將Class檔案按照某種加密手段進行加密,然後按照規則編寫自定義的ClassLoader進行解密,這樣我們就可以在程式中載入特定了類,並且這個類只能被我們自定義的載入器進行載入,提高了程式的安全性。

13 參考

http://blog.csdn.net/xyang81/article/details/7292380

http://blog.csdn.net/briblue/article/details/54973413#t36

JDK原始碼

相關推薦

Spring資源載入基礎ClassLoader

1 ClassLoader工作機制1.1 ClassLoader作用尋找類位元組碼檔案並構造出類在JVM內部表示的元件.負責執行時查詢和裝入Class位元組碼檔案1.2 裝載步驟1.2.1 裝載查詢裝載

Android動態載入基礎 ClassLoader工作機制

基本資訊 類載入器ClassLoader 早期使用過Eclipse等Java編寫的軟體的同學可能比較熟悉,Eclipse可以載入許多第三方的外掛(或者叫擴充套件),這就是動態載入。這些外掛大多是一些Jar包,而使用外掛其實就是動態載入Jar包裡的Class進行工作。這其實

Spring資源載入

1 資源介面,繼承關係,類簡介1.1 資源介面Resource介面為應用提供了底層資源訪問的能力。該介面的主要作用是描述實際的資源。public interface Resource extends InputStreamSource { //資源是否存在 boolean

spring MVC載入靜態資源(js、圖片、css等)

這裡介紹三種在spring mvc框架中,載入靜態資源的方式: 如果在web.xml中配置了以下攔截,載入不了靜態資源的問題: <!-- 對映所有的請求 -->     <servlet-mapping>  &nb

Spring-統一資源載入策略

前言 在前面我們初步簡單的分析了一下BeanFactory的體系結構,第一步我們需要從配置檔案中讀取配置資訊,JDK所提供的訪問資源的類(如java.net.URL、File等),並不能很好的滿足各種底層資源的訪問需求,比如缺少從類路徑或者Web容器的上下文獲

Spring中的資源載入

  大家也都知道JDK的類載入器:BootStrap ClassLoader、ExtenSion ClassLoader、Application ClassLoader;也使用了雙親委派模型,主要是為了防止程式碼注入;   但是我一直在想那Spring 這麼牛逼的框架到底有沒有自定義的類載入器呢?   S

spring專案載入不出來靜態資源

方法1: 攔截器中增加針對靜態資源不進行過濾(涉及spring-mvc.xml) <!-- 添加註解驅動 --> <mvc:annotation-driven/> <!-- 通過mvc:resources設定靜態資源,這樣servlet就會處理這些靜態資源,而不通過控

Spring系列-深入研究資源載入

java本身的資源載入方式 研究spring對於資源管理前,我們先看看java中的資源載入,就是通過classLoader類載入器進行的,看看它載入資源的方式: 如何使用,來看一個例子: package com.demo; import java.

spring boot 載入資源路徑配置和classpath問題

1、spring boot預設載入檔案的路徑: /META-INF/resources/ /resources/ /static/ /public/ 我們也可以從spring boot原始碼也可以看到: priva

spring原始碼解析--基礎容器XmlBeanFactory 載入過程

一、demo示例1、實體類package cn.jin.test; public class MyTestBean { private String str = "this is test"; public String getStr() { return s

簡說Spring中的資源載入

> 宣告: 本文若有 任何紕漏、錯誤,請不吝指正!謝謝! ## 問題描述 遇到一個關於資源載入的問題,因此簡單的記錄一下,對`Spring`資源載入也做一個記錄。 問題起因是使用了`@PropertySource`來進行配置檔案載入,配置路徑時,沒有使用關鍵字`classpath`來指明從`classp

java類載入器——ClassLoader

web rac rgb 好的 全盤負責機制 安全 trac 字節 如何 Java的設計初衷是主要面向嵌入式領域,對於自己定義的一些類,考慮使用依需求載入原則。即在程序使用到時才載入類,節省內存消耗,這時就可以通過類載入器來動態載入。 假設你平時僅僅是做web開發,那應該

Java類載入ClassLoader的解析

index html dir obj ble body 6.4 odin 普通 //參考 : http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 類載入器基本概念 類載

spring和hibernate 基礎的增刪改

seh 基礎 except str @override log string return .get 所有的dao層可以有個superDao 比如:BaseHibernateDao 繼承的類是hibernate4 public class BaseHibernateDao

[Spring框架]Spring 事務管理基礎入門總結.

復制 tor junit4 dao img bubuko 說過 應該 pat 前言:在之前的博客中已經說過了數據庫的事務, 不過那裏面更多的是說明事務的一些鎖機制, 今天來說一下Spring管理事務的一些基礎知識. 之前的文章: [數據庫事務與鎖]詳解一: 徹底理解數據庫

spring資源讀取

性問題 方案 耦合性 amp file 接口 loading 配置 特殊   spring中對資源的讀取提供有自己的新方案,比傳統IO操作更加智能方便。   對於所有的資源處置,spring提供了Resource接口,該接口中的方法都是通過其父接口InputStreamSo

Spring Boot實踐——基礎和常用配置

develop google art 容器 .sql pem -i 未定義 eve 借鑒:https://blog.csdn.net/j903829182/article/details/74906948 一、Spring Boot 啟動註解說明 @SpringBoot

Spring batch 入門基礎

clas 環境 html 日誌 過程 容易 計劃 bre .html Spring Batch是一個輕量級的,完全面向Spring的批處理框架,可以應用於企業級大量的數據處理系統。Spring Batch以POJO和大家熟知的Spring框架為基礎,使開發者更容易的訪問和利

Unity專案資源載入與管理

此前Unity官方技術支援工程師田彪為大家分享了Unity專案設計與管理的一些注意事項,其中最重要的莫過於資源載入與管理了。今天這篇文章將由Unity官方技術支援工程師柳振東,針對一些常見的Unity專案資源載入與管理問題進行解答。   將材質打包為AssetBundle,執行時使

自定義spring boot starter三部曲之三:原始碼分析spring.factories載入過程

本文是《自定義spring boot starter三部曲》系列的終篇,前文中我們開發了一個starter並做了驗證,發現關鍵點在於spring.factories的自動載入能力,讓應用只要依賴starter的jar包即可,今天我們來分析Spring和Spring boot原始碼,瞭解s