-
Notifications
You must be signed in to change notification settings - Fork 1
原理分析
liuqingyao edited this page Mar 22, 2017
·
9 revisions
Spring JDBC的datasource中有个抽象类:AbstractRoutingDataSource(抽象类)具备动态切换DataSource(接口)的特性
AbstractRoutingDataSource extends AbstractDataSource implements DataSource
AbstractRoutingDataSource的属性
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private Map<Object, DataSource> resolvedDataSources;//这个属性很重要,说明具备多数据源能力
private DataSource resolvedDefaultDataSource;
AbstractRoutingDataSource的关键方法
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
//下两行代码实现了数据源的切换,而用哪个数据源最终由determineCurrentLookupKey决定(返回了一个key)
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if(dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if(dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
//留给我们做扩展用的
protected abstract Object determineCurrentLookupKey();
MultiDataSource的实现细节
package com.company.component.datasource.multi_datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.HashMap;
import java.util.Map;
/**
* User: yaoliuqing
* Time: 14-9-26 下午4:46
*/
public class MultipleDataSource extends AbstractRoutingDataSource {
//保证线程安全
private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<String>();
private static final Map<String, String> packageDataSource = new HashMap<String, String>();
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}
public static void usePackageDataSource(String pkgName) {
dataSourceKey.set(packageDataSource.get(pkgName));
}
//实现AbstractRoutingDataSource的determineTargetDataSource
protected Object determineCurrentLookupKey() {
String dsName = dataSourceKey.get();
//阅后即焚
dataSourceKey.remove();
return dsName;
}
public Map<String, String> getPackageDataSource() {
return MultipleDataSource.packageDataSource;
}
public void setPackageDataSource(Map<String, String> packageDataSource) {
MultipleDataSource.packageDataSource.putAll(packageDataSource);
}
}
将原来我们在配置中使用的数据源替换成我们自定义的数据源
有了自定义数据源后,我们也要更方便地指定编码中具体某个方法使用哪个数据源 >(其实不自定义数据源也是可以实现多数据源在同一个项目中使用的,非常麻烦和不灵活以至于我已经记不得怎么用了^_^) 如果通过注解就可以指定某个方法用哪个数据源,岂不方便?!特别是如果你工程正处于数据库拆分,数据库不停加从库的场景下。 于是定义了一个注解package com.company.component.datasource.multi_datasource.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* User: yaoliuqing
* Time: 14-9-25 下午5:27
* 指定操作的数据库
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
/**
* value dataSource的名称
*/
String value();
}
AOP的东西就直接上代码吧
package com.company.component.datasource.multi_datasource.aspect;
import java.lang.reflect.Method;
import com.company.component.datasource.multi_datasource.MultipleDataSource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import com.company.component.datasource.multi_datasource.annotation.DataSource;
/**
* User: yaoliuqing
* Time: 14-9-25 下午4:03
*/
public class MultipleDataSourceAspect {
private static final Logger LOG = LoggerFactory.getLogger(MultipleDataSourceAspect.class);
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
if (LOG.isDebugEnabled()) {
LOG.debug("MultipleDataSourceAspectAdvice invoked!");
}
Signature signature = jp.getSignature();
String dataSourceKey = getDataSourceKey(signature);
if (StringUtils.hasText(dataSourceKey)) {
//指定自定义数据源使用具体哪个数据源
MultipleDataSource.setDataSourceKey(dataSourceKey);
}
Object jpVal = jp.proceed();
return jpVal;
}
private String getDataSourceKey(Signature signature) {
if (signature == null) return null;
if (signature instanceof MethodSignature) {
//检测方法级注解
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(DataSource.class)) {
return dsSettingInMethod(method);
}
//类级注解
Class declaringClazz = method.getDeclaringClass();
if (declaringClazz.isAnnotationPresent(DataSource.class)) {
try {
return dsSettingInConstructor(declaringClazz);
} catch (Exception e) {
LOG.error("获取构造方法的DataSource注解失败", e);
}
}
//包级注解,为了配置方便,包注解和类以及方法注解方式不同
Package pkg = declaringClazz.getPackage();
dsSettingInPackage(pkg);
}
return null;
}
private String dsSettingInMethod(Method method) {
DataSource dataSource = method.getAnnotation(DataSource.class);
return dataSource.value();
}
private String dsSettingInConstructor(Class clazz) {
DataSource dataSource = (DataSource) clazz.getAnnotation(DataSource.class);
return dataSource.value();
}
private void dsSettingInPackage(Package pkg) {
if (LOG.isDebugEnabled()) {
LOG.debug(pkg.getName());
}
MultipleDataSource.usePackageDataSource(pkg.getName());
}
}
因为我们此时DAO层绑定的数据源是自定义的,所以可以通过指定dataSourceKey来指定dataSource。 在切面实现了对包级,类级,方法级的配置检查(包级比较鸡肋实践中基本不用)