1. 程式人生 > >DriverManager.getConnection()方法涉及到的原始碼詳解

DriverManager.getConnection()方法涉及到的原始碼詳解

DriverManager.getConnection一共有四個過載方法,前三個由public修飾,用來獲取不同型別的引數,這三個getConnection實際相當於一個入口,他們最終都會return第四個私有化的getConnection方法,最終向第四個私有化方法的傳入引數都是url,java.util.Properties,以及Reflection.getCallerClass(),這個方法是native的

    其中Reflection.getCallerClass()是反射中的一個方法,這個方法用來返回他的呼叫類,也就說是哪個類呼叫了這個方法,Reflection類位於呼叫棧中的0幀位置,在JDK7以前,該方法可以傳入int n返回呼叫棧中從0幀開始的第n幀中的類,在JDK7中,需要設定java命令列選項Djdk.reflect.allowGetCallerClass來使用該方法,到了JDK8時,再呼叫該方法會導致UnsupportedOperationException異常。

    JDK8中getCallerClass使用方法變更為getCallerClass(),Reflection.getCallerClass()方法呼叫所在的方法必須用@CallerSensitive進行註解,通過此方法獲取class時會跳過鏈路上所有的有@CallerSensitive註解的方法的類,直到遇到第一個未使用該註解的類,避免了用Reflection.getCallerClass(int n) 這個過時方法來自己做判斷。

    在這裡每個getConnection都是用CallerSensitive修飾的,呼叫getCallerClass應該是獲取外面使用DriverManager.getConnection()的類的名稱,即在class A中呼叫了DriverManager.getConnection(),則返回class A。

    在私有化的getConnection中涉及到了類載入器的一些相關概念,

https://blog.csdn.net/briblue/article/details/54973413

    執行緒上下文類載入器的適用場景:

        1. 當高層提供了統一介面讓低層去實現,同時又要是在高層載入(或例項化)低層的類時,必須通過執行緒上下文類載入器來幫助高層的ClassLoader找到並載入該類。

        2. 當使用本類託管類載入,然而載入本類的ClassLoader未知時,為了隔離不同的呼叫者,可以取呼叫者各自的執行緒上下文類載入器代為託管。

java.sql.DriverManager.java

/*
該方法獲取url以及存放user與password的持久化結果集,並獲取請求連線的外部類,將其傳入私有化的getConnection方法中進行處理
*/
@CallerSensitive public static Connection getConnection(String url,java.util.Properties info) throws SQLException { return (getConnection(url, info, Reflection.getCallerClass())); }
/*
該方法獲取url以及string型別的user和password,並將user和password放入properties中,然後獲取請求連線的外部類,將他們都傳入私有化的getConnection方法
*/
@CallerSensitive
public static Connection getConnection(String url,String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties(); if (user != null) { info.put("user", user); } if (password != null) { info.put("password", password); } return (getConnection(url, info, Reflection.getCallerClass())); }
/*
該方法只獲取url,使用者名稱及密碼資訊也存放在url中,在方法中例項化一個空的properties,然後獲取請求連線的外部類,將他們都傳入私有化的getConnection方法
*/
@CallerSensitive
public static Connection getConnection(String url)throws SQLException {
    java.util.Properties info = new java.util.Properties();
    return (getConnection(url, info, Reflection.getCallerClass()));
}
/*
前面三個方法最終將傳入的url以及存放使用者名稱密碼或空的properties以及請求連線的外部類傳參進來,在這裡進行處理
*/
private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
     * When callerCl is null, we should check the application's
     * (which is invoking this class indirectly)
     * classloader, so that the JDBC driver class outside rt.jar
     * can be loaded from here.
     */
    /*
    * caller.getClassLoader()類載入器,獲得DriverManager.getConnection()呼叫者的classLoader,
    * ClassLoader主要對類的請求提供服務,當JVM需要某類時,它根據名稱向ClassLoader要求這個類,然後由ClassLoader返回 這個類的class物件。
    * 這裡對於類載入器的檢查應該是涉及到了委託模型的知識,好像是為了確保呼叫getConnection的類的classLoader能夠載入外部的JDBC驅動類,
    * 這裡網上部落格有一篇這麼說的:
    * ContextClassLoader可以設定,預設的都是systemClassLoader 
    * ContextClassLoader在web容器裡面用到的比較多,它可以讓父classLoader訪問到子的classLoader的class,如JDBC驅動程式 
    * 就是,在父classLoader(bootstrap)載入的類裡面,訪問到了子classLoader(SystemClassLoader)才能載入的類(com.mysql.jdbc.ReplicationDriver)。 
    */
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        /*
        * 在這裡是如果上面獲取呼叫此方法的類的classLoader為空,就無法載入驅動類了,
        * 這裡涉及到了雙親委派模型和委派鏈的知識,網上的解釋是這樣的:
        * Thread context class loader存在的目的主要是為了解決parent delegation機制下無法乾淨的解決的問題。假如有下述委派鏈: 
        * ClassLoader A -> System class loader -> Extension class loader -> Bootstrap class loader 
        * 那麼委派鏈左邊的ClassLoader就可以很自然的使用右邊的ClassLoader所載入的類。 
        * 但如果情況要反過來,是右邊的ClassLoader所載入的程式碼需要反過來去找委派鏈靠左邊的ClassLoader去載入東西怎麼辦呢?
        * 沒轍,parent delegation是單向的,沒辦法反過來從右邊找左邊。 
        * 這種情況下就可以把某個位於委派鏈左邊的ClassLoader設定為執行緒的context class loader,
        * 這樣就給機會讓程式碼不受parent delegation的委派方向的限制而載入到類了。
        */
        /*
        * DriverManager在rt.jar裡面,它的類載入器上啟動類載入器。
        * 而資料庫的driver驅動類是放在classpath裡面的,啟動類載入器是不能載入的。
        * 所以,如果嚴格按照雙親委派模型,是沒辦法解決的。
        * 而這裡的解決辦法是:通過呼叫類的類載入器去載入。
        * 而如果呼叫類的載入器是null,就設定為執行緒的上下文類載入器
        */
        /*       
        * Thread.currentThread()方法來獲取系統當前正在執行的一條執行緒,
        * getContextClassLoader方法獲取classLoader
        * 這裡獲取的是執行緒上下文類載入器
        */
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
    if(url == null) {
        throw new SQLException("The url cannot be null", "08001");
    }
    println("DriverManager.getConnection(\"" + url + "\")");
    SQLException reason = null;
    /*
    * 在註冊的驅動列表中遍歷,如果順利獲取,那麼在for中的return中結束driverManager負責的連接獲取
    */
    for(DriverInfo aDriver : registeredDrivers) {
    //進行驗證,在列表中取出使用者註冊的需要獲取連線的驅動類,這裡的列表應該是存放的不同執行緒註冊的驅動
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                //呼叫driver的connect方法,並返回獲得的驅動
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }
    }
    // if we got here nobody could connect.
    if (reason != null)    {
        println("getConnection failed: " + reason);
        throw reason;
    }
    println("getConnection: no suitable driver found for "+ url);
    throw new SQLException("No suitable driver found for "+ url, "08001");
}

在這個isDriverAllowed方法中主要是判斷列表裡面取出的驅動類是不是使用者註冊的,需要獲取連線的那個。

private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
    boolean result = false;
    if(driver != null) {
        Class<?> aClass = null;
        try {
            // object.getClass() 獲取物件的型別即Class類的物件,通過呼叫Class類提供的方法可以獲取這個類的類名稱(包名+類名)以及類載入器。
            //這裡獲取的並不是這個驅動的類,只是為了獲取這個驅動的名稱,然後通過當前獲取的類載入器載入這個名稱的驅動類,目的是為了校驗
            //校驗當前載入的這個驅動類是不是使用者註冊的那個
            // 方法返回與給定字串名的類或介面的Class物件,使用給定的類載入器
            aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
        } catch (Exception ex) {
            result = false;
        }
        // 只有同一個類載入器中的Class使用==比較時才會相等,此處就是校驗使用者註冊Driver時該Driver所屬的類載入器與呼叫時的是否為同一個
        result = ( aClass == driver.getClass() ) ? true : false;
    }

    return result;
}

不同廠商的Driver類都實現了java.sql.Driver介面
package org.postgresql.Driver.java

  @Override
  public Connection connect(String url, Properties info) throws SQLException {
    Properties defaults;
    //判斷url字首格式
    if (!url.startsWith("jdbc:postgresql:")) {
      return null;
    }
    /****************************這塊是為了獲取傳入的使用者名稱密碼****************************/
    try {
      //獲取擴大許可權的properties
      defaults = getDefaultProperties();
    } catch (IOException ioe) {
      throw new PSQLException(GT.tr("Error loading default settings from driverconfig.properties"),
          PSQLState.UNEXPECTED_ERROR, ioe);
    }

    // override defaults with provided properties
    //在這裡就是用擴大許可權的properties初始化當前方法的properties
    Properties props = new Properties(defaults);
    if (info != null) {
    //將properties中的鍵名存入set容器中,這裡存入的是user和password兩個鍵名
      Set<String> e = info.stringPropertyNames();
      for (String propName : e) {
        //根據鍵值獲取info的value,即獲取使用者名稱和密碼
        String propValue = info.getProperty(propName);
        if (propValue == null) {
          throw new PSQLException(
              GT.tr("Properties for the driver contains a non-string value for the key ")
                  + propName,
              PSQLState.UNEXPECTED_ERROR);
        }
        //將獲取的鍵值對,即使用者名稱密碼從info這個properties中取出放入當前方法的properties中
        props.setProperty(propName, propValue);,
      }
    }
    /**************************************************************************************/
    // parse URL and add more properties
    //通過pareURL方法判斷url格式並解析url並將解析出的地址埠號以及其他引數資訊放入props中
    //如果url格式錯誤,則返回null
    if ((props = parseURL(url, props)) == null) {
      LOGGER.log(Level.SEVERE, "Error in url: {0}", url);
      return null;
    }
    try {
      // Setup java.util.logging.Logger using connection properties.
      //用properties中的屬性,建日誌
      setupLoggerFromProperties(props);

      LOGGER.log(Level.FINE, "Connecting with URL: {0}", url);

      // Enforce login timeout, if specified, by running the connection
      // attempt in a separate thread. If we hit the timeout without the
      // connection completing, we abandon the connection attempt in
      // the calling thread, but the separate thread will keep trying.
      // Eventually, the separate thread will either fail or complete
      // the connection; at that point we clean up the connection if
      // we managed to establish one after all. See ConnectThread for
      // more details.

      //獲取loginTimeout屬性值,即等待建立連線時間,如果未設定則返回0毫秒
      long timeout = timeout(props);
      //如果為0則直接返回建立的連線
      if (timeout <= 0) {
        return makeConnection(url, props);
      }
      //如果等待連線時間不為0,則建立一個執行緒等待連線
      ConnectThread ct = new ConnectThread(url, props);
      Thread thread = new Thread(ct, "PostgreSQL JDBC driver connection thread");
      //設定為守護執行緒,預設的執行緒都為使用者執行緒,守護執行緒優先順序較低,沒有使用者執行緒,都是守護執行緒,那麼JVM結束
      thread.setDaemon(true); // Don't prevent the VM from shutting down
      thread.start();   
      return ct.getResult(timeout);
    } catch (PSQLException ex1) {
      LOGGER.log(Level.SEVERE, "Connection error: ", ex1);
      // re-throw the exception, otherwise it will be caught next, and a
      // org.postgresql.unusual error will be returned instead.
      throw ex1;
    } catch (java.security.AccessControlException ace) {
      throw new PSQLException(
          GT.tr(
              "Your security policy has prevented the connection from being attempted.  You probably need to grant the connect java.net.SocketPermission to the database server host and port that you wish to connect to."),
          PSQLState.UNEXPECTED_ERROR, ace);
    } catch (Exception ex2) {
      LOGGER.log(Level.SEVERE, "Unexpected connection error: ", ex2);
      throw new PSQLException(
          GT.tr(
              "Something unusual has occurred to cause the driver to fail. Please report this exception."),
          PSQLState.UNEXPECTED_ERROR, ex2);
    }
  }

parseURL方法主要用於解析url
org.postgresql.Driver.java

  public static Properties parseURL(String url, Properties defaults) {
    Properties urlProps = new Properties(defaults);

    String l_urlServer = url;
    String l_urlArgs = "";

    int l_qPos = url.indexOf('?');//獲取'?'第一次出現的位置,若未出現則返回-1
    //如果有'?'則說明開發者在url還帶了其他引數中,那麼就把協議與引數資訊截取出來分別存放到l_urlServer和l_uerArgs中
    if (l_qPos != -1) {
      l_urlServer = url.substring(0, l_qPos);
      l_urlArgs = url.substring(l_qPos + 1);
    }
    //判斷協議頭
    if (!l_urlServer.startsWith("jdbc:postgresql:")) {
      return null;
    }
    //擷取未判斷的部分以繼續完成接下來的判斷,此時變數為可能為【//localhost:5432/test】
    l_urlServer = l_urlServer.substring("jdbc:postgresql:".length());
    //如果為【//localhost:5432/test】則開頭部分為//,若url格式為jdbc:postgresql:/則擷取為【/】,若url格式為jdbc:postgresql:database,則擷取為【datavase】
    if (l_urlServer.startsWith("//")) {
      //則繼續擷取為localhost:5432/test
      l_urlServer = l_urlServer.substring(2);
      //這裡應該是通過有沒有/來判斷url是否包含了資料庫資訊,如果沒有/則返回null,那麼Driver.getConnection()就返回null
      int slash = l_urlServer.indexOf('/');
      if (slash == -1) {
        return null;
      }
      //先在localhost:5432/test中截取出資料庫名即test,然後通過URLDecoder.decode解碼,
      //URLDecoder.decode方法是為了轉換空格等特殊字元,如String path2 = URLDecoder.decode(url.getPath(),”gbk”)
      //在JDK8中該方法已不推薦使用
      //將獲取的資料庫名放入properties中
      urlProps.setProperty("PGDBNAME", URLDecoder.decode(l_urlServer.substring(slash + 1)));
      //擷取地址與埠,並通過split方法分成陣列,如jdbc:postgresql://host1:port1,host2:port2/database
      String[] addresses = l_urlServer.substring(0, slash).split(",");
      StringBuilder hosts = new StringBuilder();
      StringBuilder ports = new StringBuilder();
      for (String address : addresses) {
        //獲得localhost:5432中':'最後出現的位置
        int portIdx = address.lastIndexOf(':');
        //判斷url格式是否正確,這裡的']'是為了判斷ipv6地址如jdbc:postgresql://[::1]:5740/accounting
        if (portIdx != -1 && address.lastIndexOf(']') < portIdx) {
          //擷取埠號放入portStr中
          String portStr = address.substring(portIdx + 1);
          //未知操作,沒有找到相關資訊
          try {
            // squid:S2201 The return value of "parseInt" must be used.
            // The side effect is NumberFormatException, thus ignore sonar error here
            Integer.parseInt(portStr); //NOSONAR
          } catch (NumberFormatException ex) {
            return null;
          }
          //將獲取的埠號和地址分別存入字串並在下面用','分割
          ports.append(portStr);
          hosts.append(address.subSequence(0, portIdx));
        } else {
            //如果沒有找到':'則說明url中並沒有埠號,那麼將address中的地址存放如hosts字串,埠號字串中存放配置預設埠號  
          ports.append("/*$mvn.project.property.template.default.pg.port$*/");
          hosts.append(address);
        }
        ports.append(',');
        hosts.append(',');
      }
      //設定stringbuffer的長度為現在長度-1,用於截掉末尾的','
      ports.setLength(ports.length() - 1);
      hosts.setLength(hosts.length() - 1);
      //將得到的地址和埠號存放入properties中
      urlProps.setProperty("PGPORT", ports.toString());
      urlProps.setProperty("PGHOST", hosts.toString());
    } else {
    //如果url格式是jdbc:postgresql:database或者jdbc:postgresql:/
    //則判斷若properties也為空,則使用預設埠號和本地地址,與url中填寫的資料庫
      if (defaults == null || !defaults.containsKey("PGPORT")) {
        urlProps.setProperty("PGPORT", "/*$mvn.project.property.template.default.pg.port$*/");
      }
      if (defaults == null || !defaults.containsKey("PGHOST")) {
        urlProps.setProperty("PGHOST", "localhost");
      }
      if (defaults == null || !defaults.containsKey("PGDBNAME")) {
        urlProps.setProperty("PGDBNAME", URLDecoder.decode(l_urlServer));
      }
    }

    // 解析url的引數,並將解析出的鍵值對放入urlProps中
    String[] args = l_urlArgs.split("&");
    for (String token : args) {
      if (token.isEmpty()) {
        continue;
      }
      int l_pos = token.indexOf('=');
      if (l_pos == -1) {
        urlProps.setProperty(token, "");
      } else {
        urlProps.setProperty(token.substring(0, l_pos), URLDecoder.decode(token.substring(l_pos + 1)));
      }
    }
    //將解析出完成的properties返回
    return urlProps;
  }

connect方法中呼叫到此方法得到loginTimeout屬性值
org.postgresql.Driver.java

  private static long timeout(Properties props) {
  //呼叫列舉類,獲得loginTimeout值即設定的等待連線時間屬性,如果開發者沒有在properties中規定該屬性
  //則返回預設值0,該預設值在列舉類中定義
    String timeout = PGProperty.LOGIN_TIMEOUT.get(props);
    if (timeout != null) {
      try {
        //返回long型別的毫秒數
        return (long) (Float.parseFloat(timeout) * 1000);
      } catch (NumberFormatException e) {
        LOGGER.log(Level.WARNING, "Couldn't parse loginTimeout value: {0}", timeout);
      }
    }
    /*
    * 如果timeout為null,則呼叫DriverManager的方法
    * 該方法單純將成員變數private static volatile int loginTimeout = 0返回
    * 如果未使用setLoginTimeout方法設定該值則返回預設值0
    * 但是我並沒有看懂在什麼情況下會呼叫該方法,因為如果props為null或者props中沒有loginTimeout屬性,應該返回了列舉類中預設的0值
    * 而如果丟擲異常也不會執行此處的return
    */
    return (long) DriverManager.getLoginTimeout() * 1000;
  }

如果connect方法中得到的等待連線時間不為0那麼將建立一個執行緒用來等待獲取連線

org.postgresql.Driver.java

  private static class ConnectThread implements Runnable {
    ConnectThread(String url, Properties props) {
      this.url = url;
      this.props = props;
    }

        public void run() {
          Connection conn;
          Throwable error;
          //error最終會賦值給該類的私有異常型別變數resultException
          try {
            //通過makeConnection獲取連線,並賦值error為null
            conn = makeConnection(url, props);
            error = null;
          } catch (Throwable t) {
          //如果出現執行異常則賦值error為t並將從conn為null
            conn = null;
            error = t;
          }

          synchronized (this) {
          //這個布林值未初始化,其賦值在getResult方法中
          //在getResult方法的執行緒中進行判斷,如果等待時間超時,或在執行wait()方法時出現異常,則將abandoned賦值為true
            if (abandoned) {
            //關閉連線
              if (conn != null) {
                try {
                  conn.close();
                } catch (SQLException e) {
                }
              }
            } else {
            //Connection result,Throwable resultException;為ConnectThread類的私有成員變數
            //如果在允許等待時間內獲取到了連線,則將連線引用到result,
            //同時將resultException異常變數引用到上面的error異常變數,該異常變數用在getResult方法中進行判斷
              result = conn;
              resultException = error;
              //執行到此呼叫notify方法喚醒等待的getResult方法所在的執行緒
              notify();
            }
          }
        }

    public Connection getResult(long timeout) throws SQLException {
    //該變數記錄執行時間,用於判斷連線是否超時
      long expiry = System.currentTimeMillis() + timeout;
      /*
      * 在這個同步程式碼塊裡面檢查連線是否獲取與連線是否超時
      * 連線的獲取線上程run()方法中進行,主執行緒執行start方法後run方法與getResult方法進入多執行緒執行場景
      */
      synchronized (this) {
        //迴圈檢查連線是否獲取,若獲取則返回Connection result
        while (true) {
          if (result != null) {
            return result;
          }
          //Throwable resultException 變數在run()執行緒中定義
          //如果resultException為null則說明成功獲取到了連線或尚未獲取連線,未初始化的類預設為null
          //如果不為null則說明在獲取連線過程中出現了執行異常Throwable
          if (resultException != null) {
            //判斷是否是SQLException異常,如果是丟擲此異常
            if (resultException instanceof SQLException) {
              resultException.fillInStackTrace();
              throw (SQLException) resultException;
            } else {
            //如果不是SQLException異常,則丟擲自定義異常
              throw new PSQLException(
                  GT.tr(
                      "Something unusual has occurred to cause the driver to fail. Please report this exception."),
                  PSQLState.UNEXPECTED_ERROR, resultException);
            }
          }
          //下面的abandoned變數在出現異常或超時時定義為true,run方法中對其進行判斷,如果abandoned為true,則終止嘗試連線
          //計算得到剩餘時間判斷是否超時
          long delay = expiry - System.currentTimeMillis();
          //如果超時則abandoned賦值為true並丟擲異常
          if (delay <= 0) {
            abandoned = true;
            throw new PSQLException(GT.tr("Connection attempt timed out."),
                PSQLState.CONNECTION_UNABLE_TO_CONNECT);
          }

          try {
            //該執行緒進入等待狀態,時間是允許連線時間的剩餘時間,期間可能會被run方法所線上程的notify方法喚醒
            wait(delay);
          } catch (InterruptedException ie) {

            // reset the interrupt flag
            //出現異常則呼叫Thread.currentThread().interrupt()中斷執行緒並將abandoned標記為true
            Thread.currentThread().interrupt();
            abandoned = true;
            // throw an unchecked exception which will hopefully not be ignored by the calling code
            throw new RuntimeException(GT.tr("Interrupted while attempting to connect."));
          }
        }
      }
    }

    private final String url;
    private final Properties props;
    private Connection result;
    private Throwable resultException;
    private boolean abandoned;
}

connect方法中呼叫到的makeConnection方法用於返回連線物件,其中還呼叫了hostSpecs/user/database方法

這些方法都是簡單的對properties中屬性的處理,到此為止,獲取連線的過程全部結束,

真正的與資料庫的直接的連線互動在廠商的PgConnection類中完成,該類實際實現了Connection介面

org.postgresql.Driver.java

private static Connection makeConnection(String url, Properties props) throws SQLException {
    return new PgConnection(hostSpecs(props), user(props), database(props), props, url);
  }

  private static HostSpec[] hostSpecs(Properties props) {
    String[] hosts = props.getProperty("PGHOST").split(",");
    String[] ports = props.getProperty("PGPORT").split(",");
    HostSpec[] hostSpecs = new HostSpec[hosts.length];
    for (int i = 0; i < hostSpecs.length; ++i) {
      hostSpecs[i] = new HostSpec(hosts[i], Integer.parseInt(ports[i]));
    }
    return hostSpecs;
  }

  private static String user(Properties props) {
    return props.getProperty("user", "");
  }

  private static String database(Properties props) {
    return props.getProperty("PGDBNAME", "");
  }

Driver類中自己定義了一個properties,並且在getDefaultProperties方法中為其賦權,簡單來說就是保證其有足夠許可權讀取資源

AccessController.doPrivileged是一個在AccessController類中的靜態方法,允許在一個類例項中的程式碼通知這個AccessController:

它的程式碼主體是享受”privileged(特權的)”,它單獨負責對它的可得的資源的訪問請求,而不管這個請求是由什麼程式碼所引發的。

這就是說,一個呼叫者在呼叫doPrivileged方法時,可被標識為 “特權”。

在做訪問控制決策時,如果checkPermission方法遇到一個通過doPrivileged呼叫而被表示為“特權”的呼叫者,

並且沒有上下文自變數,checkPermission方法則將終止檢查。

如果那個呼叫者的域具有特定的許可,則不做進一步檢查,checkPermission安靜地返回,表示那個訪問請求是被允許的;

如果那個域沒有特定的許可,則象通常一樣,一個異常被丟擲。

  private Properties defaultProperties;

  private synchronized Properties getDefaultProperties() throws IOException {
    if (defaultProperties != null) {
      return defaultProperties;
    }

    // Make sure we load properties with the maximum possible privileges.
    try {
      defaultProperties =
          AccessController.doPrivileged(new PrivilegedExceptionAction<Properties>() {
            public Properties run() throws IOException {
              return loadDefaultProperties();
            }
          });
    } catch (PrivilegedActionException e) {
      throw (IOException) e.getException();
    }

    return defaultProperties;
  }

這個列舉類在這裡的用處主要是返回loginTimeout值

org.postgresql.PGProperty

public enum PGProperty {
//LOGIN_TIMEOUT三個值分別為鍵名,預設值和描述,他是設定等待建立連線資料庫時間的,預設為0
 LOGIN_TIMEOUT("loginTimeout", "0",
      "Specify how long to wait for establishment of a database connection."),
  //規定列舉中的成員變數
  private String _name;
  private String _defaultValue;
  private boolean _required;
  private String _description;
  private String[] _choices;
  //構造方法
  PGProperty(String name, String defaultValue, String description) {
    this(name, defaultValue, description, false);
  }

  PGProperty(String name, String defaultValue, String description, boolean required) {
    this(name, defaultValue, description, required, (String[]) null);
  }

  PGProperty(String name, String defaultValue, String description, boolean required,
      String... choices) {
    _name = name;
    _defaultValue = defaultValue;
    _required = required;
    _description = description;
    _choices = choices;
  }
  //呼叫getProperty方法並將鍵名和預設值放入,如果鍵名在properties中存在則返回value值,如果鍵名為null則返回預設值
  public String get(Properties properties) {
    return properties.getProperty(_name, _defaultValue);
  }