导读
通过jdbc链接数据库,是每个学习Java web 方向的人必然一开始会写的代码,虽然现在各路框架都帮大家封装好了jdbc,但是研究一下jdbc链接的套路还是很意义
术语以及相关类
SPI Service Provider Interface
classload 类加载器
AccessController 安全访问类
普通JDBC连接方式(jdk1.8)
①: Connection conn = DriverManager.getConnection(sqlUrl,sqlUserName,sqlPassword);
②: connection.prepareStatement(sql);
点击DriverManager类,你会发现,DriverManager初始化时有一个静态代码块要加载
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
loadInitialDrivers 方法的部分
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//这里典型的SPI,Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制
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;
}
});
jdk早期,数据库驱动如何加载的尼???
Class.forName(""com.mysql.jdbc.Driver"),
Connection conn = DriverManager.getConnection(sqlUrl,sqlUserName,sqlPassword);
先编程式加载MySQL数据库驱动,其次通过mysql驱动连接数据库
这2种方式最明显的不同
以前你需要指定要加载那个数据库驱动,是mysql,oracel还是sqlserver的,现在你只要在maven中引入数据库驱动jar包即可,其他的通过SPI一键搞定,SPI能智能识别到底要调用那个驱动连接数据库,原理很简单,在这个方法中 getConnection(sqlUrl,sqlUserName,sqlPassword);
getConnection 方法部分片段
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());
}
}
isDriverAllowed(aDriver.driver, callerCL) 方法会遍历所有已在maven中引入的驱动,aDriver.driver.connect(url, info); 方法会调用当期的驱动尝试连接数据库,如果能通过此驱动连接数据库成功,就返回,否则继续尝试(各个数据库库驱动如果无法连接数据库, aDriver.driver.connect(url, info); 此方法会返回null)。至此通过SPI 优雅的加载各大厂商的驱动就实现啦,哈哈
至于SPI 怎么玩,给大家一张图,具体怎么玩百度一下,这里就不多说啦
为啥说 jdbc 加载数据库驱动如何破坏双亲委托模式???
现在我们聊聊这中间涉及到一个隐藏知识点,类加载器,java 有以下几大类加载器
①:BootStrapClassload
②:ExtentionClassload
③:ApplicationClassload
④:自定义类加载器
Java 类加载遵循双亲委托模式,用白话说就是 子类类加载类之前都会咨询父类类加载器,这个类属不属于你的呀,你有加载过吗?如果BootStrapClassload,ExtentionClassload 都说我木有加载过,那么ApplicationClassload才会加载
jdk中具体的具体实现的双亲委托模式的,大家可以参考***.class.getClassLoader().loadClass("");下面的代码就是典型双亲委托模式
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) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
可能有人问,扯了这么多,跟我们上述JDBC加载驱动有啥关系?
我很肯定的回答有的哈哈
Java 类加载器,其实就是分模块加载资源?什么意思尼
Bootstrap ClassLoader :最顶层的加载类,主要加载核心类库,也就是我们环境变量下面%JRE_HOME%/lib下的rt.jar、resources.jar、charsets.jar和class等
Extention ClassLoader :扩展的类加载器,加载目录%JRE_HOME%/lib/ext目录下的jar包和class文件
Appclass Loader: 加载当前应用的classpath的所有类。
强调一下在目前双亲委托模式下,Bootstrap ClassLoader 不可以夸模块加载Appclass Loader。Appclass Loader也不可以夸模块加载Bootstrap ClassLoader,原因很简单,他们在自己的模块路径下中找不到要加载类( 比如:com.mysql.jdbc.Driver 这个类,它的类路径是在classpath下,你在rt.jar下下,你怎么找到这个类 )
那么问题来啦,DriverManager 这个类属于rt.jar 由Bootstrap ClassLoader 来加载的,各大驱动jar包是在classpath下由Appclass Loader 加载的,听着没事问题哈???
但是,你们仔细看一下代码,你们会发现DriverManager.getConnection(sqlUrl,sqlUserName,sqlPassword);在这个方法中已经完成驱动类的初始化啦,那么就说明这个驱动类是由Bootstrap ClassLoader 加载的???
这是怎么回事尼???
这里就涉及到了另外一个名词上下文类加载器,对应代码
Thread.currentThread().getContextClassLoader();
ClassLoader.getSystemClassLoader();
这2个方法在普通Java 项目下是相同的,但是在web应用中是不同的,各位有兴趣可以去查一下
每个线程都有自己的线程上下文类加载器,并且每个线程的上下文类记载器都是父线程的类加载器,那么我只要考虑第一个线程类加载器就可以啦,因为他是所有的线程的父类,那么对于普通Java程序,谁是第一个线程,这个大家肯定知道 Java 的 Main 方法哈哈,那么main 方法的上下文来加载器是谁尼?? 我们去看一下,main方法的入口类 Launcher 类
看一下 Launcher 类 初始化
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// 看这里,原来第一个上下文线程类加载器的是AppClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if(var2 != null) {
SecurityManager var3 = null;
if(!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
;
} catch (InstantiationException var6) {
;
} catch (ClassNotFoundException var7) {
;
} catch (ClassCastException var8) {
;
}
} else {
var3 = new SecurityManager();
}
if(var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
在看一下通过Java SPI 技术加载数据库驱动的时候,他是直接获取线程上下文类加载器加载数据库驱动(直接通过ApplicationClassload 来加载驱动的),
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
load源码
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
.
BootStrapClassload偷偷滴指派Appclassload 说帮我加载一下驱动类吧,本来只能由Appclassload委托BootStrapClassload来加载对应的类哈哈, 所以说,JDBC数据库加载破坏了双亲委托模式
结束语
双亲委托模式不是固定死的,它就是Java指定的一个类加载的一个规则,你完全可以不遵守它,只要你能帮类加载到JVM内存中即可。