SpringBoot 为啥单独加载类路径下spring.factories文件中的类?
SpringBoot 应用运行过程中存在两种类型的类初始化:一部分为已经提前装载到IOC容器中的bean,另一部分则为实时new的bean。
IOC容器中的bean包含:启动类所在包路径下全部的类 以及 spring.factories文件中的类。
类路径下的类并非全部需要加载到JVM中,简单理解为主动使用的类才会被加载到JVM中。spring.factories文件中的类对所有jar包中的类启动选择作用,只有位于spring.factories文件中指定jar中的类才会被加载IOC容器中,当然这些类运行过程中还有通过new方法、反射方式实例化jar包中的其他类。
为什么会存在Spi机制呢?
只是为了框架中实现灵活的扩展。如果没有Spi机制则需要显式写死实现类,后期扩展改动比较麻烦。
在配置数据库相关属性时存在以下选项:
- spring:
- datasource:
- url: jdbc:mysql://localhost:3306/folder?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&serverTimezone=GMT%2B8
- driver-class-name: com.mysql.jdbc.Driver
- username: root
- password: root
其中driver-class-name会触发SPI机制加载Driver的实现类。
mysql-connector-java是MySQL提供实现了JDBC定义的驱动(JDBC是一种规范,定义了Java语言如何去操作数据库,也就是实现相关API,是一种接口规范。mysql-connector-java是MySQL提供实现的JDBC定义的驱动),是Java程序中真正操作MySQL数据库的客户端。简单来说mysql-connector-java允许了通过Java访问MySQL。
spring-boot-starter-jdbc,是Spring提供的,它基于mysql-connector-java,又进行了封装,使得代码更加简单。也就是mysql-connector-java提供的是JDBC,而spring-boot-starter-jdbc提高了简化版的JDBC——JdbcTemplate。
不管是HikariCP还是Druid其实最终都是提供java.sql.Driver类型的驱动。获取到驱动之后进一步才能获取到数据库连接。
java.sql.Driver的实现类都会存在一个静态代码块,在静态代码块中利用DriverManager注册当前的驱动Driver。DriverManager中的静态代码块利用SPI机制加载 META-INF/services/java.sql.Driver 接口的实现类,并反通过射方式实例化。实例化过程中又会执行自身静态代码块,即DriverManager注册当前的驱动Driver。
- public class DriverManager {
-
- private final static CopyOnWriteArrayList
registeredDrivers = new CopyOnWriteArrayList<>(); -
- static {
- loadInitialDrivers();
- }
-
- public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da){
- if(driver != null) {
- registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
- } else {
- throw new NullPointerException();
- }
- }
-
- private static void loadInitialDrivers() {
-
- String drivers = AccessController.doPrivileged(new PrivilegedAction
() { - public String run() {
- return System.getProperty("jdbc.drivers");
- }
- });;
- AccessController.doPrivileged(new PrivilegedAction
() { - public Void run() {
- // SPI 机制加载 META-INF/services/java.sql.Driver 接口的实现类
- ServiceLoader
loadedDrivers = ServiceLoader.load(Driver.class); - Iterator
driversIterator = loadedDrivers.iterator(); - while(driversIterator.hasNext()) {
- driversIterator.next();
- }
- return null;
- }
- });
- if (drivers == null || drivers.equals("")) {
- return;
- }
- String[] driversList = drivers.split(":");
- println("number of Drivers:" + driversList.length);
- for (String aDriver : driversList) {
- Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
- }
- }
-
- @CallerSensitive
- public static Connection getConnection(String url,java.util.Properties info){
- return (getConnection(url, info, Reflection.getCallerClass()));
- }
-
-
- private static Connection getConnection(String url, java.util.Properties info, Class> caller) {
-
- 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");
- }
- SQLException reason = null;
- for(DriverInfo aDriver : registeredDrivers) {
- if(isDriverAllowed(aDriver.driver, callerCL)) {
- return aDriver.driver.connect(url, info);
- }
-
- }
- }
-
-
- private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
- boolean result = false;
- if(driver != null) {
- Class> aClass = null;
- try {
- aClass = Class.forName(driver.getClass().getName(), true, classLoader);
- } catch (Exception ex) {
- result = false;
- }
- result = ( aClass == driver.getClass() ) ? true : false;
- }
-
- return result;
- }
-
- }