这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
由于源码分析内容较多,一共分为三个部分,完整内容如下:
1. Spring动态多数据源--源码分析及解读(一)
2. Spring动态多数据源--源码分析及解读(二)
3. Spring动态多数据源--源码分析及解读(三)
四、通过责任链设计模式获取数据源名称
Spring动态多数据源, 获取数据源名称的方式有3种,这3中方式采用的是责任链方式连续获取的。首先在header中获取,header中没有,去session中获取, session中也没有, 通过spel获取。
上图是DSProcessor处理器的类图。 一个接口量, 三个具体实现类,主要来看一下接口类实现
1. DsProcessor 抽象类
package com.baomidou.dynamic.datasource.processor;
import org.aopalliance.intercept.MethodInvocation;
public abstract class DsProcessor {
private DsProcessor nextProcessor;
public void setNextProcessor(DsProcessor dsProcessor) {
this.nextProcessor = dsProcessor;
}
/**
* 抽象匹配条件 匹配才会走当前执行器否则走下一级执行器
*
* @param key DS注解里的内容
* @return 是否匹配
*/
public abstract boolean matches(String key);
/**
* 决定数据源
* <pre>
* 调用底层doDetermineDatasource,
* 如果返回的是null则继续执行下一个,否则直接返回
* </pre>
*
* @param invocation 方法执行信息
* @param key DS注解里的内容
* @return 数据源名称
*/
public String determineDatasource(MethodInvocation invocation, String key) {
if (matches(key)) {
String datasource = doDetermineDatasource(invocation, key);
if (datasource == null && nextProcessor != null) {
return nextProcessor.determineDatasource(invocation, key);
}
return datasource;
}
if (nextProcessor != null) {
return nextProcessor.determineDatasource(invocation, key);
}
return null;
}
/**
* 抽象最终决定数据源
*
* @param invocation 方法执行信息
* @param key DS注解里的内容
* @return 数据源名称
*/
public abstract String doDetermineDatasource(MethodInvocation invocation, String key);
}
这里定义了DsProcessor nextProcessor属性, 下一个处理器。 判断是否获取到了datasource, 如果获取到了则直接返回, 没有获取到,则调用下一个处理器。这个逻辑就是处理器的主逻辑,在determineDatasource(MethodInvocation invocation, String key)方法中实现。
接下来,每一个子类都会自定义实现doDetermineDatasource获取目标数据源的方法。不同的实现类获取数据源的方式是不同的。
下面看看具体实现类的主逻辑代码
2.DsHeaderProcessor: 从请求的header中获取ds数据源名称。
public class DsHeaderProcessor extends DsProcessor {
/**
* header prefix
*/
private static final String HEADER_PREFIX = "#header";
@Override
public boolean matches(String key) {
return key.startsWith(HEADER_PREFIX);
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request.getHeader(key.substring(8));
}
}
3.DsSessionProcessor: 从session中获取数据源ds名称
public class DsSessionProcessor extends DsProcessor {
/**
* session开头
*/
private static final String SESSION_PREFIX = "#session";
@Override
public boolean matches(String key) {
return key.startsWith(SESSION_PREFIX);
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request.getSession().getAttribute(key.substring(9)).toString();
}
}
4. DsSpelExpressionProcessor: 通过spel表达式获取ds数据源名称
public class DsSpelExpressionProcessor extends DsProcessor {
/**
* 参数发现器
*/
private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
/**
* Express语法解析器
*/
private static final ExpressionParser PARSER = new SpelExpressionParser();
/**
* 解析上下文的模板
* 对于默认不设置的情况下,从参数中取值的方式 #param1
* 设置指定模板 ParserContext.TEMPLATE_EXPRESSION 后的取值方式: #{#param1}
* issues: https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/199
*/
private ParserContext parserContext = new ParserContext() {
@Override
public boolean isTemplate() {
return false;
}
@Override
public String getExpressionPrefix() {
return null;
}
@Override
public String getExpressionSuffix() {
return null;
}
};
@Override
public boolean matches(String key) {
return true;
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
Method method = invocation.getMethod();
Object[] arguments = invocation.getArguments();
EvaluationContext context = new MethodBasedEvaluationContext(null, method, arguments, NAME_DISCOVERER);
final Object value = PARSER.parseExpression(key, parserContext).getValue(context);
return value == null ? null : value.toString();
}
public void setParserContext(ParserContext parserContext) {
this.parserContext = parserContext;
}
}
他们三个的层级关系是在哪里定义的呢?在DynamicDataSourceAutoConfiguration.java配置文件中
5. DynamicDataSourceAutoConfiguration.java配置文件
@Bean
@ConditionalOnMissingBean
public DsProcessor dsProcessor() {
DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
DsSessionProcessor sessionProcessor = new DsSessionProcessor();
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
headerProcessor.setNextProcessor(sessionProcessor);
sessionProcessor.setNextProcessor(spelExpressionProcessor);
return headerProcessor;
}
第一层是headerProcessor,第二层是sessionProcessor, 第三层是spelExpressionProcessor。层级调用,最后获得ds。
以上就是对数据源处理器模块的的分析,那么最终在哪里被调用呢?来看下一个模块。
五、动态数据源注解通知模块
这一块对应的源代码结构如下:
这个模块里主要有三部分:
- 切面类:DynamicDataSourceAdvisor,DynamicDataSourceAnnotationAdvisor
- 切点类:DynamicAspectJExpressionPointcut,DynamicJdkRegexpMethodPointcut
- 前置通知类:DynamicDataSourceAnnotationInterceptor
他们之间的关系如下。这里主要是aop方面的知识体系。具体项目结构图如下:
因为在项目中使用最多的情况是通过注解的方式来解析,所以,我们重点看一下两个文件
1.DynamicDataSourceAnnotationInterceptor:自定义的前置通知类
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
/**
* The identification of SPEL.
*/
private static final String DYNAMIC_PREFIX = "#";
private static final DataSourceClassResolver RESOLVER = new DataSourceClassResolver();
@Setter
private DsProcessor dsProcessor;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
DynamicDataSourceContextHolder.push(determineDatasource(invocation));
return invocation.proceed();
} finally {
DynamicDataSourceContextHolder.poll();
}
}
private String determineDatasource(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
DS ds = method.isAnnotationPresent(DS.class) ? method.getAnnotation(DS.class)
: AnnotationUtils.findAnnotation(RESOLVER.targetClass(invocation), DS.class);
String key = ds.value();
return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key;
}
}
这里入参中有一个是DsProcessor,也就是ds处理器。在determineDatasource中看看DS的value值是否包含#,如果包含就经过dsProcessor处理后获得key,如果不包含#则直接返回注解的value值。
2.DynamicDataSourceAnnotationAdvisor 切面类
public class DynamicDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor implements
BeanFactoryAware {
private Advice advice;
private Pointcut pointcut;
public DynamicDataSourceAnnotationAdvisor(@NonNull DynamicDataSourceAnnotationInterceptor dynamicDataSourceAnnotationInterceptor) {
this.advice = dynamicDataSourceAnnotationInterceptor;
this.pointcut = buildPointcut();
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
}
}
private Pointcut buildPointcut() {
Pointcut cpc = new AnnotationMatchingPointcut(DS.class, true);
Pointcut mpc = AnnotationMatchingPointcut.forMethodAnnotation(DS.class);
return new ComposablePointcut(cpc).union(mpc);
}
}
在切面类的构造函数中设置了前置通知和切点。这个类在项目启动的时候就会被加载。所有带有DS注解的方法都会被扫描,在方法被调用的时候触发前置通知。
六、数据源创建器
这是最底层的操作了,创建数据源。至于到底创建哪种类型的数据源,是由上层配置决定的,在这里,定义了4中类型的数据源。 并通过组合的方式,用到那个数据源,就动态的创建哪个数据源。
下面来看这个模块的源代码结构:
这里面定义了一个数据源组合类和四种类型的数据源。我们来看看他们之间的关系
四个基本的数据源类,最后通过DataSourceCreator类组合创建数据源,这里面使用了简单工厂模式创建类。下面来一个一个看看
1.BasicDataSourceCreator:基础数据源创建器
package com.baomidou.dynamic.datasource.creator;
import com.baomidou.dynamic.datasource.exception.ErrorCreateDataSourceException;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import javax.sql.DataSource;
import java.lang.reflect.Method;
/**
* 基础数据源创建器
*
* @author TaoYu
* @since 2020/1/21
*/
@Data
@Slf4j
public class BasicDataSourceCreator {
private static Method createMethod;
private static Method typeMethod;
private static Method urlMethod;
private static Method usernameMethod;
private static Method passwordMethod;
private static Method driverClassNameMethod;
private static Method buildMethod;
static {
//to support springboot 1.5 and 2.x
Class<?> builderClass = null;
try {
builderClass = Class.forName("org.springframework.boot.jdbc.DataSourceBuilder");
} catch (Exception ignored) {
}
if (builderClass == null) {
try {
builderClass = Class.forName("org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder");
} catch (Exception e) {
log.warn("not in springBoot ENV,could not create BasicDataSourceCreator");
}
}
if (builderClass != null) {
try {
createMethod = builderClass.getDeclaredMethod("create");
typeMethod = builderClass.getDeclaredMethod("type", Class.class);
urlMethod = builderClass.getDeclaredMethod("url", String.class);
usernameMethod = builderClass.getDeclaredMethod("username", String.class);
passwordMethod = builderClass.getDeclaredMethod("password", String.class);
driverClassNameMethod = builderClass.getDeclaredMethod("driverClassName", String.class);
buildMethod = builderClass.getDeclaredMethod("build");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 创建基础数据源
*
* @param dataSourceProperty 数据源参数
* @return 数据源
*/
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
try {
Object o1 = createMethod.invoke(null);
Object o2 = typeMethod.invoke(o1, dataSourceProperty.getType());
Object o3 = urlMethod.invoke(o2, dataSourceProperty.getUrl());
Object o4 = usernameMethod.invoke(o3, dataSourceProperty.getUsername());
Object o5 = passwordMethod.invoke(o4, dataSourceProperty.getPassword());
Object o6 = driverClassNameMethod.invoke(o5, dataSourceProperty.getDriverClassName());
return (DataSource) buildMethod.invoke(o6);
} catch (Exception e) {
throw new ErrorCreateDataSourceException(
"dynamic-datasource create basic database named " + dataSourceProperty.getPoolName() + " error");
}
}
}
这里就有两块,一个是类初始化的时候初始化成员变量, 另一个是创建数据源。当被调用createDataSource的时候执行创建数据源,使用的反射机制创建数据源。
2.JndiDataSourceCreator 使用jndi的方式创建数据源
public class JndiDataSourceCreator {
private static final JndiDataSourceLookup LOOKUP = new JndiDataSourceLookup();
/**
* 创建基础数据源
*
* @param name 数据源参数
* @return 数据源
*/
public DataSource createDataSource(String name) {
return LOOKUP.getDataSource(name);
}
}
这里通过name查找的方式过去datasource
3.DruidDataSourceCreator: 创建druid类型的数据源
public class DruidDataSourceCreator {
private DruidConfig druidConfig;
@Autowired(required = false)
private ApplicationContext applicationContext;
public DruidDataSourceCreator(DruidConfig druidConfig) {
this.druidConfig = druidConfig;
}
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(dataSourceProperty.getUsername());
dataSource.setPassword(dataSourceProperty.getPassword());
dataSource.setUrl(dataSourceProperty.getUrl());
dataSource.setDriverClassName(dataSourceProperty.getDriverClassName());
dataSource.setName(dataSourceProperty.getPoolName());
DruidConfig config = dataSourceProperty.getDruid();
Properties properties = config.toProperties(druidConfig);
String filters = properties.getProperty("druid.filters");
List<Filter> proxyFilters = new ArrayList<>(2);
if (!StringUtils.isEmpty(filters) && filters.contains("stat")) {
StatFilter statFilter = new StatFilter();
statFilter.configFromProperties(properties);
proxyFilters.add(statFilter);
}
if (!StringUtils.isEmpty(filters) && filters.contains("wall")) {
WallConfig wallConfig = DruidWallConfigUtil.toWallConfig(dataSourceProperty.getDruid().getWall(), druidConfig.getWall());
WallFilter wallFilter = new WallFilter();
wallFilter.setConfig(wallConfig);
proxyFilters.add(wallFilter);
}
if (!StringUtils.isEmpty(filters) && filters.contains("slf4j")) {
Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();
// 由于properties上面被用了,LogFilter不能使用configFromProperties方法,这里只能一个个set了。
DruidSlf4jConfig slf4jConfig = druidConfig.getSlf4j();
slf4jLogFilter.setStatementLogEnabled(slf4jConfig.getEnable());
slf4jLogFilter.setStatementExecutableSqlLogEnable(slf4jConfig.getStatementExecutableSqlLogEnable());
proxyFilters.add(slf4jLogFilter);
}
if (this.applicationContext != null) {
for (String filterId : druidConfig.getProxyFilters()) {
proxyFilters.add(this.applicationContext.getBean(filterId, Filter.class));
}
}
dataSource.setProxyFilters(proxyFilters);
dataSource.configFromPropety(properties);
//连接参数单独设置
dataSource.setConnectProperties(config.getConnectionProperties());
//设置druid内置properties不支持的的参数
Boolean testOnReturn = config.getTestOnReturn() == null ? druidConfig.getTestOnReturn() : config.getTestOnReturn();
if (testOnReturn != null && testOnReturn.equals(true)) {
dataSource.setTestOnReturn(true);
}
Integer validationQueryTimeout =
config.getValidationQueryTimeout() == null ? druidConfig.getValidationQueryTimeout() : config.getValidationQueryTimeout();
if (validationQueryTimeout != null && !validationQueryTimeout.equals(-1)) {
dataSource.setValidationQueryTimeout(validationQueryTimeout);
}
Boolean sharePreparedStatements =
config.getSharePreparedStatements() == null ? druidConfig.getSharePreparedStatements() : config.getSharePreparedStatements();
if (sharePreparedStatements != null && sharePreparedStatements.equals(true)) {
dataSource.setSharePreparedStatements(true);
}
Integer connectionErrorRetryAttempts =
config.getConnectionErrorRetryAttempts() == null ? druidConfig.getConnectionErrorRetryAttempts()
: config.getConnectionErrorRetryAttempts();
if (connectionErrorRetryAttempts != null && !connectionErrorRetryAttempts.equals(1)) {
dataSource.setConnectionErrorRetryAttempts(connectionErrorRetryAttempts);
}
Boolean breakAfterAcquireFailure =
config.getBreakAfterAcquireFailure() == null ? druidConfig.getBreakAfterAcquireFailure() : config.getBreakAfterAcquireFailure();
if (breakAfterAcquireFailure != null && breakAfterAcquireFailure.equals(true)) {
dataSource.setBreakAfterAcquireFailure(true);
}
Integer timeout = config.getRemoveAbandonedTimeoutMillis() == null ? druidConfig.getRemoveAbandonedTimeoutMillis()
: config.getRemoveAbandonedTimeoutMillis();
if (timeout != null) {
dataSource.setRemoveAbandonedTimeout(timeout);
}
Boolean abandoned = config.getRemoveAbandoned() == null ? druidConfig.getRemoveAbandoned() : config.getRemoveAbandoned();
if (abandoned != null) {
dataSource.setRemoveAbandoned(abandoned);
}
Boolean logAbandoned = config.getLogAbandoned() == null ? druidConfig.getLogAbandoned() : config.getLogAbandoned();
if (logAbandoned != null) {
dataSource.setLogAbandoned(logAbandoned);
}
Integer queryTimeOut = config.getQueryTimeout() == null ? druidConfig.getQueryTimeout() : config.getQueryTimeout();
if (queryTimeOut != null) {
dataSource.setQueryTimeout(queryTimeOut);
}
Integer transactionQueryTimeout =
config.getTransactionQueryTimeout() == null ? druidConfig.getTransactionQueryTimeout() : config.getTransactionQueryTimeout();
if (transactionQueryTimeout != null) {
dataSource.setTransactionQueryTimeout(transactionQueryTimeout);
}
try {
dataSource.init();
} catch (SQLException e) {
throw new ErrorCreateDataSourceException("druid create error", e);
}
return dataSource;
}
}
其实,这里面重点方法也是createDataSource(), 如果看不太明白是怎么创建的,一点关系都没有,就知道通过这种方式创建了数据源就ok了。
4. HikariDataSourceCreator: 创建Hikari类型的数据源
@Data
@AllArgsConstructor
public class HikariDataSourceCreator {
private HikariCpConfig hikariCpConfig;
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
HikariConfig config = dataSourceProperty.getHikari().toHikariConfig(hikariCpConfig);
config.setUsername(dataSourceProperty.getUsername());
config.setPassword(dataSourceProperty.getPassword());
config.setJdbcUrl(dataSourceProperty.getUrl());
config.setDriverClassName(dataSourceProperty.getDriverClassName());
config.setPoolName(dataSourceProperty.getPoolName());
return new HikariDataSource(config);
}
}
这里就不多说了, 就是创建hikari类型的数据源。
5.DataSourceCreator数据源创建器
@Slf4j
@Setter
public class DataSourceCreator {
/**
* 是否存在druid
*/
private static Boolean druidExists = false;
/**
* 是否存在hikari
*/
private static Boolean hikariExists = false;
static {
try {
Class.forName(DRUID_DATASOURCE);
druidExists = true;
log.debug("dynamic-datasource detect druid,Please Notice \n " +
"https://github.com/baomidou/dynamic-datasource-spring-boot-starter/wiki/Integration-With-Druid");
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName(HIKARI_DATASOURCE);
hikariExists = true;
} catch (ClassNotFoundException ignored) {
}
}
private BasicDataSourceCreator basicDataSourceCreator;
private JndiDataSourceCreator jndiDataSourceCreator;
private HikariDataSourceCreator hikariDataSourceCreator;
private DruidDataSourceCreator druidDataSourceCreator;
private String globalPublicKey;
/**
* 创建数据源
*
* @param dataSourceProperty 数据源信息
* @return 数据源
*/
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
DataSource dataSource;
//如果是jndi数据源
String jndiName = dataSourceProperty.getJndiName();
if (jndiName != null && !jndiName.isEmpty()) {
dataSource = createJNDIDataSource(jndiName);
} else {
Class<? extends DataSource> type = dataSourceProperty.getType();
if (type == null) {
if (druidExists) {
dataSource = createDruidDataSource(dataSourceProperty);
} else if (hikariExists) {
dataSource = createHikariDataSource(dataSourceProperty);
} else {
dataSource = createBasicDataSource(dataSourceProperty);
}
} else if (DRUID_DATASOURCE.equals(type.getName())) {
dataSource = createDruidDataSource(dataSourceProperty);
} else if (HIKARI_DATASOURCE.equals(type.getName())) {
dataSource = createHikariDataSource(dataSourceProperty);
} else {
dataSource = createBasicDataSource(dataSourceProperty);
}
}
this.runScrip(dataSourceProperty, dataSource);
return dataSource;
}
private void runScrip(DataSourceProperty dataSourceProperty, DataSource dataSource) {
String schema = dataSourceProperty.getSchema();
String data = dataSourceProperty.getData();
if (StringUtils.hasText(schema) || StringUtils.hasText(data)) {
ScriptRunner scriptRunner = new ScriptRunner(dataSourceProperty.isContinueOnError(), dataSourceProperty.getSeparator());
if (StringUtils.hasText(schema)) {
scriptRunner.runScript(dataSource, schema);
}
if (StringUtils.hasText(data)) {
scriptRunner.runScript(dataSource, data);
}
}
}
/**
* 创建基础数据源
*
* @param dataSourceProperty 数据源参数
* @return 数据源
*/
public DataSource createBasicDataSource(DataSourceProperty dataSourceProperty) {
if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
dataSourceProperty.setPublicKey(globalPublicKey);
}
return basicDataSourceCreator.createDataSource(dataSourceProperty);
}
/**
* 创建JNDI数据源
*
* @param jndiName jndi数据源名称
* @return 数据源
*/
public DataSource createJNDIDataSource(String jndiName) {
return jndiDataSourceCreator.createDataSource(jndiName);
}
/**
* 创建Druid数据源
*
* @param dataSourceProperty 数据源参数
* @return 数据源
*/
public DataSource createDruidDataSource(DataSourceProperty dataSourceProperty) {
if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
dataSourceProperty.setPublicKey(globalPublicKey);
}
return druidDataSourceCreator.createDataSource(dataSourceProperty);
}
/**
* 创建Hikari数据源
*
* @param dataSourceProperty 数据源参数
* @return 数据源
* @author 离世庭院 小锅盖
*/
public DataSource createHikariDataSource(DataSourceProperty dataSourceProperty) {
if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
dataSourceProperty.setPublicKey(globalPublicKey);
}
return hikariDataSourceCreator.createDataSource(dataSourceProperty);
}
}
其实仔细看,就是整合了前面四种类型的数据源,通过简单工厂模式创建实体类。这里是真正的去调用数据源,开始创建的地方。
通过拆解来看,发现,也并不太难。继续来看下一个模块。