org.zodiac.autoconfigure.datasource.DynamicDataSourceObjectAopSwitcherAutoConfiguration Maven / Gradle / Ivy
package org.zodiac.autoconfigure.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.zodiac.autoconfigure.datasource.condition.ConditionalOnDataSourceEnabled;
import org.zodiac.commons.util.Classes;
import org.zodiac.commons.util.Colls;
import org.zodiac.commons.util.lang.Strings;
import org.zodiac.core.aop.MethodInterceptorContext;
import org.zodiac.core.aop.MethodInterceptorHolder;
import reactor.core.publisher.Flux;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* 通过AOP方式进行对注解方式切换数据源提供支持。
*
*/
@SpringBootConfiguration
@ConditionalOnDataSourceEnabled
@ConditionalOnClass(value = {org.aopalliance.intercept.MethodInterceptor.class, org.zodiac.datasource.DynamicDataSourceObjectHolder.class,
org.zodiac.script.engine.util.ScriptExpressions.class, reactor.core.CorePublisher.class, org.reactivestreams.Publisher.class})
public class DynamicDataSourceObjectAopSwitcherAutoConfiguration {
private DataSourceConfigProperties dataSourceConfigProperties;
public DynamicDataSourceObjectAopSwitcherAutoConfiguration(DataSourceConfigProperties dataSourceConfigProperties) {
this.dataSourceConfigProperties = dataSourceConfigProperties;
}
@Bean
@ConditionalOnMissingBean
//@ConfigurationProperties(prefix = "hsweb.datasource")
protected org.zodiac.datasource.strategy.ExpressionDataSourceSwitchStrategyMatcher expressionDataSourceSwitchStrategyMatcher() {
return new org.zodiac.datasource.strategy.ExpressionDataSourceSwitchStrategyMatcher().setSwitcher(dataSourceConfigProperties.getDynamicStrategies());
}
@Bean
@ConditionalOnMissingBean
protected org.zodiac.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher annotationDataSourceSwitchStrategyMatcher() {
return new org.zodiac.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher();
}
@Bean
@ConditionalOnMissingBean
protected org.zodiac.datasource.strategy.TableSwitchStrategyMatcher alwaysNoMatchStrategyMatcher() {
return new org.zodiac.datasource.strategy.TableSwitchStrategyMatcher() {
@Override
public boolean match(Class target, Method method) {
return false;
}
@Override
public Strategy getStrategy(MethodInterceptorContext context) {
return null;
}
};
}
@Bean
@ConditionalOnMissingBean
protected SwitcherMethodMatcherPointcutAdvisor switcherMethodMatcherPointcutAdvisor(
List matchers, List tableSwitcher) {
return new SwitcherMethodMatcherPointcutAdvisor(matchers, tableSwitcher);
}
public static class SwitcherMethodMatcherPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
private static final long serialVersionUID = -7957131071706966882L;
private static final Logger logger = LoggerFactory.getLogger(SwitcherMethodMatcherPointcutAdvisor.class);
private List matchers;
private List tableSwitcher;
private Map cache =
Colls.concurrentMap();
private Map tableCache =
Colls.concurrentMap();
public SwitcherMethodMatcherPointcutAdvisor(List matchers,
List tableSwitcher) {
this.matchers = matchers;
this.tableSwitcher = tableSwitcher;
setAdvice((org.aopalliance.intercept.MethodInterceptor)methodInvocation -> {
org.zodiac.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher.CacheKey key =
new org.zodiac.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher.CacheKey(
Classes.getUserClass(methodInvocation.getThis()), methodInvocation.getMethod());
org.zodiac.datasource.strategy.CachedTableSwitchStrategyMatcher.CacheKey tableKey = new org.zodiac.datasource.strategy.CachedTableSwitchStrategyMatcher.CacheKey(
Classes.getUserClass(methodInvocation.getThis()), methodInvocation.getMethod());
org.zodiac.datasource.strategy.DataSourceSwitchStrategyMatcher matcher = cache.get(key);
org.zodiac.datasource.strategy.TableSwitchStrategyMatcher tableMatcher = tableCache.get(tableKey);
Consumer before = context -> {
};
AtomicBoolean dataSourceChanged = new AtomicBoolean(false);
AtomicBoolean databaseChanged = new AtomicBoolean(false);
if (matcher != null) {
before = before.andThen(context -> {
org.zodiac.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher.Strategy strategy = matcher.getStrategy(context);
if (strategy == null) {
dataSourceChanged.set(false);
logger.warn("strategy matcher found:{}, but strategy is null!", matcher);
} else {
logger.debug("switch datasource. use strategy:{}", strategy);
if (strategy.isUseDefaultDataSource()) {
org.zodiac.datasource.DynamicDataSourceObjectHolder.switcher().datasource()
.useDefault();
} else {
try {
String id = strategy.getDataSourceId();
if (Strings.hasText(id)) {
if (id.contains("${")) {
id = org.zodiac.script.engine.util.ScriptExpressions.analytical(id, context.getNamedArguments(), "spel");
}
if (!org.zodiac.datasource.DynamicDataSourceObjectHolder.existing(id)) {
if (strategy.isFallbackDefault()) {
org.zodiac.datasource.DynamicDataSourceObjectHolder.switcher()
.datasource().useDefault();
} else {
throw new org.zodiac.datasource.exception.DataSourceNotFoundException(
"数据源[" + id + "]不存在");
}
} else {
org.zodiac.datasource.DynamicDataSourceObjectHolder.switcher().datasource()
.use(id);
}
dataSourceChanged.set(true);
}
} catch (RuntimeException e) {
dataSourceChanged.set(false);
throw e;
} catch (Exception e) {
dataSourceChanged.set(false);
throw new RuntimeException(e.getMessage(), e);
}
}
if (Strings.hasText(strategy.getDatabase())) {
databaseChanged.set(true);
org.zodiac.datasource.DynamicDataSourceObjectHolder.switcher().datasource()
.use(strategy.getDatabase());
}
}
});
}
if (tableMatcher != null) {
before = before.andThen(context -> {
org.zodiac.datasource.strategy.TableSwitchStrategyMatcher.Strategy strategy = tableMatcher.getStrategy(context);
if (null != strategy) {
logger.debug("switch table. use strategy:{}", strategy);
// strategy.getMapping().forEach(DataSourceHolder.switcher()::use);
} else {
logger.warn("table strategy matcher found:{}, but strategy is null!", matcher);
}
});
}
Class> returnType = methodInvocation.getMethod().getReturnType();
if (returnType.isAssignableFrom(Flux.class)) {
// TODO: 2019-10-08
}
MethodInterceptorHolder holder = MethodInterceptorHolder.create(methodInvocation);
before.accept(holder.createParamContext());
try {
return methodInvocation.proceed();
} finally {
if (dataSourceChanged.get()) {
org.zodiac.datasource.DynamicDataSourceObjectHolder.switcher().datasource().useLast();
}
if (databaseChanged.get()) {
org.zodiac.datasource.DynamicDataSourceObjectHolder.switcher().datasource().useLast();
}
// DataSourceHolder.tableSwitcher().reset();
}
});
}
@Override
public boolean matches(Method method, Class> aClass) {
Class> targetClass = Classes.getUserClass(aClass);
org.zodiac.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher.CacheKey key =
new org.zodiac.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher.CacheKey(targetClass, method);
matchers.stream().filter(matcher -> matcher.match(targetClass, method)).findFirst()
.ifPresent((matcher) -> cache.put(key, matcher));
boolean datasourceMatched = cache.containsKey(key);
boolean tableMatched = false;
if (null != tableSwitcher) {
org.zodiac.datasource.strategy.CachedTableSwitchStrategyMatcher.CacheKey tableCacheKey =
new org.zodiac.datasource.strategy.CachedTableSwitchStrategyMatcher.CacheKey(targetClass, method);
tableSwitcher.stream().filter(matcher -> matcher.match(targetClass, method)).findFirst()
.ifPresent((matcher) -> tableCache.put(tableCacheKey, matcher));
tableMatched = tableCache.containsKey(tableCacheKey);
}
return datasourceMatched || tableMatched;
}
}
}