事情是这样的,一个夏日的午后,我先是提交了代码到我自己的开发分支,然后将开发分支合并到一个test分支,准备发到测试环境。先用Jenkins构建,正常,然后测试同学更新到test环境,十分钟过去了,更新仍未完成,哟嚯,多半是要凉了。
打开日志,一看,果然报错了。不对呀,我的分支启动是正常的啊,本地重新启动试试,我的分支可以正常启动,于是切到test分支重新启动,果然启动失败,错误信息如下:
从最下方的信息来看,大致是连接数据的时候失败了,抛了空指针,难道是从配置中心没有读取到配置mysql配置吗?
先看看代码
JDBCTemplate.java:49对应的queryDB方法
public void queryDB(String sql, DBCallback callback) throws Exception {
Connection conn = null;
Statement stmt = null;
try {
Class.forName("com.mysql.jdbc.Driver");
// 获取连接,调用的是DriverManager的方法
conn = getConnection(dbUrl, dbUsername, dbPassword);
。。。
} catch (SQLException se) {
。。。
}
} finally {
。。。
}
}
DriverManager.java
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
// Properties实际是一个Hashtable
java.util.Properties info = new java.util.Properties();
// 此处做了判空处理,因此不可能是没有获取到用户名,密码同理
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
// 继续获取connection
return (getConnection(url, info, Reflection.getCallerClass()));
}
DriverManager.java
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
。。。
// url有判空处理
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
。。。
// 遍历每个注册的Driver,如果有一个能获取到connection就返回
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());
// 操蛋就操蛋在这里,每个Driver都会去尝试获取connection,竟然还有Driver会更改info
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;
}
}
}
// 没有找到Driver抛异常
throw new SQLException("No suitable driver found for "+ url, "08001");
}
debug时截的图,已经加载的Drivers
通过调试,发现org.apache.phoenix.queryserver.client.Driver
更改了info
public Connection connect(String url, Properties info) throws SQLException {
String prefix = this.getConnectStringPrefix();
String urlSuffix = url.toLowerCase().substring(prefix.length());
Properties connectionInfo = ConnectStringParser.parse(urlSuffix, info);
// 当没有配置serialization时,就会set一个Serialization.PROTOBUF,WTF(注意,它不是String类型)
if (!connectionInfo.containsKey("serialization") && !connectionInfo.containsKey("serialization".toUpperCase())) {
info.put("serialization", Serialization.PROTOBUF);
}
this.setConnectionInfo(connectionInfo);
return super.connect(url, info);
}
接着往下看,进入到com.mysql.jdbc.NonRegisteringDriver#connect
方法
public java.sql.Connection connect(String url, Properties info) throws SQLException {
if (url == null) {
throw SQLError.createSQLException(Messages.getString("NonRegisteringDriver.1"), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);
}
。。。
Properties props = null;
// 解析url
if ((props = parseURL(url, info)) == null) {
return null;
}
if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) {
return connectFailover(url, info);
}
try {
Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);
return newConn;
} catch (SQLException sqlEx) {
。。。
} catch (Exception ex) {
。。。
}
}
这里是mysql-connector-java:5.1.46
中的com.mysql.jdbc.NonRegisteringDriver#parseURL(String url, Properties defaults)
方法的一个片段
defaults对应的就是info,原本info中只有用户名和密码,但是经过org.apache.phoenix.queryserver.client.Driver
处理之后,里面加入了一个serialization
,value值是Serialization.PROTOBUF
,这个时候defaults.getProperty("serialization")
取出的就不是一个String
,而是null
,urlProps.setProperty(key, property);
就会抛出NullPointerException,urlProps
是一个Hashtable
// 获取String类型的值,如果取出的非String类型,则返回null
public String getProperty(String key) {
Object oval = super.get(key);
String sval = (oval instanceof String) ? (String)oval : null;
return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
}
urlProps
是一个Properties
对象
Properties urlProps = (defaults != null) ? new Properties(defaults) : new Properties();
而 Properties 是Hashtable
public class Properties extends Hashtable<Object,Object>
OK,发现罪魁祸首了,org.apache.phoenix.queryserver.client.Driver
,这个是哪里引入的呢?我们需要它吗?
后续通过mvn依赖树,查到是有有其他团队的jar包传递依赖过来的,exclude之后程序启动正常。