AOPによるダイナミックデータソースの実現


AOPによるダイナミックデータソースの実現
ページ要求がservice層に処理されると、呼び出し方法におけるブロッキングタイプDataSourceIntercepterをトリガし、現在スレッド中のデータソースをHandlerDataSourceに格納されているhandle ThredLocalのセットに引用し、service層内のトランザクションブロックに入り、事務管理を開始し、DataSourceTransation ManagerのBedogin接続方法データベースを取得します。AbstractRoutingDataSource類のget Connection方法を呼び出しますが、この方法の中のdetermine Target DataSource方法はdetermine Curent Look up Key方法を呼び出すことができます。しかし、AbstractRoutingDataSource類のこの方法は、自分で実現する必要があります。DataSourceInterceptorブロック類にハンドル・ThredLocalのオブジェクトに格納されたデータソースをkey値を引用して取り出します。このようにdetermine Target Data Source方法のDataSource=this.reolvedDataSources.get(lookukey);具体的なデータソース接続を取得します。
public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }
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) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }
 
determine Target Data Source().get Connection();
例:
@Service("userService")
public class UserServiceImpl implements UserService{
    @Resource
    private UserDao userDao;
    @Override
    public int save(User user) {
        // TODO Auto-generated method stub
        return userDao.save(user);
    }
​
    @Override
    public int deleteById(int id) {
        // TODO Auto-generated method stub
        return userDao.deleteById(id);
    }
​
    @Override
    public int update(User user) {
        // TODO Auto-generated method stub
        return userDao.update(user);
    }
​
}
​​
//  ThreadLocal        ,              ;
public class HandlerDataSource {
     private static ThreadLocal handlerThredLocal = new ThreadLocal();
​
        /**
         * @desction:    AOP               
         * @param: [datasource]
         */
        public static void putDataSource(String datasource) {
            handlerThredLocal.set(datasource);
        }
​
        /**
         * @desction:    AbstractRoutingDataSource    ,  key     
         * @date: 2017/8/21
         */
        public static String getDataSource() {
            return handlerThredLocal.get();
        }
​
        /**
         * @desction:         
         */
        public static void clear() {
            handlerThredLocal.remove();
        }
}
​
//  spring  AbstractRoutingDataSource              determineCurrentLookupKey,               key          ;
public class DynamicDataSource extends AbstractRoutingDataSource {
​
    @Override
    protected Object determineCurrentLookupKey() {
        // TODO Auto-generated method stub
        return HandlerDataSource.getDataSource();
    }
​
}
//AOP   ,            ,        
@Aspect
@Component
@Order(-999)//      AOP  ,         ,           
public class DataSourceInterceptor {
    @Pointcut("execution(* *.service.impl.*.save(..))")
    public void save() {
    };
    @Before("save()")
    public void beforeFirst1(JoinPoint jp) {
        System.out.println("save       dataSource1");
        HandlerDataSource.putDataSource("dataSource1");
    }
    @Pointcut("execution(* *.service.impl.*.update(..))")
    public void update() {
    };
    @Before("update()")
    public void beforeFirst2(JoinPoint jp) {
        System.out.println("update       dataSource2");
        HandlerDataSource.putDataSource("dataSource2");
    }
}


    
        
        
        
        
        
        
    
    
        
        
        
        
        
        
    
        
         
               
        
            
                
                
            
        
    
    
    
        
        
        
    
    
    
    

 
    
 
 
ソース分析の実現:
/**
 * Abstract {@link javax.sql.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()
 */
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
​
    private Map targetDataSources;
​
    private Object defaultTargetDataSource;
​
    private boolean lenientFallback = true;
​
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
​
    private Map resolvedDataSources;
​
    private DataSource resolvedDefaultDataSource;
​
​
    /**
     * Specify the map of target DataSources, with the lookup key as key.
     * The mapped value can either be a corresponding {@link javax.sql.DataSource}
     * instance or a data source name String (to be resolved via a
     * {@link #setDataSourceLookup DataSourceLookup}).
     * 

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()}. */ public void setTargetDataSources(Map targetDataSources) { this.targetDataSources = targetDataSources; } ​ /** * Specify the default target DataSource, if any. *

The mapped value can either be a corresponding {@link javax.sql.DataSource} * instance or a data source name String (to be resolved via a * {@link #setDataSourceLookup DataSourceLookup}). *

This DataSource will be used as target if none of the keyed * {@link #setTargetDataSources targetDataSources} match the * {@link #determineCurrentLookupKey()} current lookup key. */ public void setDefaultTargetDataSource(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. *

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. *

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() */ public void setLenientFallback(boolean lenientFallback) { this.lenientFallback = lenientFallback; } ​ /** * Set the DataSourceLookup implementation to use for resolving data source * name Strings in the {@link #setTargetDataSources targetDataSources} map. *

Default is a {@link JndiDataSourceLookup}, allowing the JNDI names * of application server DataSources to be specified directly. */ public void setDataSourceLookup(DataSourceLookup dataSourceLookup) { this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup()); } ​ ​ @Override public void afterPropertiesSet() {  //spring , , if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap(this.targetDataSources.size()); for (Map.Entry entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); 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}. *

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. *

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 instanceof DataSource) { return (DataSource) dataSource; } else if (dataSource instanceof String) { return this.dataSourceLookup.getDataSource((String) dataSource); } else { throw new IllegalArgumentException( "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource); } } ​ ​ @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection();  // } ​ @Override public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } ​ @Override @SuppressWarnings("unchecked") public T unwrap(Class iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } return determineTargetDataSource().unwrap(iface); } ​ @Override public boolean isWrapperFor(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(); // DynamicDataSource   DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; } ​ /** * Determine the current lookup key. This will typically be * implemented to check a thread-bound transaction context. *

Allows for arbitrary keys. The returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolveSpecifiedLookupKey} method. */  // DynamicDataSource , aop key protected abstract Object determineCurrentLookupKey();   ​ } ​

2)注釈タイプの実現:
//aop
@Aspect
@Component
@Order(-999)
public class HandlerDataSourceAop {
    //@within     
    //@annotation        
    @Pointcut("@within(dataSource.handler.DynamicSwitchDataSource)||@annotation(dataSource.handler.DynamicSwitchDataSource)")
    public void pointcut() {}
    @Before("pointcut()") //    
    public void testBefore(JoinPoint point){
        //       class
        Class> className = point.getTarget().getClass();
        DynamicSwitchDataSource dataSourceAnnotation = className.getAnnotation(DynamicSwitchDataSource.class);
        if (dataSourceAnnotation != null ) {
            //        
            String methodName = point.getSignature().getName();
            //          
            Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
            String dataSource = DataSourceContextHolder.DATA_SOURCE_A;
            try {
                Method method = className.getMethod(methodName, argClass);
                if (method.isAnnotationPresent(DynamicSwitchDataSource.class)) {
                    DynamicSwitchDataSource annotation = method.getAnnotation(DynamicSwitchDataSource.class);
                    dataSource = annotation.dataSource();
                    System.out.println("DataSource Aop ====> "+dataSource);
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            DataSourceContextHolder.setDbType(dataSource);
        }
    }
    @After("pointcut()")   //    
    public void testAfter(JoinPoint point){
        //       class
        Class> className = point.getTarget().getClass();
        DynamicSwitchDataSource dataSourceAnnotation = className.getAnnotation(DynamicSwitchDataSource.class);
        if (dataSourceAnnotation != null ) {
            //        
            String methodName = point.getSignature().getName();
            //          
            Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
            String dataSource = DataSourceContextHolder.DATA_SOURCE_A;
            try {
                Method method = className.getMethod(methodName, argClass);
                if (method.isAnnotationPresent(DynamicSwitchDataSource.class)) {
                    DynamicSwitchDataSource annotation = method.getAnnotation(DynamicSwitchDataSource.class);
                    dataSource = annotation.dataSource();
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if(dataSource != null && !DataSourceContextHolder.DATA_SOURCE_A.equals(dataSource)) DataSourceContextHolder.clearDbType();
        }
    }
}
​
カスタムコメント;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicSwitchDataSource {
    String dataSource() default "";
}
@Service("userService")
@DynamicSwitchDataSource
public class UserServiceImpl implements UserService{
    @Resource
    private UserDao userDao;
    @DynamicSwitchDataSource(dataSource = "datasource1")
    public int save(User user) {
        // TODO Auto-generated method stub
        return userDao.save(user);
    }
​
    @Override
    public int deleteById(int id) {
        // TODO Auto-generated method stub
        return userDao.deleteById(id);
    }
​
    @DynamicSwitchDataSource(dataSource = "datasource2")
    public int update(User user) {
        // TODO Auto-generated method stub
        return userDao.update(user);
    }
}