主要利用AOP+ThreadLocal+自定义注释实现注释切换
pom.xml
<!-- springboot-aop包,AOP切面注解,Aspectd等相关注解 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
这里主要看AbstractRoutingDataSource.java源码:
标黄部分:根据Key获取对应数据源。
这里新建类继承AbstractRoutingDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDB(); } }
这里新建类,ThreadLocal用于存放数据源
public class DataSourceContextHolder { /** * 默认数据源 */ public static final String DEFAULT_DS = "chenDataSource"; private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); // 设置数据源名 public static void setDB(String dbType) { System.out.println("切换到{" + dbType + "}数据源"); contextHolder.set(dbType); } // 获取数据源名 public static String getDB() { return (contextHolder.get()); } // 清除数据源名 public static void clearDB() { contextHolder.remove(); } }
说明:
用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
工作原理:
1、Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。
2、当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value。get值时则类似。
3、ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。
4、由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的。
import com.paic.phssp.springtest.dataSource.DynamicDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration public class DataSourceConfig { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Bean(name = "chenDataSource") @Qualifier("chenDataSource") @ConfigurationProperties(prefix = "spring.datasource.chen") public DataSource chenDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "quartzds") @Qualifier("quartzds") @ConfigurationProperties(prefix = "spring.datasource.quartzds") public DataSource quartzDSDataSource() { return DataSourceBuilder.create().build(); } /** * 动态数据源: 通过AOP在不同数据源之间动态切换 * * @return */ @Primary @Bean(name = "dynamicDataSource") public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 默认数据源 dynamicDataSource.setDefaultTargetDataSource(chenDataSource()); // 配置多数据源 Map<Object, Object> dsMap = new HashMap(); dsMap.put("chenDataSource", chenDataSource()); dsMap.put("quartzds", quartzDSDataSource()); dynamicDataSource.setTargetDataSources(dsMap); return dynamicDataSource; } /** * 配置@Transactional注解事物 * @return */ @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dynamicDataSource()); } }
自定义注释
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface IDataSource { String value() default "chenDataSource"; }
AOP从注释中获取数据源key,然后通过AbstractRoutingDataSource.determineTargetDataSource()获取数据源
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.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * 自定义注解 + AOP的方式实现数据源动态切换。 * Created by pure on 2018-05-06. */ @Component public class DynamicDataSourceAspect { @Before("@annotation(IDataSource)") public void beforeSwitchDS(JoinPoint point) { //获得当前访问的class Class<?> className = point.getTarget().getClass(); //获得访问的方法名 String methodName = point.getSignature().getName(); //得到方法的参数的类型 Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes(); String dataSource = DataSourceContextHolder.DEFAULT_DS; try { // 得到访问的方法对象 Method method = className.getMethod(methodName, argClass); // 判断是否存在@IDataSource注解 if (method.isAnnotationPresent(IDataSource.class)) { IDataSource annotation = method.getAnnotation(IDataSource.class); // 取出注解中的数据源名 dataSource = annotation.value(); } } catch (Exception e) { e.printStackTrace(); } // 切换数据源 DataSourceContextHolder.setDB(dataSource); } @After("@annotation(IDataSource)") public void afterSwitchDS(JoinPoint point) { //清楚数据源 DataSourceContextHolder.clearDB(); } }
应用:
import com.paic.phssp.springtest.dao.UserMapper; import com.paic.phssp.springtest.dataSource.IDataSource; import com.paic.phssp.springtest.dto.User; import com.paic.phssp.springtest.service.IUserService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service public class IUserServiceImpl implements IUserService { private final Logger log = LoggerFactory.getLogger(getClass()); @Autowired private UserMapper userMapper; @Override @IDataSource(value="chenDataSource") public List<User> findAll() { return userMapper.getAll(); } }
运行结果:
总结:
上面叨叨了那么多,其实重点:AOP+注解 结构。