用Spring aop配置动态数据源(annotation配置,annotation动态参数)
方法很简单,直接贴代码了。
首先继承spring的一个类AbstractRoutingDataSource
import java.util.Map;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
/**
*类名:DynamicDataSource.java
*功能:动态数据源类
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/*
* 该方法必须要重写 方法是为了根据数据库标示符取得当前的数据库
*/
protected Object determineCurrentLookupKey() {
String dataSourceType= DataSourceContextHolder.getDataSourceType();
return dataSourceType;
}
public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
super.setDataSourceLookup(dataSourceLookup);
}
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
}
public void setTargetDataSources(Map targetDataSources) {
super.setTargetDataSources(targetDataSources);
}
}
determineCurrentLookupKey必须重写用来判断是需要那个数据源。
public class DataSourceContextHolder {
private static final ThreadLocal contextHolder=new ThreadLocal();
public static void setDataSourceType(String dataSourceType){
contextHolder.set(dataSourceType);
}
public static String getDataSourceType(){
return (String) contextHolder.get();
}
public static void clearDataSourceType(){
contextHolder.remove();
}
}
DataSourceContextHolder类用来在当前线程中设置和保存数据源关键字。setDataSourceType的参数我设置为了String,如果需要可以自己定义枚举类来设值
创建一个annotation,用来让aop切入,并配置所用数据源信息
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicSwitchDataSource {
String dataSource() default "";
}
创建aop类进行数据源信息切换
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.doframework.core.extend.annotation.DynamicSwitchDataSource;
import com.doframework.core.extend.datasource.DataSourceContextHolder;
/**
* descrption: 使用AOP拦截特定的注解去动态的切换数据源
* authohr: 武润泽
* date: 2019.07.18
*/
@Aspect
@Component
@Order(0)
public class DynamicDataSourceAop {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAop.class);
//@within在类上设置
//@annotation在方法上进行设置
//within拦截类上的注解,annotation拦截方法上的注解
@Pointcut("@within(com.doframework.core.extend.annotation.DynamicSwitchDataSource)||@annotation(com.doframework.core.extend.annotation.DynamicSwitchDataSource)")
public void pointcut() {}
@Before("pointcut()")
public void doBefore(JoinPoint joinPoint)
{ Method realMethod=null;
try {
//获取方法上的注解必须用getDeclaredMethod()获取,用((MethodSignature)joinPoint.getSignature()).getMethod(),获取到的为代理对象不带注解信息
realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(((MethodSignature)joinPoint.getSignature()).getMethod().getName(),((MethodSignature)joinPoint.getSignature()).getMethod().getParameterTypes());
if(realMethod==null) {
realMethod= joinPoint.getTarget().getClass().getSuperclass().getDeclaredMethod(((MethodSignature)joinPoint.getSignature()).getMethod().getName(),((MethodSignature)joinPoint.getSignature()).getMethod().getParameterTypes());
}
} catch (NoSuchMethodException e) {
try {
realMethod= joinPoint.getTarget().getClass().getSuperclass().getDeclaredMethod(((MethodSignature)joinPoint.getSignature()).getMethod().getName(),((MethodSignature)joinPoint.getSignature()).getMethod().getParameterTypes());
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
} catch (SecurityException e1) {
e1.printStackTrace();
}
} catch (SecurityException e) {
e.printStackTrace();
}
DynamicSwitchDataSource annotationClass=realMethod.getAnnotation(DynamicSwitchDataSource.class);//获取方法上的注解
if(annotationClass == null){
annotationClass = joinPoint.getTarget().getClass().getAnnotation(DynamicSwitchDataSource.class);//获取类上面的注解
if(annotationClass == null) return;
}
//获取注解上的数据源的值的信息
String dataSourceKey = annotationClass.dataSource();
if(dataSourceKey !=null){
//给当前的执行SQL的操作设置特殊的数据源的信息
DataSourceContextHolder.setDataSourceType(dataSourceKey);
log.info("AOP动态切换数据源,className"+(joinPoint.getTarget().getClass().getName())+"methodName"+(((MethodSignature)joinPoint.getSignature()).getMethod().getName())+";切换致dataSourceKey:"+(DataSourceContextHolder.getDataSourceType()==""?"默认数据源":DataSourceContextHolder.getDataSourceType()));
}
}
@After("pointcut()")
public void after(JoinPoint joinPoint) {
//清理掉当前设置的数据源,让默认的数据源不受影响
DataSourceContextHolder.clearDataSourceType();
log.info("AOP动态切换数据源,className"+(joinPoint.getTarget().getClass().getName())+"methodName"+(((MethodSignature)joinPoint.getSignature()).getMethod().getName())+";关闭切换还原默认dataSourceKey:"+(DataSourceContextHolder.getDataSourceType()==""?"默认数据源":DataSourceContextHolder.getDataSourceType()));
}
}
数据源配置按正常配置类,然后配置个动态数据源bean,类型为我们自己写的DynamicDataSource类型
<!-- 配置数据源1(主库数据源) -->
<bean name="dataSource_one" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url.dod}" />
<property name="username" value="${jdbc.username.dod}" />
<property name="password" value="${jdbc.password.dod}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="50" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="5" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="${validationQuery.sql}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 开启Druid的监控统计功能 -->
<property name="filters" value="stat" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="3600" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- Oracle连接是获取字段注释 -->
<property name="connectProperties">
<props>
<prop key="remarksReporting">true</prop>
</props>
</property>
</bean>
<!-- 配置数据源2(客户库) -->
<bean name="dataSource_two" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url.customer}" />
<property name="username" value="${jdbc.username.customer}" />
<property name="password" value="${jdbc.password.customer}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="50" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="5" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="${validationQuery.sql}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 开启Druid的监控统计功能 -->
<property name="filters" value="stat" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="3600" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- Oracle连接是获取字段注释 -->
<property name="connectProperties">
<props>
<prop key="remarksReporting">true</prop>
</props>
</property>
</bean>
<!-- 数据源集合 -->
<bean id="dataSource"
class="com.doframework.core.extend.datasource.DynamicDataSource">
<property name="targetDataSources">
<!-- key和数据库对应!!! -->
<map key-type="java.lang.String">
<entry key="Demo3" value-ref="dataSource_one" />
<entry key="Test" value-ref="dataSource_two"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource_dodDB" />
</bean>
<!-- 基于DynamicDataSource的key可进行指定数据库 -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="entityInterceptor" ref="hiberAspect" />
<property name="hibernateProperties">
<props>
<!--<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop> -->
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.temp.use_jdbc_metadata_defaults">false</prop>
</props>
</property>
<!-- 注解方式配置 -->
<property name="packagesToScan">
<list>
<value>com.doframework.web.system.pojo.*</value>
</list>
</property>
</bean>
如果需要动态修改注解的数据源参数可以参考↓
下文链接
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
/**
* Created by jiangzeyin on 2018/4/15.
*/
public class test {
@testA("a")
public static void main(String[] args) throws Exception {
Method method = test.class.getMethod("main", String[].class);
testA testA = method.getAnnotation(testA.class);
if (testA == null)
throw new RuntimeException("please add testA");
InvocationHandler invocationHandler = Proxy.getInvocationHandler(testA);
Field value = invocationHandler.getClass().getDeclaredField("memberValues");
value.setAccessible(true);
Map<String, Object> memberValues = (Map<String, Object>) value.get(invocationHandler);
String val = (String) memberValues.get("value");
System.out.println("改变前:" + val);
val = "b";
memberValues.put("value", val);
System.out.println("改变后:" + testA.value());
}
}