转载

jdbc 加载数据库驱动如何破坏双亲委托模式

导读

通过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 加载数据库驱动如何破坏双亲委托模式

为啥说 jdbc 加载数据库驱动如何破坏双亲委托模式???

现在我们聊聊这中间涉及到一个隐藏知识点,类加载器,java 有以下几大类加载器

①:BootStrapClassload

②:ExtentionClassload

③:ApplicationClassload

④:自定义类加载器

jdbc 加载数据库驱动如何破坏双亲委托模式

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内存中即可。

原文  http://www.cnblogs.com/huxuhong/p/11856786.html
正文到此结束
Loading...