Примечание. Анализ исходного кода соответствует JDK версии 1.8.
1. Введение
Это первая статья, посвященная интерпретации исходного кода JDK [Примечание к источнику]. В этой статье мы рассмотрим соответствующий исходный код механизма Java SPI.
2 Что такое механизм SPI
Итак, что же представляет собой механизм SPI?
SPI — это аббревиатура от Service Provider Interface, т. е.интерфейс поставщика услугзначение. По буквальному смыслу мы можем немного запутаться.SPI - это механизм расширения.Мы определяем класс реализации интерфейса в соответствующем конфигурационном файле, а затем загружаем класс экземпляра в конфигурационный файл в соответствии с интерфейсом и инстанцируем его , вообще-то SPI такая штука. Когда дело доходит до механизма SPI, наиболее распространенным является механизм SPI Java, кроме того, существуют также механизмы SPI, настроенные Dubbo и SpringBoot.
С помощью механизма SPI можно гибко расширять некоторые фреймворки без необходимости писать в коде какие-то классы реализации фреймворка.
Итак, как некоторые фреймворки используют механизм SPI для гибкого расширения? Вот несколько каштанов для иллюстрации:
- Вариант загрузки драйвера JDBC: Используя механизм Java SPI, мы можем ввести различные пакеты драйверов JDBC в зависимости от разных производителей баз данных;
-
Механизм SPI SpringBoot: мы можем
spring.factoriesДобавьте наши пользовательские классы автоконфигурации, прослушиватели событий или инициализаторы и т. д.; - Механизм SPI Даббо: Dubbo даже применяет механизм SPIостро и ярко, Dubbo в основном предоставляет точки расширения для каждой из своих собственных функциональных точек, таких как расширение кластера, расширение маршрутизации и расширение балансировки нагрузки, которые приближаются к 30 точкам расширения. Если одна из встроенных реализаций Dubbo не соответствует нашим потребностям, то нам просто нужно использовать ее механизм SPI, чтобы заменить реализацию Dubbo нашей реализацией.
Приведенные выше три каштана сначала позволяют интуитивно почувствовать, как некоторые фреймворки могут быть гибко расширены с помощью механизма SPI.
3 Как использовать SPI в Java?
Давайте сначала посмотрим, как использовать SPI, поставляемый с Java.
сначала определитеDeveloperинтерфейс
// Developer.java
package com.ymbj.spi;
public interface Developer {
void sayHi();
}
определить еще дваDeveloperДва класса реализации интерфейса:
// JavaDeveloper.java
package com.ymbj.spi;
public class JavaDeveloper implements Developer {
@Override
public void sayHi() {
System.out.println("Hi, I am a Java Developer.");
}
}
// PythonDeveloper.java
package com.ymbj.spi;
public class PythonDeveloper implements Developer {
@Override
public void sayHi() {
System.out.println("Hi, I am a Python Developer.");
}
}
тогда в проектеresourcesСоздайте новый в каталогеMETA-INF/servicesпапку, а затем создайте новую сDeveloperФайл с полным именем интерфейса.Содержимое файла:
// com.ymbj.spi.Developer文件
com.ymbj.spi.JavaDeveloper
com.ymbj.spi.PythonDeveloper
Наконец, мы создаем новый тестовый классJdkSPITest:
// JdkSPITest.java
public class JdkSPITest {
@Test
public void testSayHi() throws Exception {
ServiceLoader<Developer> serviceLoader = ServiceLoader.load(Developer.class);
serviceLoader.forEach(Developer::sayHi);
}
}
Запустите приведенный выше тестовый класс, и успешный результат показан на следующем снимке экрана:
Из приведенной выше простой демонстрации мы знаем, как использовать механизм Java SPI для реализации загрузки точек расширения.Дополнения к JAVA — о механизме SPIЯ считаю, что благодаря этой статье каждый получит более глубокое представление о SPI Java, особенно о драйвере загрузки JDBC.
4 Интерпретация исходного кода механизма Java SPI
расширенный по фронтуDeveloperПростая демонстрация интерфейса, мы видим, что реализация механизма Java SPI следуетServiceLoaderЭтот класс связан, поэтому сначала рассмотрим егоServiceLoaderКод структуры класса:
// ServiceLoader实现了【Iterable】接口
public final class ServiceLoader<S>
implements Iterable<S>{
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
// 构造方法
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
// ...暂时省略相关代码
// ServiceLoader的内部类LazyIterator,实现了【Iterator】接口
// Private inner class implementing fully-lazy provider lookup
private class LazyIterator
implements Iterator<S>{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
// 覆写Iterator接口的hasNext方法
public boolean hasNext() {
// ...暂时省略相关代码
}
// 覆写Iterator接口的next方法
public S next() {
// ...暂时省略相关代码
}
// 覆写Iterator接口的remove方法
public void remove() {
// ...暂时省略相关代码
}
}
// 覆写Iterable接口的iterator方法,返回一个迭代器
public Iterator<S> iterator() {
// ...暂时省略相关代码
}
// ...暂时省略相关代码
}
можно увидеть,ServiceLoaderДостигнутоIterableинтерфейс, перекрывая егоiteratorметод создает итератор; такжеServiceLoaderимеет внутренний классLazyIterator,иLazyIteratorпонял сноваIteratorОписание интерфейсаLazyIteratorявляется итератором.
4.1 Метод ServiceLoader.load для подготовки к загрузке классов реализации поставщика услуг
Итак, теперь мы приступаем к изучению исходного кода механизма Java SPI,
Первый взглядJdkSPITestпервое предложение кодаServiceLoader<Developer> serviceLoader = ServiceLoader.load(Developer.class);серединаServiceLoader.load(Developer.class);Исходный код:
// ServiceLoader.java
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取当前线程上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 将service接口类和线程上下文类加载器作为参数传入,继续调用load方法
return ServiceLoader.load(service, cl);
}
давайте посмотрим еще разServiceLoader.load(service, cl);метод:
// ServiceLoader.java
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
// 将service接口类和线程上下文类加载器作为构造参数,新建了一个ServiceLoader对象
return new ServiceLoader<>(service, loader);
}
Продолжай читатьnew ServiceLoader<>(service, loader);Как он устроен?
// ServiceLoader.java
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
можно увидеть зданиеServiceLoaderПомимо присвоения значений своим свойствам-членам, объект также вызываетreloadметод:
// ServiceLoader.java
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
можно увидеть вreloadметод создает новыйLazyIteratorобъект, а затем присвоитьlookupIterator.
// ServiceLoader$LazyIterator.java
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
можно увидеть зданиеLazyIteratorобъект, он передается только его переменным-членамserviceиloaderНазначение атрибута, мы прошли весь исходный код и не увидели егоMETA-INF/servicesзагрузка папкиDeveloperКласс реализации интерфейса! Это странно, мы все былиServiceLoaderизloadНазвание метода обманчиво.
Помните, что при анализе предыдущего кода был создан новыйLazyIteratorобъект?Lazyкак следует из названияленивыйзначение,IteratorИмеется в виду итерация. Мы предполагаем, что на данный момент, чтоLazyIteratorРоль объекта должна заключаться в том, чтобы загружать его при повторенииDeveloperКласс реализации интерфейса.
4.2 Метод ServiceLoader.iterator, реализующий ленивую загрузку классов реализации поставщика услуг
давайте посмотрим сейчасJdkSPITestВторое предложение кодаserviceLoader.forEach(Developer::sayHi);, после выполнения этого кода он в конечном итоге вызоветserviceLoaderизiteratorметод:
// serviceLoader.java
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
// 调用lookupIterator即LazyIterator的hasNext方法
// 可以看到是委托给LazyIterator的hasNext方法来实现
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 调用lookupIterator即LazyIterator的next方法
// 可以看到是委托给LazyIterator的next方法来实现
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
можно увидеть вызовserviceLoaderизiteratorМетод вернет анонимный объект итератора, и этот анонимный объект итератора фактически эквивалентен фасадному классу, который переопределяетhasNextиnextметоды делегируются отдельноLazyIteratorизhasNextиnextметод достижения.
Продолжаем отлаживать и обнаруживаем, что войдем дальшеLazyIteratorизhasNextметод:
// serviceLoader$LazyIterator.java
public boolean hasNext() {
if (acc == null) {
// 调用hasNextService方法
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
следовать заhasNextServiceметод:
// serviceLoader$LazyIterator.java
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// PREFIX = "META-INF/services/"
// service.getName()即接口的全限定名
// 还记得前面的代码构建LazyIterator对象时已经给其成员属性service赋值吗
String fullName = PREFIX + service.getName();
// 加载META-INF/services/目录下的接口文件中的服务提供者类
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 还记得前面的代码构建LazyIterator对象时已经给其成员属性loader赋值吗
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 返回META-INF/services/目录下的接口文件中的服务提供者类并赋值给pending属性
pending = parse(service, configs.nextElement());
}
// 然后取出一个全限定名赋值给LazyIterator的成员变量nextName
nextName = pending.next();
return true;
}
можно увидеть выполнениеLazyIteratorизhasNextServiceметод в конечном итоге пойдетMETA-INF/services/Содержимое файла интерфейса, загруженного в каталог, состоит в том, чтобы загрузить полное имя класса реализации поставщика услуг, а затем извлечь полное имя класса реализации поставщика услуг и назначить его дляLazyIteratorпеременная-членnextName. Когда мы приходим сюда, мы понимаемLazyIteratorФункция действительно ленивая загрузка, и она будет загружена при использовании.
считать: Зачем здесь использовать ленивую загрузку? В чем идея ленивой загрузки? Каковы преимущества ленивой загрузки? Можете ли вы привести другие случаи отложенной загрузки?
Аналогично, после выполненияLazyIteratorизhasNextметод, будет продолжать выполнятьсяLazyIteratorизnextметод:
// serviceLoader$LazyIterator.java
public S next() {
if (acc == null) {
// 调用nextService方法
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
мы продолжаем следитьnextServiceметод:
// serviceLoader$LazyIterator.java
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
// 还记得在hasNextService方法中为nextName赋值过服务提供者实现类的全限定名吗
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 【1】去classpath中根据传入的类加载器和服务提供者实现类的全限定名去加载服务提供者实现类
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 {
// 【2】实例化刚才加载的服务提供者实现类,并进行转换
S p = service.cast(c.newInstance());
// 【3】最终将实例化后的服务提供者实现类放进providers集合
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
можно увидетьLazyIteratorизnextServiceВ конечном итоге метод создаст экземпляр ранее загруженного класса реализации поставщика услуг и поместит его вprovidersколлекцию, а затем вызвать метод класса реализации поставщика услуг (например, здесь имеется в видуJavaDeveloperизsayHiметод). Обратите внимание, что после загрузки класса реализации поставщика услуг, еслиmainЕсли в функции есть метод, который вызывает класс реализации поставщика услуг, его метод будет вызван немедленно, а затем он продолжит создавать экземпляр следующего класса поставщика услуг.
Шаблоны проектирования: видно, что режим итератора применяется в коде реализации механизма SPI Java.Режим итератора скрывает внутренние структурные различия различных объектов хранения и обеспечивает унифицированное представление для обхода каждого объекта хранения (объект хранения может быть коллекцией , массив Подождите).
java.util.IteratorЭто также реализация шаблона итератора: в то же время каждый класс коллекции Java обычно реализуетIterableинтерфейс, реализующий егоiteratorметод полученияIteratorОбъект класса реализации интерфейса (обычно это внутренний класс коллекции), а затем используйтеIteratorкласс реализации объектаhasNextиnextметод для перебора элементов коллекции.
5 Интерпретация исходного кода загрузки драйвера JDBC
Исходный код реализации механизма SPI в Java был проанализирован ранее, теперь давайте посмотрим на реальное применение механизма SPI в Java.
Все мы знаем, что загрузка драйвера JDBC является типичным случаем применения механизма SPI в Java. JDBC в основном предоставляет набор спецификаций интерфейса, и API этой спецификации находится в основной библиотеке java (rt.jar), и разные поставщики баз данных могут использовать язык Java для подключения к базе данных, если они пишут код драйвера, соответствующий этой спецификации интерфейса JDBC.
Основная библиотека java (rt.jar), а основные интерфейсы и классы, загружаемые драйвером JDBC,java.sql.Driverинтерфейс иjava.sql.DriverManagerкласс, которыйjava.sql.Driverэто интерфейс, который должен быть реализован классами драйверов различных производителей баз данных, иDriverManagerЭто класс драйвера, используемый для управления базой данных.Стоит отметить, чтоDriverManagerЭтот класс имеетregisteredDriversСвойство коллекции, используемое для хранения класса драйвера базы данных.
// DriverManager.java
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
Здесь мы берем загрузку драйвера Mysql в качестве примера для анализа исходного кода, загруженного драйвером JDBC.
Знакомство с нашим проектомmysql-connector-javaзависимости (версия здесь5.1.47), то файл класса реализации драйвера Mysql показан на следующем рисунке:
Вы можете видеть, что есть два пакета драйверов Mysql.
Driverкласс водителя соответственноcom.mysql.jdbc.Driverиcom.mysql.fabric.jdbc.FabricMySQLDriver, по умолчанию мы используем только первое.
5.1 Используйте SPI Java для загрузки класса драйвера Mysql
Затем давайте рассмотрим, как реализован код, загруженный драйвером JDBC.
Давайте сначала посмотрим на простой тестовый код JDBC:
// JdbcTest.java
public class JdbcTest {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet rs = null;
try {
// 注意:在JDBC 4.0规范中,这里可以不用再像以前那样编写显式加载数据库的代码了
// Class.forName("com.mysql.jdbc.Driver");
// 获取数据库连接,注意【这里将会加载mysql的驱动包】
/***************【主线,切入点】****************/
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456");
// 创建Statement语句
statement = connection.createStatement();
// 执行查询语句
rs = statement.executeQuery("select * from user");
// 遍历查询结果集
while(rs.next()){
String name = rs.getString("name");
System.out.println(name);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
// ...省略释放资源的代码
}
}
}
существуетJdbcTestизmainвызов функцииDriverManagerизgetConnectionметод, он должен быть выполнен первымDriverManagerЗатем выполняется код статического блока кода класса.getConnectionметод, так что давайте посмотримDriverManagerБлок статического кода:
// DriverManager.java
static {
// 加载驱动实现类
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
следовать заloadInitialDriversкод:
// DriverManager.java
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;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 来到这里,是不是感觉似曾相识,对,没错,我们在前面的JdkSPITest代码中执行过下面的两句代码
// 这句代码前面已经分析过,这里不会真正加载服务提供者实现类
// 而是实例化一个ServiceLoader对象且实例化一个LazyIterator对象用于懒加载
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 调用ServiceLoader的iterator方法,在迭代的同时,也会去加载并实例化META-INF/services/java.sql.Driver文件
// 的com.mysql.jdbc.Driver和com.mysql.fabric.jdbc.FabricMySQLDriver两个驱动类
/****************【主线,重点关注】**********************/
Iterator<Driver> driversIterator = loadedDrivers.iterator();
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);
}
}
}
В приведенном выше коде мы видим, что загрузка класса драйвера Mysql в основном реализуется с использованием механизма SPI Java, то есть с использованиемServiceLoaderЧтобы реализовать класс драйвера, который загружает и создает экземпляр Mysql.
5.2 Зарегистрируйте класс драйвера Mysql
Итак, приведенный выше код — это просто загрузка и создание экземпляра класса драйвера Mysql,Итак, как класс драйвера регистрируется вDriverManagerизregisteredDriversЧто с коллекцией?
В это время мы заметилиcom.mysql.jdbc.DriverВ классе также есть блок статического кода, то есть выполнение кода блока статического кода обязательно будет запущено при создании экземпляра класса, поэтому давайте непосредственно посмотрим, что делает этот блок статического кода:
// com.mysql.jdbc.Driver.java
// Register ourselves with the DriverManager
static {
try {
// 将自己注册进DriverManager类的registeredDrivers集合
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
Как видите, это класс драйвера Mysql.com.mysql.jdbc.DriverВо время создания экземпляра используйте возможность выполнить его статический блок кода, чтобы зарегистрироваться вDriverManagerизregisteredDriversв коллекции.
Хорошо, продолжай следитьDriverManagerизregisterDriverметод:
// DriverManager.java
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
// 继续调用registerDriver方法
registerDriver(driver, null);
}
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) {
// 将driver驱动类实例注册进registeredDrivers集合
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
На данный момент мы понимаем, как механизм SPI Java загружает класс драйвера Mysql и как зарегистрировать класс драйвера Mysql вDriverManagerизregisteredDriversв коллекции.
5.3 Используйте ранее зарегистрированный класс драйвера Mysql для подключения к базе данных
Теперь, когда класс драйвера Mysql зарегистрирован, когда он будет использоваться?
Если мы хотим подключиться к базе данных Mysql, нам, естественно, нужно использовать класс драйвера Mysql, верно? На этом этапе мы возвращаемся к тестовому коду JDBC.JdbcTestКатегорияconnection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456");В этом коде посмотрите наgetConnectionИсходный код:
// DriverManager.java
@CallerSensitive
public 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);
}
// 继续调用getConnection方法来连接数据库
return (getConnection(url, info, Reflection.getCallerClass()));
}
следовать заgetConnectionметод:
// DriverManager.java
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();
}
}
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;
// 遍历registeredDrivers集合,注意之前加载的Mysql驱动类实例被注册进这个集合
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());
// 利用Mysql驱动类来连接数据库
/*************【主线,重点关注】*****************/
Connection con = aDriver.driver.connect(url, info);
// 只要连接上,那么加载的其余驱动类比如FabricMySQLDriver将会忽略,因为下面直接返回了
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");
}
можно увидеть,DriverManagerизgetConnectionметод начнется сregisteredDriversИзвлеките класс драйвера Mysql, только что загруженный из коллекции, чтобы подключиться к базе данных.
Ну, здесь в основном анализируется исходный код, загруженный драйвером JDBC.
6 Загрузчик класса контекста потока
Я в основном проанализировал исходный код загрузки драйвера JDBC, но все еще есть очень важная точка знаний, которая не была объяснена, а именно модель родительского делегирования, которая разрушает механизм загрузки классов.загрузчик класса контекста потока.
Все мы знаем, что родственные классы спецификации JDBC (такие как предыдущийjava.sql.Driverиjava.sql.DriverManager) все в JDKrt.jarПод пакетом подразумевается, что эти классы будут загружаться BootstrapClassLoader, тогда как класс драйвера Mysql реализуется производителем внешней базы данных, и он же находится в проекте при введении класса драйвера в проект.classpath, в это время определенно невозможно запустить загрузчик классов, чтобы загрузить эти классы драйверов, что мне делать в это время?
Из-за недостатков родительской модели делегирования механизма загрузки классов, родительская модель делегирования может быть только нарушена. из-за проектаclasspathКлассы загружаются загрузчиком классов приложения (AppClassLoader), поэтому можем ли мы «реверсировать» загрузчик классов запуска, чтобы делегировать загрузчику классов приложения загрузку классов драйверов этих внешних поставщиков баз данных? Если да, то как мы можем заставить загрузчик класса запуска делегировать загрузчик класса приложения для загрузкиclasspathА как насчет занятий в ?
Ответ определенно да, мы можем установить загрузчик класса приложения в поток, то есть определить новое свойство загрузчика класса в потокеcontextClassLoader, а затем в какой-то момент установить загрузчик класса приложения в потокcontextClassLoaderВ этом свойстве, если не задано, то по умолчанию используется загрузчик класса приложения. Затем запустите загрузчик классов для загрузкиjava.sql.Driverиjava.sql.DriverManagerПри ожидании класса он одновременно будет взят и из текущего потокаcontextClassLoaderто есть загрузчик класса приложения переходит кclasspathЗагрузите классы драйверов JDBC, предоставленные внешними производителями. Поэтому, нарушая родительскую модель делегирования механизма загрузки класса, используйтезагрузчик класса контекста потокаИдеальное решение этой проблемы.
Теперь давайте оглянемся назадКогда загрузчик класса контекста потока получается при загрузке драйвера Mysql?
Ответ вDriverManagerизloadInitialDriversметод называетсяServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Этот код и загрузчик класса контекста потока находятся вServiceLoaderизloadВывести из метода:
public static <S> ServiceLoader<S> load(Class<S> service) {
// 取出线程上下文类加载器取出的是contextClassLoader,而contextClassLoader装的应用程序类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 把刚才取出的线程上下文类加载器作为参数传入,用于后去加载classpath中的外部厂商提供的驱动类
return ServiceLoader.load(service, cl);
}
Поэтому на данном этапе мы понимаем роль загрузчика класса контекста потока в загрузке пакета драйвера JDBC. Кроме того, мы должны знать, что большая часть загрузки Java с использованием SPI выполняется с использованием загрузчиков классов контекста потока, таких как JNDI, JCE, JBI и т. д.
расширять: Нарушение родительской модели делегирования механизма загрузки классов также включает горячее развертывание кода и т. д. Кроме того, стоит прочитать механизм загрузки классов Tomcat.
Примечание. Если некоторым небольшим партнерам неясна модель родительского делегирования механизма загрузки классов, рекомендуетсяПолное понимание модели родительского делегирования и пользовательского ClassLoaderПрочтите эту статью.
7 Расширение: механизм SPI Dubbo
Как было сказано ранее, фреймворк Dubbo переполнен приложениями механизма SPI, можно сказать, что везде есть точки расширения, которые действительно в полной мере применяют механизм SPI. Но Dubbo не использует механизм Java SPI по умолчанию, а сам реализует набор механизмов SPI.
Так,Почему Dubbo не использовал механизм SPI Java?
Есть две основные причины:
- Механизм SPI в Java будет создавать экземпляры всех реализаций точек расширения одновременно.Если есть реализации расширений, инициализация занимает много времени, но если они не используются, они будут загружены, что будет пустой тратой ресурсов;
- Механизм SPI в Java не поддерживает Ioc и AOP, поэтому Dubbo использует свой собственный механизм SPI: добавляя поддержку точек расширения IoC и AOP, точка расширения может напрямую внедрять сеттеры в другие точки расширения.
По вышеуказанным причинам Dubbo настроил набор механизмов SPI для загрузки собственных точек расширения. Механизм SPI Dubbo здесь подробно описываться не будет.Заинтересованные друзья могут зайти на официальный сайт Dubbo и посмотреть, как расширить SPI Dubbo? На официальном сайте Duboo также есть статья с анализом исходного кода SPI.
8 Резюме
Что ж, здесь был интерпретирован механизм SPI Java.Давайте сначала подведем итоги предыдущих знаний:
- Использование механизма Java SPI;
- Принцип механизма Java SPI;
- принцип загрузки драйвера JDBC;
- Роль загрузчика класса контекста потока в загрузке драйвера JDBC;
- Кратко описан механизм SPI Duboo.
В связи с ограниченным уровнем автора, если есть ошибки в тексте, просьба указать, спасибо.
Ссылаться на:
1,Хироши Ватанабэ.apache.org/this-capable/docs/…
2, «Углубленное понимание виртуальной машины Java»