参考:
为ClassLoader中的父加载器的定义:
public abstract class ClassLoader{ // The parent class loader for delegation private final ClassLoader parent; }
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized(getClassLoadingLock(name)){ // 1️⃣First, check if the class has alreadly been loaded Class<?> c = findLoadedClass(name); if(c == null){ long t0 = System.nanoTime(); try{ if(parent != null){ c = parent.loadClass(name, false); } c = findBootstrapClassOrNull(name); } }catch(ClassNotFoundException e){ // 2️⃣ClassNotFoundException thrown if class not found // from the non-null parent class loader } if(c == null){ // 3️⃣If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // 4️⃣this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassName().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if(resolve){ resolveClass(c); } return c; } }
SPI(Service Provider Interface, 是一种服务发现机制,通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
建立数据库连接6大步骤: 1. 加载JDBC驱动程序 2. 建立连接 3. 创建命令发送器 4. 执行SQL语句 5. 处理结果集 6. 关闭数据库
- 加载JDBC驱动程序: Class.forName("com.mysql.jdbc.Driver");
- 建立连接: Connection conn = DriverManager.getConnection(url, username, password);
- 创建命令发送器: Statement(执行不带参数SQL)、PreparedStatement(执行带参数或不带参数的SQL)、CallableStatement(执行数据库存储过程调用);
- 执行SQL语句: executeQuery(sql) -> ResultSet、execute(sql) -> boolean、executeUpdate(sql) -> int;
- 处理ResultSet结果集: 通过executeQuery(sql) 返回结果集
- 关闭数据库: eg: conn.close()、stmt.close();
我们在日常开发中需要使用大量的接口包括Java内部API和第三方服务提供的SPI
Java内部API由类加载器进行加载而SPI按照双亲委派的原则无法被类加载器加载需要破坏双亲委派的机制来调用。
JDBC破坏双亲委派原则的例子如下:
在Java中为了获取指定数据库连接要用到第三方数据库服务商提供的数据库驱动
获取数据库连接的步骤
首先要加载Java提供的DriverManager类
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");
然后在DriverManager类中需要调用ServiceLoader
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
而ServiceLoader来获取各个数据库服务提供商对Driver接口的所有实现类才能获得数据库的连接
但是Driver接口的实现类是由第三方提供属于SPI所以必须破坏双亲委派原则才能加载Driver的实现类
在JDBC的ServiceLoader类是通过构建ThreadContextLoader即线程上下文加载器来加载Driver实现类最终
由DriverManager调用从而能够获取与数据库的连接
public static<S> ServiceLoader<S> load(Class<S> service){ // 1. 获取当前线程的线程上下文类加载器 ClassLoader c1 = Thread.currentThread().getContextClassLoader(); // 2. 用于加载ClassPath中的具体实现类 return ServiceLoader.load(service, c1); }
Tomcat是一个Web容器,一个Web容器中需要部署多个应用程序。而这些应用程序可能会依赖同一个第三方类库的不同版本
双亲委派模型的好处之前提到 1.避免重复加载相同的类 2. 安全
如果采用双亲委派模型则无法加载多个相同的类。所以,Tomcat破坏双亲委派模型,提供隔离机制,为每个Web应用单独提供一个WebAppClassLoader加载器。
WebAppClassLoader负责加载本身目录下的class文件,加载不到时才会交给CommonClassLoader加载,这个机制和双亲委派模型正好相反。