1. 程式人生 > >class.forName(org.postgresql.Driver)驅動註冊過程涉及到的原始碼中的方法

class.forName(org.postgresql.Driver)驅動註冊過程涉及到的原始碼中的方法

當執行class.forName(“org.postgresql.Driver”);載入驅動時執行以下方法

org.postgresql.Driver.java

public class Driver implements java.sql.Driver {

  private static Driver registeredDriver;//靜態變數,再註冊方法中指向自己
  private static final Logger PARENT_LOGGER = Logger.getLogger("org.postgresql");
  private static final Logger LOGGER = Logger.getLogger("org.postgresql.Driver"
); private static SharedTimer sharedTimer = new SharedTimer(); static {//在類被載入時自動執行 try { register();//執行註冊驅動方法 } catch (SQLException e) { throw new ExceptionInInitializerError(e); } } /* *用來註冊驅動 */ public static void register() throws SQLException { if (isRegistered()) {//如果已經註冊過,則丟擲異常提示
throw new IllegalStateException( "Driver is already registered. It can only be registered once."); } Driver registeredDriver = new Driver();//將自己例項化 DriverManager.registerDriver(registeredDriver);//把自己放入DriverManager中,進行註冊的下一步 Driver.registeredDriver = registeredDriver;//將靜態變數的引用指向自己,deregister中用到,deregister是用來登出驅動的方法
} /* *用來登出驅動 */ public static void deregister() throws SQLException { if (!isRegistered()) { throw new IllegalStateException( "Driver is not registered (or it has not been registered using Driver.register() method)"); } /*registeredDriver 用在了下面*/ DriverManager.deregisterDriver(registeredDriver); registeredDriver = null; }

java.sql.DriverManager
“`
public class DriverManager {
/* List of registered JDBC drivers,註冊的JDBC驅動都會存放在這個容器中,CopyOnWrite是執行緒安全的,用於讀多寫少的併發場景。
CopyOnWrite容器即寫時複製的容器。通俗的理解是當我們往一個容器新增元素的時候,不直接往當前容器新增,
而是先將當前容器進行Copy,複製出一個新的容器,
然後新的容器裡新增元素,新增完元素之後,再將原容器的引用指向新的容器。
這樣做的好處是我們可以對CopyOnWrite容器進行併發的讀,而不需要加鎖,
因為當前容器不會新增任何元素。
所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器*/。
private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();
private static volatile int loginTimeout = 0;
private static volatile java.io.PrintWriter logWriter = null;
private static volatile java.io.PrintStream logStream = null;
// Used in println() to synchronize logWriter
private final static Object logSync = new Object();

/* Prevent the DriverManager class from being instantiated. */
private DriverManager(){}


/**
 * Load the initial JDBC drivers by checking the System property
 * jdbc.properties and then use the {@code ServiceLoader} mechanism
 */
 /*
 這裡的靜態程式碼塊用來執行loadInitialDrivers方法,
 用於載入配置在jdbc.drivers系統屬性內的驅動Driver,
 配置在jdbc.drivers中的驅動driver將會首先被載入

 */
static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
    String drivers;
    try {
    /*
        AccessController是許可權控制器,他提供了一個預設的安全策略執行機制,
        它使用棧檢查來決定潛在不安全的操作是否被允許doPrivileged是他常用的一個方法,
        doPrivileged 方法能夠使一段受信任程式碼獲得更大的許可權,
        甚至比呼叫它的應用程式還要多,可做到臨時訪問更多的資源,
        該方法被@CallerSensitive修飾,
        這個註解是JVM中專用的註解,在下面另開一塊進行記錄
    */
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
            //System.getProperty()是獲取系統屬性的方法,
            //DriverManager中的jdbc.drivers這個系統屬性不是本來系統自帶的,
            //需要使用者自己設定採用。如果不設定,則為null。
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    // If the driver is packaged as a Service Provider, load it.
    // Get all the drivers through the classloader
    // exposed as a java.sql.Driver.class service.
    // ServiceLoader.load() replaces the sun.misc.Providers()
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {

            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });
    println("DriverManager.initialize: jdbc.drivers = " + drivers);

    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }

}
/*
在執行完loadInitialDrivers方法後,
由於Driver類中registerDriver方法的呼叫,會執行該方法
該方法用來註冊jdbc驅動,由於傳參不同,在此處呼叫了他的重寫方法
*/
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
/*
這個方法比上面的方法多了一個DriverAction引數,這個DriverAction是一個介面,
也在java.sql包中,裡面只規定了一個void deregister()方法
*/
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {

    /* Register the driver if it has not already been added to our list */
    /*
        執行到此驅動註冊正式完成,我們可以註冊多個驅動,
        這個驅動的註冊實際就是在下面的程式碼中將當前的Driver類
        放入一開始定義的靜態的CopyOnWriteArrayList容器的registeredDrivers中
        放入之後,在DriverManager類中的其他方法中都可以看到從這個容器中
        遍歷拿出driver使用
    */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }

    println("registerDriver: " + driver);

}

“`
@CallerSensitive在類載入過過程中是可以常常看到的,他是用來找到真正發起反射請求的類的,這個註解是為了堵住漏洞用的。
用@CallerSensitive進行註解,通過此方法獲取class時會跳過鏈路上所有的有@CallerSensitive註解的方法的類,直到遇到第一個未使用該註解的類

     曾經有黑客通過構造雙重反射來提升許可權,原理是當時反射只檢查固定深度的呼叫者的類,看它有沒有特權,例如固定看兩層的呼叫者(getCallerClass(2))。如果我的類本來沒足夠許可權群訪問某些資訊,那我就可以通過雙重反射去達到目的:反射相關的類是有很高許可權的,而在 我->反射1->反射2 這樣的呼叫鏈上,反射2檢查許可權時看到的是反射1的類,這就被欺騙了,導致安全漏洞。使用CallerSensitive後,getCallerClass不再用固定深度去尋找actual caller(“我”),而是把所有跟反射相關的介面方法都標註上CallerSensitive,搜尋時凡看到該註解都直接跳過,這樣就有效解決了前面舉例的問題

     當”我“->反射1->反射2->反射3->反射4->…反射->N,從”我“開始後的每個引用物件,當我引用下一個物件(物件1)時,首先會被裝載,驗證,這個時候把這個物件在這個過程中沒呼叫過的反射介面都標記上@callSentitive,然後物件1引用物件2後重復這個過程一直到N,那麼在N呼叫GetCallCalss時跳過這中間所有已經被標記的物件,最後到達未被標記的“我”。