基于AOP的动态数据源实现
当页面请求处理到service层时,触发调用方法中的拦截切面类DataSourceInterceptor,将当前线程中的数据源引用存入HandlerDataSource的handlerThredLocal集合中,然后进入service层中的事务拦截,开启事务管理,DataSourceTransactionManager中的doBegin方法获取数据库连接时,会调用AbstractRoutingDataSource类中的getConnection方法,而该方法中的determineTargetDataSource方法会调用determineCurrentLookupKey方法,但是AbstractRoutingDataSource类中的该方法接口方法,需要我们自己实现它,因此才有了DynamicDataSource类,将DataSourceInterceptor拦截类中存入handlerThredLocal对象中的数据源引用key值取出来,这样determineTargetDataSource方法的DataSource dataSource = this.resolvedDataSources.get(lookupKey);就会获取到具体的数据源连接;
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
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 + "]");
}
return dataSource;
}
determineTargetDataSource().getConnection();
例子;
@Service("userService")
public class UserServiceImpl implements UserService{
@Resource
private UserDao userDao;
@Override
public int save(User user) {
// TODO Auto-generated method stub
return userDao.save(user);
}
@Override
public int deleteById(int id) {
// TODO Auto-generated method stub
return userDao.deleteById(id);
}
@Override
public int update(User user) {
// TODO Auto-generated method stub
return userDao.update(user);
}
}
//利用ThreadLocal定义线程局部变量,根据线程获取当前线程的数据源;
public class HandlerDataSource {
private static ThreadLocal<String> handlerThredLocal = new ThreadLocal<String>();
/**
* @desction: 提供给AOP去设置当前的线程的数据源的信息
* @param: [datasource]
*/
public static void putDataSource(String datasource) {
handlerThredLocal.set(datasource);
}
/**
* @desction: 提供给AbstractRoutingDataSource的实现类,通过key选择数据源
* @date: 2017/8/21
*/
public static String getDataSource() {
return handlerThredLocal.get();
}
/**
* @desction: 使用默认的数据源
*/
public static void clear() {
handlerThredLocal.remove();
}
}
//利用spring 中AbstractRoutingDataSource 提供调用继承者覆盖的空方法determineCurrentLookupKey,将当前的线程所使用的数据源引用key值传入获取数据源之前;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// TODO Auto-generated method stub
return HandlerDataSource.getDataSource();
}
}
//AOP拦截类,指明调用某个类中某个方法,使用具体的数据源
@Aspect
@Component
@Order(-999)//顺序排在事务AOP之前,这样在开启事务之前,将决定出使用哪个数据源
public class DataSourceInterceptor {
@Pointcut("execution(* *.service.impl.*.save(..))")
public void save() {
};
@Before("save()")
public void beforeFirst1(JoinPoint jp) {
System.out.println("save方法调用数据源dataSource1");
HandlerDataSource.putDataSource("dataSource1");
}
@Pointcut("execution(* *.service.impl.*.update(..))")
public void update() {
};
@Before("update()")
public void beforeFirst2(JoinPoint jp) {
System.out.println("update方法调用数据源dataSource2");
HandlerDataSource.putDataSource("dataSource2");
}
}
<!--applicationContent-dao.xml配置-->
<!-- 数据库连接池 -->
<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="maxActive" value="10" />
<property name="minIdle" value="5" />
</bean>
<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="maxActive" value="10" />
<property name="minIdle" value="5" />
</bean>
<bean id="dataSource" class="dataSource.handler.DynamicDataSource">
<!-- 设置默认的目标数据源 -->
<property name="defaultTargetDataSource" ref="dataSource1" />
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dataSource1" value-ref="dataSource1" />
<entry key="dataSource2" value-ref="dataSource2" />
</map>
</property>
</bean>
<!-- 配置sqlsessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml"></property>
<property name="mapperLocations" value="classpath:mapping/*.map.xml"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- AOP自动代理功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
实现源码分析:
/**
* Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
* calls to one of various target DataSources based on a lookup key. The latter is usually
* (but not necessarily) determined through some thread-bound transaction context.
*
* @author Juergen Hoeller
* @since 2.0.1
* @see #setTargetDataSources
* @see #setDefaultTargetDataSource
* @see #determineCurrentLookupKey()
*/
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
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;
/**
* Specify the map of target DataSources, with the lookup key as key.
* The mapped value can either be a corresponding {@link javax.sql.DataSource}
* instance or a data source name String (to be resolved via a
* {@link #setDataSourceLookup DataSourceLookup}).
* <p>The key can be of arbitrary type; this class implements the
* generic lookup process only. The concrete key representation will
* be handled by {@link #resolveSpecifiedLookupKey(Object)} and
* {@link #determineCurrentLookupKey()}.
*/
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
/**
* Specify the default target DataSource, if any.
* <p>The mapped value can either be a corresponding {@link javax.sql.DataSource}
* instance or a data source name String (to be resolved via a
* {@link #setDataSourceLookup DataSourceLookup}).
* <p>This DataSource will be used as target if none of the keyed
* {@link #setTargetDataSources targetDataSources} match the
* {@link #determineCurrentLookupKey()} current lookup key.
*/
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}
/**
* Specify whether to apply a lenient fallback to the default DataSource
* if no specific DataSource could be found for the current lookup key.
* <p>Default is "true", accepting lookup keys without a corresponding entry
* in the target DataSource map - simply falling back to the default DataSource
* in that case.
* <p>Switch this flag to "false" if you would prefer the fallback to only apply
* if the lookup key was {@code null}. Lookup keys without a DataSource
* entry will then lead to an IllegalStateException.
* @see #setTargetDataSources
* @see #setDefaultTargetDataSource
* @see #determineCurrentLookupKey()
*/
public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
}
/**
* Set the DataSourceLookup implementation to use for resolving data source
* name Strings in the {@link #setTargetDataSources targetDataSources} map.
* <p>Default is a {@link JndiDataSourceLookup}, allowing the JNDI names
* of application server DataSources to be specified directly.
*/
public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
}
@Override
public void afterPropertiesSet() { //spring启动时,实例化该类对象后调用该方法,将配置文件中数据源属性注入
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
/**
* Resolve the given lookup key object, as specified in the
* {@link #setTargetDataSources targetDataSources} map, into
* the actual lookup key to be used for matching with the
* {@link #determineCurrentLookupKey() current lookup key}.
* <p>The default implementation simply returns the given key as-is.
* @param lookupKey the lookup key object as specified by the user
* @return the lookup key as needed for matching
*/
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}
/**
* Resolve the specified data source object into a DataSource instance.
* <p>The default implementation handles DataSource instances and data source
* names (to be resolved via a {@link #setDataSourceLookup DataSourceLookup}).
* @param dataSource the data source value object as specified in the
* {@link #setTargetDataSources targetDataSources} map
* @return the resolved DataSource (never {@code null})
* @throws IllegalArgumentException in case of an unsupported value type
*/
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}
else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String) dataSource);
}
else {
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
}
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection(); //获取数据源链接
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
return determineTargetDataSource().unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
}
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey(); // 实际中调用的DynamicDataSource中实现的方法
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 + "]");
}
return dataSource;
}
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
// DynamicDataSource类实现该方法,将在aop时将当前线程所使用的数据源key值放入
protected abstract Object determineCurrentLookupKey();
}
2) 注解类型实现:
//aop
@Aspect
@Component
@Order(-999)
public class HandlerDataSourceAop {
//@within在类上设置
//@annotation在方法上进行设置
@Pointcut("@within(dataSource.handler.DynamicSwitchDataSource)||@annotation(dataSource.handler.DynamicSwitchDataSource)")
public void pointcut() {}
@Before("pointcut()") //前置通知
public void testBefore(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
DynamicSwitchDataSource dataSourceAnnotation = className.getAnnotation(DynamicSwitchDataSource.class);
if (dataSourceAnnotation != null ) {
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DATA_SOURCE_A;
try {
Method method = className.getMethod(methodName, argClass);
if (method.isAnnotationPresent(DynamicSwitchDataSource.class)) {
DynamicSwitchDataSource annotation = method.getAnnotation(DynamicSwitchDataSource.class);
dataSource = annotation.dataSource();
System.out.println("DataSource Aop ====> "+dataSource);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
DataSourceContextHolder.setDbType(dataSource);
}
}
@After("pointcut()") //后置通知
public void testAfter(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
DynamicSwitchDataSource dataSourceAnnotation = className.getAnnotation(DynamicSwitchDataSource.class);
if (dataSourceAnnotation != null ) {
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DATA_SOURCE_A;
try {
Method method = className.getMethod(methodName, argClass);
if (method.isAnnotationPresent(DynamicSwitchDataSource.class)) {
DynamicSwitchDataSource annotation = method.getAnnotation(DynamicSwitchDataSource.class);
dataSource = annotation.dataSource();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(dataSource != null && !DataSourceContextHolder.DATA_SOURCE_A.equals(dataSource)) DataSourceContextHolder.clearDbType();
}
}
}
自定义注解;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicSwitchDataSource {
String dataSource() default "";
}
@Service("userService")
@DynamicSwitchDataSource
public class UserServiceImpl implements UserService{
@Resource
private UserDao userDao;
@DynamicSwitchDataSource(dataSource = "datasource1")
public int save(User user) {
// TODO Auto-generated method stub
return userDao.save(user);
}
@Override
public int deleteById(int id) {
// TODO Auto-generated method stub
return userDao.deleteById(id);
}
@DynamicSwitchDataSource(dataSource = "datasource2")
public int update(User user) {
// TODO Auto-generated method stub
return userDao.update(user);
}
}

本文介绍了如何基于AOP的动态数据源实现,当请求到达service层时,通过拦截切面类DataSourceInterceptor设置数据源。在事务管理过程中,DataSourceTransactionManager使用AbstractRoutingDataSource获取连接,其中的关键在于自定义的DynamicDataSource类,它根据拦截类中存储的键值确定具体的数据源,从而实现动态数据源的切换。

1054

被折叠的 条评论
为什么被折叠?



