主要利用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+注解 結構。