DruidDataSource Spring Bean 初始化
在Spring Boot
项目中,我们只需要简单声明一下Druid
就会自动调用,DruidDataSource
的init()
方法
@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class,
DruidStatViewServletConfiguration.class,
DruidWebStatFilterConfiguration.class,
DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
//声明初始化方法。
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
//DruidDataSourceWrapper extend DruidDataSource
return new DruidDataSourceWrapper();
}
}
驱动加载
当我们设置yml
携带URL
时,就会将URL
加载到jdbcUrl
这个参数中。
spring:
datasource:
druid:
username: XXXX
password: XXXX
url: jdbc:mysql://127.0.0.1:3306/XXXX?createDatabaseIfNotExist=true&characterEncoding=UTF-8&connectTimeout=60000&socketTimeout=60000
解析驱动,根据jdbcUrl
来识别数据库类型,并加载驱动。
protected void resolveDriver() throws SQLException {
if (this.driver == null) {
//根据jdbcUrl识别数据库类型,并返回DriverClassName
if (this.driverClass == null || this.driverClass.isEmpty()) {
this.driverClass = JdbcUtils.getDriverClassName(this.jdbcUrl);
}
if (MockDriver.class.getName().equals(driverClass)) {
driver = MockDriver.instance;
} else if ("com.alibaba.druid.support.clickhouse.BalancedClickhouseDriver".equals(driverClass)) {
Properties info = new Properties();
info.put("user", username);
info.put("password", password);
info.putAll(connectProperties);
driver = new BalancedClickhouseDriver(jdbcUrl, info);
} else {
if (jdbcUrl == null && (driverClass == null || driverClass.length() == 0)) {
throw new SQLException("url not set");
}
//通过Thread.currentThread().getContextClassLoader()来加载driver
driver = JdbcUtils.createDriver(driverClassLoader, driverClass);
}
} else {
if (this.driverClass == null) {
this.driverClass = driver.getClass().getName();
}
}
}
创建连接 createPhysicalConnection()
用户名和密码加密
通过createPhysicalConnection
了解到,我们可以通过继承以下两个类对密码和用户进行加密。
但存在着问题,加密通常非常的耗时,每次创建连接都要进行一次加密验证。
如果一定要对密码和用户名做加密的话,建议设置initial-size
参数为设置的线程池最大大小。减少创建连接的次数。
类 | 说明 |
---|---|
javax.security.auth.callback.NameCallback | 用户名加密 |
com.alibaba.druid.utilDruidPasswordCallback | 密码加密 |
在YML的使用如下:
spring:
datasource:
druid:
user-callback: XXX
password-callback: XXX
校验和初始化SQL
当我们设置 connection-init-sqls
,每次创建连接都会进行一次初始化执行SQL,可以用于记录连接的创建信息。
设置validation-query:
,则每次创建都会进行一次校验。
设置valid-connection-checker
可以实现连接重连。
spring:
datasource:
druid:
connection-init-sqls: XXXX
validation-query: XXX
valid-connection-checker: com.mysql.jdbc.integration.jboss.MysqlValidConnectionChecker
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
String url = this.getUrl();
Properties connectProperties = getConnectProperties();
String user;
//判断是否有名称加密
if (getUserCallback() != null) {
user = getUserCallback().getName();
} else {
user = getUsername();
}
String password = getPassword();
//获取密码加密类PasswordCallback
PasswordCallback passwordCallback = getPasswordCallback();
//如果密码有加密,并且继承于DruidPasswordCallback,则通过该类来获取密码password。
if (passwordCallback != null) {
if (passwordCallback instanceof DruidPasswordCallback) {
DruidPasswordCallback druidPasswordCallback = (DruidPasswordCallback) passwordCallback;
druidPasswordCallback.setUrl(url);
druidPasswordCallback.setProperties(connectProperties);
}
char[] chars = passwordCallback.getPassword();
if (chars != null) {
password = new String(chars);
}
}
//将信息加载到physicalConnectProperties进行创建连接
Properties physicalConnectProperties = new Properties();
if (connectProperties != null) {
physicalConnectProperties.putAll(connectProperties);
}
if (user != null && user.length() != 0) {
physicalConnectProperties.put("user", user);
}
if (password != null && password.length() != 0) {
physicalConnectProperties.put("password", password);
}
Connection conn = null;
long connectStartNanos = System.nanoTime();
long connectedNanos, initedNanos, validatedNanos;
Map<String, Object> variables = initVariants
? new HashMap<String, Object>()
: null;
Map<String, Object> globalVariables = initGlobalVariants
? new HashMap<String, Object>()
: null;
createStartNanosUpdater.set(this, connectStartNanos);
creatingCountUpdater.incrementAndGet(this);
try {
//通过getDriver().connect(url, info)来获取连接
conn = createPhysicalConnection(url, physicalConnectProperties);
connectedNanos = System.nanoTime();
if (conn == null) {
throw new SQLException("connect error, url " + url + ", driverClass " + this.driverClass);
}
//如果存在初始化SQL,则会去调用,设置值connection-init-sqls
initPhysicalConnection(conn, variables, globalVariables);
initedNanos = System.nanoTime();
validateConnection(conn);
validatedNanos = System.nanoTime();
setFailContinuous(false);
setCreateError(null);
} catch (SQLException ex) {
setCreateError(ex);
JdbcUtils.close(conn);
throw ex;
} catch (RuntimeException ex) {
setCreateError(ex);
JdbcUtils.close(conn);
throw ex;
} catch (Error ex) {
createErrorCountUpdater.incrementAndGet(this);
setCreateError(ex);
JdbcUtils.close(conn);
throw ex;
} finally {
long nano = System.nanoTime() - connectStartNanos;
createTimespan += nano;
creatingCountUpdater.decrementAndGet(this);
}
//返回连接
return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
}