• 周六. 10 月 12th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

【java_ Deep foundation] com.mysql.jdbc . driver breaks parental delegation with SPI

King Wang

1 月 3, 2022

One . SPI In the role

Come to the conclusion first

  • DriverManager.getConnection() Used internally SPI Mechanism , scanning mysql Of jar Bag META-INF/services/ Get the full pathname and use Class.forName(cn, false, loader),c.newInstance() Load target driver .

  • On the other hand , It also solves why not use Class.forName() You can also break parental delegation , because getConnection It’s encapsulated inside Class.forName(cn, false, loader);

  • false Express Loading bytecode returns class object . But it doesn’t initialize , It doesn’t trigger static Code block

  • c.newInstance The object must be instantiated , Trigger static Code block

Preface

The following concepts extend to Effective java page 6 ( Chinese version )

  • SPI (Service Provider Interface), Interface used to expand engineering examples

  • about JDBC ,Connection It’s part of its service interface

1.1 The service provider is for JDK Interface Driver

Service Interface

JDK Provides standards , Specific database drivers are provided by major database manufacturers

 Insert picture description here

1.2 Provider Registration API registerDriver(new Driver)

Provider Registration API

JDK For registration API, Registered API Would call new Driver()
Driver There must be a null parameter constructor , For support Class.forName().newInstance()

 static {

try {

java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {

throw new RuntimeException("Can't register driver!");
}
}
 public Driver() throws SQLException {

// Required for Class.forName().newInstance()
}

1.3 The user invokes the API getConnection(url)

Provider access API

Code used by developers

 @CallerSensitive
public static Connection getConnection(String url)
throws SQLException {

java.util.Properties info = new java.util.Properties();
return (getConnection(url, info, Reflection.getCallerClass()));
}

Two . DriverManager join SPI

2.1. DriverManager getConnection The underlying source code

private static Connection getConnection(
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {

// synchronize loading of the correct classloader.
if (callerCL == null) {

callerCL = Thread.currentThread().getContextClassLoader();
}
}
// register SPI All the drivers in the directory 
for(DriverInfo aDriver : registeredDrivers) {

if(isDriverAllowed(aDriver.driver, callerCL)) {

try {

Connection con = aDriver.driver.connect(url, info);
if (con != null) {

// Success!
return (con);
}
}
}
}
}
}
 private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {

boolean result = false;
if(driver != null) {

Class<?> aClass
= Class.forName(driver.getClass().getName(), true, classLoader);
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}

Most called Class.forName

2.2 DriverManager Use Class.forName

About getContextClassLoader, Click on this link , Pull to the bottom of the article

Thread.currentThread().getContextClassLoader(); // What you get is AppClassLoader

Loading the core class DriverManager after , You can use AppClassLoader load SPI Implementation class

Class.forName(driver.getClass().getName(), true, classLoader);

Class.forName It uses getContextClassLoader() Medium AppClassLoader load

2.3 DriverManager.getConnection Trigger static methods

For the first time getConnection(String url) Will trigger DriverManager Of static Method

 static {

loadInitialDrivers();
println("JDBC DriverManager initialized");
}

2.4 DriverManager.loadInitialDrivers()

ServiceLoader<Driver> Namely SPI Is executing container

 private static void loadInitialDrivers() {

AccessController.doPrivileged(new PrivilegedAction<Void>() {

public Void run() {

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while(driversIterator.hasNext()) {

driversIterator.next(); // next() The method is SPI The ultimate realization of 
}
return null;
}
});
}

3、 … and . SPI Final realization

Physical support

public final class ServiceLoader<S>implements Iterable<S>{

private static final String PREFIX = "META-INF/services/";
}

 Insert picture description here

ServiceLoader.nextService() and com.mysql.jdbc.Driver Static method of

ServiceLoader.nextService Used to traverse all SPI(Iterable.next Encapsulates the method )

 private S nextService() {

String cn = nextName;//cn yes SPI All implementers are limited to their names 
nextName = null;
Class<?> c = null;
try {

// Loading bytecode returns class object . But it doesn't initialize , It doesn't trigger static Code block 
c = Class.forName(cn, false, loader);
}
try {

// cast Not the main business logic . c.newInstance Triggered com.mysql.jdbc.Driver Static method of 
// The static method completes Driver Instantiation and registration of 
S p = service.cast(c.newInstance());
providers.put(cn, p);// Local cache ( All names are limited , Implementation class object )
return p;
}
}

Driver The static method of is triggered , Completion will SPI The implementation class is registered to DriverManager

Because it was c.newInstance The trigger , there new Driver() Has been replaced by SPI Implementation class , Registration completed

 static {

try {

java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {

throw new RuntimeException("Can't register driver!");
}
}

Four . carding getConnection(url) The registration logic of

DriverManager.getConnection(url) // How to get the drive 
  • Step one , Thread on , Parent delegation assigns core class loading ,DriverManager When not loaded

    1. Bootstrap ClassLoader load
    2. Launcher load AppClassLoader、ExtClassLoader load
    3. Load the ** Threads pass through setContextClassLoader(this.loader)** hold AppClassLoader Set into context
    public class Launcher {
    
    static class AppClassLoader extends URLClassLoader {
    }
    static class ExtClassLoader extends URLClassLoader {
    }
    public Launcher() {
    
    try {
    
    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    }
    // hold AppClassLoader Class loader set as the context of this thread 
    Thread.currentThread().setContextClassLoader(this.loader);
    }
    }
    
  • Step two , The thread continues to execute ,DriverManager Be loaded , Trigger loadInitialDrivers(), Direct trigger SPI Read tool class instantiation

     static {
    
    loadInitialDrivers(); // Internal use is ServiceLoader<Driver> Of api
    println("JDBC DriverManager initialized");
    }
    
     // loadInitialDrivers(); Internal initialization ServiceLoader
    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    
  • Step three ,ServiceLoader< Driver> initialization , Take along AppClassLoader

     public static <S> ServiceLoader<S> load(Class<S> service) {
    
    // What you get is 【 Step one 】 Set in the AppClassLoader
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
    }
    public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
    
    return new ServiceLoader<>(service, loader);
    }
    // Set in AppClassLoader Assign a value to loader
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
    
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    }
    
  • Step four ,ServiceLoader< Driver> Scan directory , initialization SPI Implementation class

    public final class ServiceLoader<S>implements Iterable<S>{
    
    // Specification catalog ,MySQL Driven Jar There must be this file under the package , Inside is SPI The full pathname of the implementation class 
    private static final String PREFIX = "META-INF/services/";
    private S nextService() {
    
    String cn = nextName;//SPI All implementers are limited to their names 
    nextName = null;
    Class<?> c = null;
    try {
    
    // Loading bytecode returns class object . But it doesn't initialize , It doesn't trigger static Code block 
    c = Class.forName(cn, false, loader);
    }
    try {
    
    // cast Not the main business logic . c.newInstance Triggered Driver Static method of 
    S p = service.cast(c.newInstance());
    providers.put(cn, p);// Local cache ( All names are limited , Implementation class object )
    return p;
    }
    }
    }
    
  • Step five ,SPI Implementation class com.mysql.jdbc.Driver After the initialization , use static call DriverManager Registration drives itself , It’s registration com.mysql.jdbc.Driver complete

     static {
    
    try {
    
    java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
    
    throw new RuntimeException("Can't register driver!");
    }
    }
    

5、 … and . Sum up , One sentence summary

  • DriverManager.getConnection() Used internally SPI Mechanism , scanning mysql Of jar Bag META-INF/services/ Get the full pathname and use Class.forName(cn, false, loader),c.newInstance() Load target driver .

  • On the other hand , It also solves why not use Class.forName() You can also break parental delegation , because getConnection It’s encapsulated inside Class.forName(cn, false, loader);

发表回复