AOP获取方法注解实现动态切换数据源

AOP获取方法注解实现动态切换数据源(以下方式尚未经过测试,仅提供思路)

------

自定义一个用于切换数据源的注解:

package com.xxx.annotation;

import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DataSource {
    String value() default "";

}

定义一个工具类,方便设置、删除、获取从数据源注解中得到的不同数据源类型:

package com.xxx.utils.dataSource;

public class DataSourceHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    /**
     * @Description: 设置数据源类型
     * @param dataSourceType 数据库类型
     * @return void
     * @throws
     */
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    /**
     * @Description: 获取数据源类型
     * @param
     * @return String
     * @throws
     */
    public static String getDataSourceType() {
        return contextHolder.get();
    }

    /**
     * @Description: 清除数据源类型
     * @param
     * @return void
     * @throws
     */
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

--------

配置动态数据源:

spring配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
    <!-- 引入配置文件 -->
    <bean id="propertyConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:jdbc.properties" />
    </bean>

    <bean id="dataSource_R" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name ="jndiName">
        <!-- 下面第一行为测试环境配置,第二行为生产环境配置,运行时保留一个 -->
            <value>java:comp/env/jdbc/JTORDER</value>
            <!-- <value>jdbc/JTORDER</value> -->
        </property>
    </bean>

    <bean id="dataSource_RW" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name ="jndiName">
         <!-- 下面第一行为测试环境配置,第二行为生产环境配置,运行时保留一个 -->
            <value>java:comp/env/jdbc/JTORDER</value>
           <!--  <value>jdbc/JTORDER</value> -->
        </property>
    </bean>

    <!-- 动态数据源 -->
    <bean id="dataSource" class="com.xxx.utils.dataSource.RoutingDataSource">
        <!-- 为targetDataSources注入两个数据源 -->
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="R" value-ref="dataSource_R"/>
                <entry key="RW" value-ref="dataSource_RW"/>
            </map>
        </property>
        <!-- 为指定数据源RoutingDataSource注入默认的数据源-->
        <property name="defaultTargetDataSource" ref="dataSource_R"/>
    </bean>

        <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 自动扫描mapping.xml文件 -->
        <!--<property name="mapperLocations" value="classpath:mapping/*.xml"></property>-->
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="mapperLocations" value="classpath*:module.*.mapper/*.xml"></property>
    </bean>

    <!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- xml接口映射文件 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.xxx.mapper"></property>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

    <!-- 配置事物切点 -->
    <aop:config>
        <aop:pointcut id="transactionPointcut" expression="execution(* com.xxx.service.impl.*.*(..) )"/>
        <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" />
    </aop:config>

    <!-- 注解方式配置事务-->
    <!-- <tx:annotation-driven transaction-manager="transactionManager" /> -->

    <!-- 拦截器方式配置事务-->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <!-- 自动扫描定时任务 -->
    <task:annotation-driven/>
    <!-- spring自动创建代理,植入切面,proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy
    poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。 -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

</beans>



<!--     
    本地测试需要在Tomcat的context.xml的 <Context> 标签中加入如下配置:
    <Resource
    name="jdbc/myName1"
    scope="Shareable"
    type="javax.sql.DataSource"
    factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
    url="jdbc:oracle:thin:@10.10.10.10:1521:xxdb1"
    driverClassName ="oracle.jdbc.driver.OracleDriver"
    username="usesr1"
    password="pwd1"
    />

    <Resource
    name="jdbc/myName2"
    scope="Shareable"
    type="javax.sql.DataSource"
    factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
    url="jdbc:oracle:thin:@10.10.10.10:1521:xxdb2"
    driverClassName ="oracle.jdbc.driver.OracleDriver"
    username="user2"
    password="pwd2"
    /> -->

动态数据源类【其中RoutingDataSource 和上面xml中的RoutingDataSource 对应】:

package com.xxx.utils.dataSource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


public class RoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSourceType();
    }
}

它的作用是通过获取我们自定义的数据源类型持有工具类DataSourceHolder中存储的数据源类型来动态试用真正的数据源

--------

通过AOP获取方法上的注解实现动态切换数据源

(其中@Order(1)作用:

Spring中的事务是通过aop来实现的,当我们自己写aop拦截的时候,会遇到跟spring的事务aop执行的先后顺序问题,比如说动态切换数据源的问题,如果事务在前,数据源切换在后,会导致数据源切换失效,所以就用到了Order(排序)这个关键字.)

import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Repository;


@Order(1) @Aspect @Repository public class DataSourceAspect { @Pointcut("execution(* com.xxx.service.impl.*.*(..))") private void anyMethod() {} @AfterReturning(value = "anyMethod()", returning = "result") public void afterReturning(JoinPoint joinPoint,Object result){ DataSourceHolder.clearDataSourceType(); } @Before(value="anyMethod()") public void before(JoinPoint joinPoint){
     //通过切点对象获取当前切点所在的方法对象 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); //如果方法体上使用了DataSource注解 if (method.isAnnotationPresent(DataSource.class)) { //获取该方法上的注解名 DataSource datasource = method.getAnnotation(DataSource.class); //将方法体上的注解的值赋予给DataSourceHolder数据源持有类 DataSourceHolder.setDataSourceType(datasource.value()); } } }

试用时只需要在被切方法上加上自定义注解,并且在里面配上不同的目标数据源即可。

原文地址:https://www.cnblogs.com/libin6505/p/11227267.html