import org.springframework.beans.factory.InitializingBean;import org.springframework.jdbc.datasource.AbstractDataSource;import org.springframework.jdbc.datasource.lookup.DataSourceLookup;import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;import org.springframework.lang.Nullable;import org.springframework.util.Assert;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;import java.util.HashMap;import java.util.Map;/**
* Abstract {@link 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()
*///AbstractRoutingDataSource 实现了 InitializingBean 那么spring在初始化该bean时,// 会调用AbstractRoutingDataSource 重写的afterPropertiesSet方法 (重要 把外部动态设置数据源 刷新到内部容器)publicabstractclassAbstractRoutingDataSourceextendsAbstractDataSourceimplementsInitializingBean{// 外部设置的多数据源容器@Nullableprivate Map<Object, Object> targetDataSources;//外部设置默认的数据源@Nullableprivate Object defaultTargetDataSource;privateboolean lenientFallback =true;private DataSourceLookup dataSourceLookup =newJndiDataSourceLookup();// 内部的多数据源容器@Nullableprivate Map<Object, DataSource> resolvedDataSources;// 内部的默认数据源@Nullableprivate DataSource resolvedDefaultDataSource;/**
* Specify the map of target DataSources, with the lookup key as key.
* The mapped value can either be a corresponding {@link 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()}.
*/publicvoidsetTargetDataSources(Map<Object, Object> targetDataSources){this.targetDataSources = targetDataSources;}/**
* Specify the default target DataSource, if any.
* <p>The mapped value can either be a corresponding {@link 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.
*/publicvoidsetDefaultTargetDataSource(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()
*/publicvoidsetLenientFallback(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.
*/publicvoidsetDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup){this.dataSourceLookup =(dataSourceLookup != null ? dataSourceLookup :newJndiDataSourceLookup());}/**
* @desc 初始化bean的时候,调用afterPropertiesSet
* @param
* @return
*/@OverridepublicvoidafterPropertiesSet(){if(this.targetDataSources == null){thrownewIllegalArgumentException("Property 'targetDataSources' is required");}this.resolvedDataSources =newHashMap<>(this.targetDataSources.size());this.targetDataSources.forEach((key, value)->{
Object lookupKey =resolveSpecifiedLookupKey(key);
DataSource dataSource =resolveSpecifiedDataSource(value);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 instanceofDataSource){return(DataSource) dataSource;}elseif(dataSource instanceofString){returnthis.dataSourceLookup.getDataSource((String) dataSource);}else{thrownewIllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: "+ dataSource);}}@Overridepublic Connection getConnection()throws SQLException {returndetermineTargetDataSource().getConnection();}@Overridepublic Connection getConnection(String username, String password)throws SQLException {returndetermineTargetDataSource().getConnection(username, password);}@Override@SuppressWarnings("unchecked")public<T> T unwrap(Class<T> iface)throws SQLException {if(iface.isInstance(this)){return(T)this;}returndetermineTargetDataSource().unwrap(iface);}@OverridepublicbooleanisWrapperFor(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();
DataSource dataSource =this.resolvedDataSources.get(lookupKey);if(dataSource == null &&(this.lenientFallback || lookupKey == null)){
dataSource =this.resolvedDefaultDataSource;}if(dataSource == null){thrownewIllegalStateException("Cannot determine target DataSource for lookup key ["+ lookupKey +"]");}return dataSource;}/**
* 需要子类自己实现(必须),决定数据源的key是哪一个
* 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.
*/@Nullableprotectedabstract Object determineCurrentLookupKey();/**
* @desc 添加数据源的方法
* @param key
* @param dataSource
* @return
*/publicvoidaddDataSource(Object key, Object dataSource){this.targetDataSources.put(key, dataSource);// 添加之后重新刷新 到内部容器afterPropertiesSet();}/**
* @desc 移除数据源方法
* @param key
* @return
* @author xuliu
* @date 2020/07/05 20:31
*/publicvoidremoveDataSource(Object key){this.targetDataSources.remove(key);// 移除之后重新刷新 到内部容器afterPropertiesSet();}/**
* @desc 判断数据源是否存在
* @param key
* @return
*/publicbooleanisDatasourceExist(String key){returnthis.targetDataSources.get(key)!=null?true:false;}}