一篇文章吃透:为什么加载数据库驱动要用Class.forName()
1、Class.forName()和ClassLoader.loadClass()和new XX的區別
Class.forName():將類的.class文件加載到jvm中之外,還會對類進行解釋,執行類中的static代碼塊。
ClassLoader.loadClass():只會將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance才會去執行static塊。
?
《深入理解Java虛擬機》p214講類的加載過程:加載、驗證、準備、解析和初始化。
其中
加載:主要在內存中生成class文件對應的Class對象,作為方法區這個類各種數據的訪問入口。
?驗證:驗證Class文件的字節流中的信息符合當前虛擬機的要求,并且不會危害虛擬機自身安全。
準備:為類變量分配內存并設置類變量的初始值。
解析:常量池中的符號引用替換為直接引用。
初始化:初始化階段才開始執行類中定義的Java程序代碼。
我們看Class.forName源碼
@CallerSensitivepublic static Class<?> forName(String className)throws ClassNotFoundException {Class<?> caller = Reflection.getCallerClass();return forName0(className, true, ClassLoader.getClassLoader(caller), caller);}調用了三個參數的重載的方法
/*** Returns the {@code Class} object associated with the class or* interface with the given string name, using the given class loader.* Given the fully qualified name for a class or interface (in the same* format returned by {@code getName}) this method attempts to* locate, load, and link the class or interface. The specified class* loader is used to load the class or interface. If the parameter* {@code loader} is null, the class is loaded through the bootstrap* class loader. The class is initialized only if the* {@code initialize} parameter is {@code true} and if it has* not been initialized earlier.** <p> If {@code name} denotes a primitive type or void, an attempt* will be made to locate a user-defined class in the unnamed package whose* name is {@code name}. Therefore, this method cannot be used to* obtain any of the {@code Class} objects representing primitive* types or void.** <p> If {@code name} denotes an array class, the component type of* the array class is loaded but not initialized.** <p> For example, in an instance method the expression:** <blockquote>* {@code Class.forName("Foo")}* </blockquote>** is equivalent to:** <blockquote>* {@code Class.forName("Foo", true, this.getClass().getClassLoader())}* </blockquote>** Note that this method throws errors related to loading, linking or* initializing as specified in Sections 12.2, 12.3 and 12.4 of <em>The* Java Language Specification</em>.* Note that this method does not check whether the requested class* is accessible to its caller.** <p> If the {@code loader} is {@code null}, and a security* manager is present, and the caller's class loader is not null, then this* method calls the security manager's {@code checkPermission} method* with a {@code RuntimePermission("getClassLoader")} permission to* ensure it's ok to access the bootstrap class loader.** @param name fully qualified name of the desired class* @param initialize if {@code true} the class will be initialized.* See Section 12.4 of <em>The Java Language Specification</em>.* @param loader class loader from which the class must be loaded* @return class object representing the desired class** @exception LinkageError if the linkage fails* @exception ExceptionInInitializerError if the initialization provoked* by this method fails* @exception ClassNotFoundException if the class cannot be located by* the specified class loader** @see java.lang.Class#forName(String)* @see java.lang.ClassLoader* @since 1.2*/@CallerSensitivepublic static Class<?> forName(String name, boolean initialize,ClassLoader loader)throws ClassNotFoundException{Class<?> caller = null;SecurityManager sm = System.getSecurityManager();if (sm != null) {// Reflective call to get caller class is only needed if a security manager// is present. Avoid the overhead of making this call otherwise.caller = Reflection.getCallerClass();if (sun.misc.VM.isSystemDomainLoader(loader)) {ClassLoader ccl = ClassLoader.getClassLoader(caller);if (!sun.misc.VM.isSystemDomainLoader(ccl)) {sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);}}}return forName0(name, initialize, loader, caller);}?
提示我們第二個參數表示是否初始化,看java參考手冊
https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4
?
new可以理解粗略的理解為:? ?【加載class文件到jvm + 初始化static代碼塊】(Class.forName) +構造實例(newInstance)
/*** Creates a new instance of the class represented by this {@code Class}* object. The class is instantiated as if by a {@code new}* expression with an empty argument list. The class is initialized if it* has not already been initialized.** <p>Note that this method propagates any exception thrown by the* nullary constructor, including a checked exception. Use of* this method effectively bypasses the compile-time exception* checking that would otherwise be performed by the compiler.* The {@link* java.lang.reflect.Constructor#newInstance(java.lang.Object...)* Constructor.newInstance} method avoids this problem by wrapping* any exception thrown by the constructor in a (checked) {@link* java.lang.reflect.InvocationTargetException}.** @return a newly allocated instance of the class represented by this* object.* @throws IllegalAccessException if the class or its nullary* constructor is not accessible.* @throws InstantiationException* if this {@code Class} represents an abstract class,* an interface, an array class, a primitive type, or void;* or if the class has no nullary constructor;* or if the instantiation fails for some other reason.* @throws ExceptionInInitializerError if the initialization* provoked by this method fails.* @throws SecurityException* If a security manager, <i>s</i>, is present and* the caller's class loader is not the same as or an* ancestor of the class loader for the current class and* invocation of {@link SecurityManager#checkPackageAccess* s.checkPackageAccess()} denies access to the package* of this class.*/@CallerSensitivepublic T newInstance()throws InstantiationException, IllegalAccessException{if (System.getSecurityManager() != null) {checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);}// NOTE: the following code may not be strictly correct under// the current Java memory model.// Constructor lookupif (cachedConstructor == null) {if (this == Class.class) {throw new IllegalAccessException("Can not call newInstance() on the Class for java.lang.Class");}try {Class<?>[] empty = {};final Constructor<T> c = getConstructor0(empty, Member.DECLARED);// Disable accessibility checks on the constructor// since we have to do the security check here anyway// (the stack depth is wrong for the Constructor's// security check to work)java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {public Void run() {c.setAccessible(true);return null;}});cachedConstructor = c;} catch (NoSuchMethodException e) {throw (InstantiationException)new InstantiationException(getName()).initCause(e);}}Constructor<T> tmpConstructor = cachedConstructor;// Security check (same as in java.lang.reflect.Constructor)int modifiers = tmpConstructor.getModifiers();if (!Reflection.quickCheckMemberAccess(this, modifiers)) {Class<?> caller = Reflection.getCallerClass();if (newInstanceCallerCache != caller) {Reflection.ensureMemberAccess(caller, this, null, modifiers);newInstanceCallerCache = caller;}}// Run constructortry {return tmpConstructor.newInstance((Object[])null);} catch (InvocationTargetException e) {Unsafe.getUnsafe().throwException(e.getTargetException());// Not reachedreturn null;}}其中根據newInstance是創建一個代表這個類對象的實例,如果沒有初始化會觸發初始化。
代碼的主要邏輯是查找無參數構造方法,然后通過反射來調用構造實例。
?
?
寫個測試類驗證一下:
/*** 加載測試demo類* * @author: 明明如月 liuwangyanghdu@163.com* @date: 2019-04-09 12:43*/ public class LoadTestClass {static {System.out.println("靜態代碼塊");}public LoadTestClass() {System.out.println("構造方法");}public static void test() {System.out.println("這是靜態方法");} }測試類
/*** 加載測試類** @author: 明明如月 liuwangyanghdu@163.com* @date: 2019-04-09 12:42*/ public class LoadTest {@Testpublic void test(){LoadTestClass.test();}@Testpublic void forName() throws ClassNotFoundException {Class<?> aClass = Class.forName("com.chujianyun.common.clazz.LoadTestClass");System.out.println(aClass);}@Testpublic void newTest() {new LoadTestClass();}@Testpublic void loader() throws ClassNotFoundException {Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("com.chujianyun.common.clazz.LoadTestClass");System.out.println(aClass);}@Testpublic void loaderNewInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("com.chujianyun.common.clazz.LoadTestClass");System.out.println(aClass);Object result = aClass.newInstance();System.out.println(result instanceof LoadTestClass);} }測試類分別輸出:
?
另外可以看到調用靜態方法前,會觸發靜態代碼塊的調用(也會觸發類的加載)。
?
二、為什么加載數據庫驅動要用Class.forName()?
?
其實JDBC4.0以后(mysql-connector-java 5.1.6之后) + java6以后,不再需要顯示調用Class.forName()加載驅動了。
下面是摘錄的一段話,簡單明了:
JDBC 4.0的特性
得益于Mustang中的Java SE 服務提供商機制,Java開發人員再也不必用類似Class.forName() 的代碼注冊JDBC驅動來明確加載JDBC。當調用DriverManager.getConnection()方法時,DriverManager類將 自動設置合適的驅動程序。該特性向后兼容,因此無需對現有的JDBC代碼作任何改動。
https://www.ibm.com/developerworks/cn/java/j-lo-jse65/#N100EE
?
JDBC 4.0 的規范規定,所有 JDBC 4.0 的驅動 jar 文件必須包含一個?java.sql.Driver,它位于 jar 文件的 META-INF/services 目錄下
?
接下來詳細展開:
以mysql驅動 8.0.11為例,采用了SPI機制(這里不展開,詳細了解可參考這篇文章:https://juejin.im/post/5af952fdf265da0b9e652de3)
Java SPI 實際上是“基于接口的編程+策略模式+配置文件”組合實現的動態加載機制。
?
使用時可以這么寫:
String url = "jdbc:mysql://localhost:3306/test"; Connection conn = DriverManager.getConnection(url,username,password); package com.mysql.cj.jdbc;import java.sql.DriverManager; import java.sql.SQLException;public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}} }
驅動的類靜態代碼塊中,調用DriverManager的注冊驅動方法new一個自己當參數傳給驅動管理器。
?
public static synchronized void registerDriver(java.sql.Driver driver)throws SQLException {registerDriver(driver, null);}另外最關鍵的是,驅動管理器的靜態代碼塊有加載初始化驅動的方法
/*** Load the initial JDBC drivers by checking the System property* jdbc.properties and then use the {@code ServiceLoader} mechanism*/ static {loadInitialDrivers();println("JDBC DriverManager initialized"); }?
private static void loadInitialDrivers() {String drivers;try {drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {public String run() {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();/* Load these drivers, so that they can be instantiated.* It may be the case that the driver class may not be there* i.e. there may be a packaged driver with the service class* as implementation of java.sql.Driver but the actual class* may be missing. In that case a java.util.ServiceConfigurationError* will be thrown at runtime by the VM trying to locate* and load the service.** Adding a try catch block to catch those runtime errors* if driver not available in classpath but it's* packaged as service and that service is there in classpath.*/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);}}}先查找?jdbc.drivers 屬性的指,然后SPI機制查找驅動
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);可以研究下ServiceLoader的源碼
?
?
可以看到加載META-INF/services/ 文件夾下類名為文件名(這里相當于Driver.class.getName())的資源,然后將其加載到虛擬機。
注釋有這么一句“Load these drivers, so that they can be instantiated.” 意思是加載SPI掃描到的驅動來觸發他們的初始化。既觸發他們的static代碼塊,既
既
/*** Registers the given driver with the {@code DriverManager}.* A newly-loaded driver class should call* the method {@code registerDriver} to make itself* known to the {@code DriverManager}. If the driver is currently* registered, no action is taken.** @param driver the new JDBC Driver that is to be registered with the* {@code DriverManager}* @param da the {@code DriverAction} implementation to be used when* {@code DriverManager#deregisterDriver} is called* @exception SQLException if a database access error occurs* @exception NullPointerException if {@code driver} is null* @since 1.8*/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 */if(driver != null) {registeredDrivers.addIfAbsent(new DriverInfo(driver, da));} else {// This is for compatibility with the original DriverManagerthrow new NullPointerException();}println("registerDriver: " + driver);}將自己注冊到 驅動管理器的驅動列表中
?
然后調用驅動管理器的獲取連接方法時從這里列表(registeredDrivers)中取
Connection conn = DriverManager.getConnection(url,username,password);調用 Url,用戶名和密碼三個參數的獲取連接的方法
@CallerSensitivepublic 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()));}具體又調用如下私有方法
// Worker method called by the public getConnection() methods.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.*/ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;synchronized(DriverManager.class) {// synchronize loading of the correct classloader.if (callerCL == null) {callerCL = Thread.currentThread().getContextClassLoader();}}if(url == null) {throw new SQLException("The url cannot be null", "08001");}println("DriverManager.getConnection(\"" + url + "\")");// Walk through the loaded registeredDrivers attempting to make a connection.// Remember the first exception that gets raised so we can reraise it.SQLException reason = null;for(DriverInfo aDriver : registeredDrivers) {// If the caller does not have permission to load the driver then// skip it.if(isDriverAllowed(aDriver.driver, callerCL)) {try {println(" trying " + aDriver.driver.getClass().getName());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");}其中又判斷調用者是否有權限加載驅動類,如果沒有就忽略(利用Class.forName嘗試加載,加載失敗則忽略))從前往后找到第一個可以構造Connection的對象就返回。
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {boolean result = false;if(driver != null) {Class<?> aClass = null;try {aClass = Class.forName(driver.getClass().getName(), true, classLoader);} catch (Exception ex) {result = false;}result = ( aClass == driver.getClass() ) ? true : false;}return result;}因此在調用
Connection conn = DriverManager.getConnection(url,username,password);這個靜態函數前,?DriverManager的靜態代碼塊已經被執行,既已經通過SPI機制講驅動注入到驅動列表中,因此無需在之前調用一次Class.forName。
?
由于JDBC 4.0之前并沒有采用SPI機制也沒有用static代碼塊講自己注冊到驅動管理器的驅動列表中,另外配套的Java版本好像也比較低(是為了適配jdk5),對應的驅動管理器代碼和現在也不一樣。因此需要手動調用Class.forName來加載class文件到jvm并初始化。
?
JDK6之后提供了SPI機制,另外mysql-connector-java 5.1.6之后采用了SPI方式編寫驅動。
以下是5.1.5版本的結構
5.1.6版本的結構
?
參考文章:
https://blog.csdn.net/w369033345/article/details/54173818
https://www.cnblogs.com/gaojing/archive/2012/03/15/2413638.html
https://blog.csdn.net/fengyuzhengfan/article/details/38086743
http://www.runoob.com/w3cnote/java-class-forname.html
總結
以上是生活随笔為你收集整理的一篇文章吃透:为什么加载数据库驱动要用Class.forName()的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 家用路由器AP模式设置
- 下一篇: 深圳手册