jar包冲突一种解决思路
- 前言
- 一、shade打包
- 二、java获取数据库连接
前言
跟着部门大佬学习了一种解决jar冲突的方法,起因是因为连接数据库时pg的url前缀、driver与基于pg修改扩展的数据库url前缀、driver名称相同,同一个jar无法同时支持连接两种数据库(阴差阳错。。。其实可以连)。需要将两个数据库驱动jar都引入,但又会引起冲突,提出使用maven的shade打包方式,通过改变包名来解决冲突, 因为包的本来用途就是解决同名问题,工程里引入打的新jar,这样可以加载两个jar包,同时自定义url对pg连接驱动做了代理,这样避免地址相同。
一、shade打包
maven-shade-plugin 打包,并且修改依赖中冲突的包名,例如下面pattern指示原包名,shadedPattern新包名。
<relocations>
<relocation>
<pattern>com.google.protobuf</pattern>
<shadedPattern>com.shade.google.protobuf</shadedPattern>
</relocation>
</relocations>
二、java获取数据库连接
走一遍获取数据库连接过程,一般连接数据库步骤:
Class.forName(“com.mysql.jdbc.Driver”); //注册驱动
String url = “jdbc:mysql://localhost:3306/test?user=root&password=123456″;
Connection con = DriverManager.getConnection(url); //获取连接
Statement statement = con.createStatement();
注册驱动这步其实是可以省略的,查看DriverManager源码,类开头有段注释
* <P>Applications no longer need to explicitly load JDBC drivers using <code>Class.forName()</code>. Existing programs
* which currently load JDBC drivers using <code>Class.forName()</code> will continue to work without
* modification.
DriverManager中在静态代码块首先注册加载驱动,通过注释也可以看出来。原理是虚拟机加载类初始化的时候按顺序初始化父类的静态属性及静态代码块、子类静态属性及静态代码块、父类属性调用父构造函数、子类属性及调用子构造函数。
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
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;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
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);
}
}
}
从getConnection方法看出获取连接时循环查找注册的驱动registeredDrivers,因此不用Class.forName注册,虚拟机加载类的时候已经完成注册。
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
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;
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());
Connection con = aDriver.driver.connect(url, info);
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中loadInitialDrivers方法通过ServiceLoader注册加载服务,这里使用了java提供的spi标准(Service Provider Interface)实现,大致就是指服务提供方接口,一种JVM层面的服务注册发现机制。
一个服务(service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按spi标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名。如有一个服务接口com.test.Service,其服务实现类为com.test.ChildService,那此时需要在META-INF/services中放置文件com.test.Service,其中的内容就为该实现类的全限定名com.test.ChildService,有多个服务实现,每一行写一个服务实现。
可参考:
[1] https://www.jianshu.com/p/27c837293aeb
[2] http://blog.itpub.net/69912579/viewspace-2656555/
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
ServiceLoader类里大概方式就是按行读取services目录下文件获取实现类初始化。
在数据库驱动包里,都实现了Driver接口,并且在静态代码块中初始化注册。比如mysql、oralce都实现了上述spi方式。
参考:
https://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider