java.lang.ClassNotFoundException: com.zaxxer.hikari.metrics.MetricsTrackerFactory的深入研究

springboot启动报错java.lang.ClassNotFoundException: com.zaxxer.hikari.metrics.MetricsTrackerFactory


)

springboot启动报错java.lang.ClassNotFoundException: com.zaxxer.hikari.metrics.MetricsTrackerFactory

Caused by: java.lang.ClassNotFoundException: com.zaxxer.hikari.metrics.MetricsTrackerFactory
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:92)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
… 27 common frames omitted

项目本地运行没问题,打包到服务器上报了这个错,啥原因?
以下是问题解决后个人认为比较正确的问题定位思路。
springboot项目版本号为2.2.2。

解决过程

我们知道Hikari是一款连接池,在springboot2.0之后采用的默认的数据库连接池就是Hikari。但是我目前的项目用的数据库连接池是druid,莫非是初始化还是加载了hikari?
查了资料发现hikari是在spring-boot-starter-jdbc和spring-boot-starter-data-jpa包下默认解析HikariCP依赖,项目里并没有引用到hakari。于是我查看了一下目前的maven引用情况:
发现在sharding-jdbc包里引用到里HikariCP-java6的包
于是我把该引用去除:

   <dependency>
            <groupId>io.shardingsphere</groupId>
            <artifactId>sharding-jdbc</artifactId>
            <version>3.0.0.M3</version>
                        <exclusions>
                            <exclusion>
                                <groupId>com.zaxxer</groupId>
                                <artifactId>HikariCP-java6</artifactId>
                            </exclusion>
                        </exclusions>
        </dependency>

重新部署该项目,发现项目成功启动了。
但是这个时候有个疑问,为什么把hikari依赖去除了,反而“加载成功了呢”?
到这里,应该去了解一下springboot,它是如何默认初始化Hikari连接池的。
在这之前,我们需要了解一个知识,即springboot的配置项依赖是如何实现的:
大家应该都知道,我们的springboot启动类上要添加一个注解@SpringBootApplication

下面展示一些该注解的内部。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication 

里面关键的是

@SpringBootConfiguration
@EnableAutoConfiguration

@SpringBootConfiguration
这个注解,只是@Configuration注解的派生注解,跟@Configuration注解的功能一致,标注这个类是一个配置类,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。这个不是本文重点,先不展开。
@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

这里导入了AutoConfigurationImportSelector类:
接下来看该类的selectImports方法:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

先看下去,loadMetadata方法:

protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

可以看到,它通过PATH加载了需要自动装配的元数据:


org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,reactor.core.publisher.Flux,org.springframework.data.cassandra.core.ReactiveCassandraTemplate
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration.ConditionalOnWebApplication=SERVLET
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration=
.....

等号右边是需要进行自动装载的类。

在其中,可以看到一个DataSourceAutoConfiguration类。字面意思可以看出来这是自动配置数据源的类。
到这里,我们可以看出spring容器在初始化时通过找到元数据,加载其内的配置类,实现自动装配,包括我们要找的数据源Datasource(我们可以通过添加配置元数据properties文件添加默认的装配,也可以通过@configuration来实现自定义的装配)。
接下来就来看看DataSourceAutoConfiguration是怎么实现数据源的初始化的:
ConditionalOnMissingBean中,datasource已经通过druid生成了,但是XADAtaSource没有生成。因此继续import。

	@Configuration(proxyBeanMethods = false)
	@Conditional(PooledDataSourceCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
			DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
			DataSourceJmxConfiguration.class })
	protected static class PooledDataSourceConfiguration {

	}

可以看到其引入了包括Hikari的四个数据源配置类,都在DataSourceConfiguration中:

/**
	 * Hikari DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(HikariDataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
			matchIfMissing = true)
	static class Hikari {

		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.hikari")
		HikariDataSource dataSource(DataSourceProperties properties) {
			HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
			if (StringUtils.hasText(properties.getName())) {
				dataSource.setPoolName(properties.getName());
			}
			return dataSource;
		}

	}

接下来具体看hikari数据源的创建过程,首先看到@ConditionalOnClass(HikariDataSource.class)
这个注解意思是该类的初始化需要找到HikariDataSource。
这先解释了为什么我把hikari的依赖去掉后,项目启动成功,因为它根本不会去创建hikari连接池了,也不会出现之后的问题。

同时再看dataSource上的注解
@ConfigurationProperties(prefix = “spring.datasource.hikari”)
它从元数据spring-configuration-metadata.json中匹配name前缀为spring.datasource.hikari的属性,其中就有:

  {
      "name": "spring.datasource.hikari.metrics-tracker-factory",
      "type": "com.zaxxer.hikari.metrics.MetricsTrackerFactory",
      "sourceType": "com.zaxxer.hikari.HikariDataSource"
    },

也就是说需要初始化该对象。

这时我没有依赖hikari,因此找不到该类,该数据源也不会进行初始化.
具体的体现方式是在
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
这边类引入的时候就出现了加载错误。

Caused by: java.lang.ClassNotFoundException: com.zaxxer.hikari.metrics.MetricsTrackerFactory
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
        at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:92)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        ... 27 common frames omitted

那么我把之前的依赖加上看看:

  <dependency>
            <groupId>io.shardingsphere</groupId>
            <artifactId>sharding-jdbc</artifactId>
            <version>3.0.0.M3</version>
<!--                        <exclusions>-->
<!--                            <exclusion>-->
<!--                                <groupId>com.zaxxer</groupId>-->
<!--                                <artifactId>HikariCP-java6</artifactId>-->
<!--                            </exclusion>-->
<!--                        </exclusions>-->
        </dependency>


先查看createDateSource方法:
这边传入的type即HikariDataSource.class

protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
		return (T) properties.initializeDataSourceBuilder().type(type).build();
	}

查看build()方法:

   public T build() {
        Class<? extends DataSource> type = this.getType();
        DataSource result = (DataSource)BeanUtils.instantiateClass(type);
        this.maybeGetDriverClassName();
        this.bind(result);
        return result;
    }

可以看到通过BeanUtils去加载对应到数据源(在这里是HikariDataSource):

这时查看该类:

  //
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.zaxxer.hikari;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.pool.HikariPool;
import com.zaxxer.hikari.proxy.IHikariConnectionProxy;
import com.zaxxer.hikari.util.DriverDataSource;
import java.io.Closeable;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Wrapper;
import java.util.HashMap;
import java.util.Iterator;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HikariDataSource extends HikariConfig implements DataSource, Closeable {
    private static final Logger LOGGER = LoggerFactory.getLogger(HikariDataSource.class);
    private final HashMap<HikariDataSource.MultiPoolKey, HikariPool> multiPool;
    private volatile boolean isShutdown;
    private final HikariPool fastPathPool;
    private volatile HikariPool pool;

    public HikariDataSource() {
        this.fastPathPool = null;
        this.multiPool = new HashMap();
    }

    public HikariDataSource(HikariConfig configuration) {
        configuration.validate();
        configuration.copyState(this);
        this.multiPool = new HashMap();
        LOGGER.info("HikariCP pool {} is starting.", configuration.getPoolName());
        this.pool = this.fastPathPool = new HikariPool(this);
        this.multiPool.put(new HikariDataSource.MultiPoolKey(this.getUsername(), this.getPassword()), this.pool);
    }

    public Connection getConnection() throws SQLException {
        if (this.isShutdown) {
            throw new SQLException("Pool has been shutdown");
        } else if (this.fastPathPool != null) {
            return this.fastPathPool.getConnection();
        } else {
            HikariPool result = this.pool;
            if (result == null) {
                synchronized(this) {
                    result = this.pool;
                    if (result == null) {
                        this.validate();
                        LOGGER.info("HikariCP pool {} is starting.", this.getPoolName());
                        this.pool = result = new HikariPool(this);
                        this.multiPool.put(new HikariDataSource.MultiPoolKey(this.getUsername(), this.getPassword()), this.pool);
                    }
                }
            }

            return result.getConnection();
        }
    }

    /** @deprecated */
    @Deprecated
    public Connection getConnection(String username, String password) throws SQLException {
        if (this.isShutdown) {
            throw new SQLException("Pool has been shutdown");
        } else {
            HikariDataSource.MultiPoolKey key = new HikariDataSource.MultiPoolKey(username, password);
            HikariPool hikariPool;
            synchronized(this.multiPool) {
                hikariPool = (HikariPool)this.multiPool.get(key);
                if (hikariPool == null) {
                    hikariPool = new HikariPool(this, username, password);
                    this.multiPool.put(key, hikariPool);
                }
            }

            return hikariPool.getConnection();
        }
    }

    public PrintWriter getLogWriter() throws SQLException {
        return this.pool.getDataSource() != null ? this.pool.getDataSource().getLogWriter() : null;
    }

    public void setLogWriter(PrintWriter out) throws SQLException {
        if (this.pool.getDataSource() != null) {
            this.pool.getDataSource().setLogWriter(out);
        }

    }

    public void setLoginTimeout(int seconds) throws SQLException {
        Iterator var2 = this.multiPool.values().iterator();

        while(var2.hasNext()) {
            HikariPool hikariPool = (HikariPool)var2.next();
            hikariPool.getDataSource().setLoginTimeout(seconds);
        }

    }

    public int getLoginTimeout() throws SQLException {
        HikariPool hikariPool = (HikariPool)this.multiPool.values().iterator().next();
        return hikariPool != null ? hikariPool.getDataSource().getLoginTimeout() : 0;
    }

    public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
        throw new SQLFeatureNotSupportedException();
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        if (iface.isInstance(this)) {
            return this;
        } else {
            if (this.pool != null) {
                if (iface.isInstance(this.pool.getDataSource())) {
                    return this.pool.getDataSource();
                }

                if (this.pool.getDataSource() instanceof Wrapper) {
                    return this.pool.getDataSource().unwrap(iface);
                }
            }

            throw new SQLException("Wrapped DataSource is not an instance of " + iface);
        }
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        if (iface.isInstance(this)) {
            return true;
        } else {
            if (this.pool != null) {
                if (iface.isInstance(this.pool.getDataSource())) {
                    return true;
                }

                if (this.pool.getDataSource() instanceof Wrapper) {
                    return this.pool.getDataSource().isWrapperFor(iface);
                }
            }

            return false;
        }
    }

    public void setMetricRegistry(Object metricRegistry) {
        boolean isAlreadySet = this.getMetricRegistry() != null;
        super.setMetricRegistry(metricRegistry);
        if (this.fastPathPool != null || this.pool != null) {
            if (isAlreadySet) {
                throw new IllegalStateException("MetricRegistry can only be set one time");
            }

            this.pool.setMetricRegistry((MetricRegistry)super.getMetricRegistry());
        }

    }

    public void setHealthCheckRegistry(Object healthCheckRegistry) {
        boolean isAlreadySet = this.getHealthCheckRegistry() != null;
        super.setHealthCheckRegistry(healthCheckRegistry);
        if (this.fastPathPool != null || this.pool != null) {
            if (isAlreadySet) {
                throw new IllegalStateException("HealthCheckRegistry can only be set one time");
            }

            this.pool.setHealthCheckRegistry((HealthCheckRegistry)super.getHealthCheckRegistry());
        }

    }

    public void evictConnection(Connection connection) {
        if (!this.isShutdown && this.pool != null && connection instanceof IHikariConnectionProxy) {
            this.pool.evictConnection((IHikariConnectionProxy)connection);
        }

    }

    public void suspendPool() {
        if (!this.isShutdown && this.pool != null) {
            this.pool.suspendPool();
        }

    }

    public void resumePool() {
        if (!this.isShutdown && this.pool != null) {
            this.pool.resumePool();
        }

    }

    public void close() {
        this.shutdown();
    }

    public void shutdown() {
        if (!this.isShutdown) {
            this.isShutdown = true;
            if (this.fastPathPool != null) {
                this.shutdownHelper(this.fastPathPool);
            }

            Iterator var1 = this.multiPool.values().iterator();

            while(var1.hasNext()) {
                HikariPool hikariPool = (HikariPool)var1.next();
                this.shutdownHelper(hikariPool);
            }

        }
    }

    public String toString() {
        return String.format("HikariDataSource (%s)", this.pool);
    }

    private void shutdownHelper(HikariPool hPool) {
        try {
            hPool.shutdown();
        } catch (InterruptedException var3) {
            LoggerFactory.getLogger(this.getClass()).warn("Interrupted during shutdown", var3);
        }

        if (hPool.getDataSource() instanceof DriverDataSource) {
            ((DriverDataSource)hPool.getDataSource()).shutdown();
        }

    }

    private static class MultiPoolKey {
        private String username;
        private String password;

        MultiPoolKey(String username, String password) {
            this.username = username;
            this.password = password;
        }

        public int hashCode() {
            return this.password == null ? 0 : this.password.hashCode();
        }

        public boolean equals(Object obj) {
            HikariDataSource.MultiPoolKey otherKey = (HikariDataSource.MultiPoolKey)obj;
            if (this.username != null && !this.username.equals(otherKey.username)) {
                return false;
            } else if (this.username == null && otherKey.username != null) {
                return false;
            } else if (this.password != null && !this.password.equals(otherKey.password)) {
                return false;
            } else {
                return this.password != null || otherKey.password == null;
            }
        }
    }
}

发现该版本的hikaridatasource中并没有该属性。从而导致启动时报找不到该类的错误。
问题的原因找到了,接下来看看springboot其要求的hikari版本

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

查看spring-boot-starter-parent

 <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
  </parent>

查看spring-boot-dependencies

 <hikaricp.version>3.4.1</hikaricp.version>

可以看到其支持的版本为3.4.1。
那么现在我主动引入该版本看看。

     <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>3.4.1</version>
        </dependency>

查看HikariDataSource类

public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) {
        boolean isAlreadySet = this.getMetricsTrackerFactory() != null;
        super.setMetricsTrackerFactory(metricsTrackerFactory);
        HikariPool p = this.pool;
        if (p != null) {
            if (isAlreadySet) {
                throw new IllegalStateException("MetricsTrackerFactory can only be set one time");
            }

            p.setMetricsTrackerFactory(super.getMetricsTrackerFactory());
        }

    }

看到了MetricsTrackerFactory。

发布到服务器报错到原因找到了,那么为什么本地跑不报错呢?
看下本地的maven仓库:
在这里插入图片描述
发现有好几个hikari包,其中也包括了最新版本的包(Hikari),mvn clean,mvn package之后发现,高版本的包还是存在(发现spring-boot-dependencies里的很多没用到的依赖也被下载到了本地仓库),因此它能够找到该类。
经过调试发现我们实际引入3.4.1之后,还是不会生成hikari数据源。

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(HikariDataSource.class)
	static class HikariPoolDataSourceMetadataProviderConfiguration {

		@Bean
		DataSourcePoolMetadataProvider hikariPoolDataSourceMetadataProvider() {
			return (dataSource) -> {
				HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource, HikariDataSource.class);
				if (hikariDataSource != null) {
					return new HikariDataSourcePoolMetadata(hikariDataSource);
				}
				return null;
			};
		}

	}

是因为在这里,获得了druid数据源,并无法wrap(转换)为hikari数据源,直接return null,结束了hikari数据源的初始化。
在这里插入图片描述

总结

该错误原因是引入了低版本的hikari,导致springboot默认配置的hikari对象属性类找不到,出现classNotFound异常。去掉其低版本依赖就可以了。
在springboot容器初始化时候报的错,我们可以去追溯其初始化过程,理解spring装载过程,就很容易解决这类问题了。
除此之外,默认的hikari数据源在生成时会判断是否已经创建了数据源,若创建的数据源为hikari类型,则返回其,表明hikari创建成功,若不是,则返回null,表明hikari数据源创建失败。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值