基于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); } }