基于AOP的动态数据源实现

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

猜你喜欢

转载自blog.csdn.net/Java_Jsp_Ssh/article/details/81607192