1、本文只是简单的描述本人看源码的一些记录,觉得比较重要的记录下来,如果有漏记重点,望大神们告之,万分感谢。
先扯点别的:
不知道各位有没有这种感觉,工作几年后,感觉技术达到一种瓶颈,在开发时,大多只停留在使用阶段,没有深究其框架的内部实现,使用都会,但一到优化就蒙逼了。
记不得在哪个公众号里看到“熟练工”一词了,可能上述就是吧,想让自己技术有提升,却不知道如何去做,现在我决定从jdk的源码入手,一点点去深入,虽不知这方法是否可行,至少行动了,每日看一点。本文也就在这样的背景下出现了。
欢迎大家来指点、交流如何提升技术。我会尽量每天写一点,分享自己的收获。
最后再插一句,如果发现文中有错误,请留言,感谢大家。
开始:
一个简单获取连接,只是为了找到入口,由入口开始分析。
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/microservicedb?useUnicode=true&characterEncoding=utf8", "root", "123456");
这是关键的获取连接的地方。(因篇幅问题,删除了一些代码,只保留重要部分)
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
//获取类加载器
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
}
}
}
上述代码的重点应该是两处
1、获取类加载器
2、registeredDrivers属性。
首先registeredDrivers是个静态属性,而getConnection(String, String, String)的方法也是静态方法,那么registeredDrivers的初始化,应该是由静态块加载(因为我们在拿连接时,并没有先创建DriverManager类,也不需要创建)。
果然(其实此处是为了加载由系统变量设置的驱动的:System.setProperty("jdbc.drivers","xxxxxx.Driver") 在第一次使用DriverManager之前设定才有效:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
// 第1部分
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);
}
}
}
loadInitialDrivers()方法中有两个少用(在自己写功能的时候)的点:
1、AccessController.doPrivileged方法
大家可以去找答案。
有篇简单介绍的文章https://www.cnblogs.com/langtianya/p/5133633.html
2、关于ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
其实时从META-INF文件夹中读取现实Driver接口的实现类的,在这里,其实就是读取mysql-connection-java的META-INF/services/java.sql.Driver 文件。此处的java.sql.Driver文件名是必要的,不能写成别的。就是接口的全路径。还不清楚的自己去查找相关ServiceLoader的资料吧。里面的内容有
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
正式说下loadInitialDrivers方法:
我将上述的代码分成了三个部分。
第一部分:主要是加载系统变量的System.setProperty("jdbc.drivers","xxxxxx.Driver") 多个之间用冒号分割(注意:必须在第一次使用DriverManager的时候设置才有效,static块只会加载一次)。
第三部分是对第一部分的延续,将读取到驱动初始化(在static块中调用 DriverManager.registerDriver)。
其实也是调用Class.forName(aDriver, true,ClassLoader.getSystemClassLoader()); 来做的。
第二部分(有这个,其实在我们做入口的时候不用再调用Class.forName("com.mysql.jdbc.Driver");了):
通过ServiceLoadr可以获取到Driver的实现类(还不清楚怎么获取到的童鞋自己查查ServiceLoadr)
然后调用driversIterator.next(); 这个调用其实是调用了ServiceLoadr中的nextService();
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
上述代码,最主要的部分是{c = Class.forName(cn, false, loader); } 大括号的代码其实并不会初始化(即不会调用static块,因为initial是false)而是由下面的service.cast(c.newInstance()) 来创建实例时调用的static块。 此处做个补充。
也就是说,其实driversIterator.next();中已经帮我们做了Class.forName了,也就是做了注册Driver的工作了。
别被这里的Iterator的next给迷惑了。
我们看看com.mysql.jdbc.Driver类初始化时做了什么:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
没错,就是调用了注册的方法。再次回到getConnection方法。
Connection con = aDriver.driver.connect(url, info);
此处的aDriver.driver就获取到了我们需要的com.mysql.jdbc.Driver对象。
aDriver.driver.connect(url, info)该方法的实现其实是在com.mysql.jdbc.Driver的父类com.mysql.jdbc.NonRegisteringDriver中的。
-----------------------------------------转发请注明出处。--------------------------------------------------------------------------------
DriverManager的其它方法下次写。