获取MongoTemplate的逻辑(数据源需要事先全部配置好,并对应写死的key,如下dao1,dao2),主要是参考网上的写法适当做修改:http://blog.csdn.net/qq994406030/article/details/52861421。aop拦截提供统一的代码,框架会扫描所有实现DecisionHandler的所有bean,该接口由业务代码决定,参考SpecifiedDecisionHandler。
配置如下:用户要改的是扫描的路径,用哪个数据库由用户自己实现的DecisionHandler决定
<bean id="mongoDBTemplate" class=" net.xuele.common.mongo.dao.multi.MongoDBTemplate"> <property name="targetMongoTemplates"> <map key-type="java.lang.String"> <entry key="dao1" value-ref="mongoTemplate"/> <entry key="dao2" value-ref="mongoTemplate2"/> </map> </property> <property name="defaultTargetMongoTemplate" ref="mongoTemplate"/> </bean> <bean name="dbDecisionDaoAop" class="net.xuele.common.mongo.aop.DBDecisionDaoAop"/> <!-- 配置AOP 切面 --> <aop:config proxy-target-class="true"> <!-- 定义切点函数 --> <aop:pointcut id="pointcut" expression="execution(* net.xuele.circle.reposity.impl.*.*(..)))"/> <!-- 定义通知 order 定义优先级,值越小优先级越大--> <aop:aspect ref="dbDecisionDaoAop" order="0"> <!-- 环绕通知 --> <aop:around method="doAround" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> <!-- 框架提供的一个默认实现以供参考--> <bean class="net.xuele.common.mongo.aop.handler.SpecifiedDecisionHandler" />
主要代码:
package net.xuele.common.mongo.dao.multi; import org.springframework.data.mongodb.core.MongoTemplate; /** * 可以切换数据源的mongoTemplate. * * @see MongodbTemplateContextHolder#setMongoDBTemplate(String) */ public class MongoDBTemplate extends AbstractMongoDBRoutingMongoTemplate { public MongoDBTemplate() { } public MongoTemplate getMongoTemplate() { return determineMongoTemplate(); } @Override protected Object determineCurrentLookupKey() { return MongodbTemplateContextHolder.getMongoDBTemplate(); } }
package net.xuele.common.mongo.dao.multi; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.util.Assert; import java.util.HashMap; import java.util.Map; /** * mongoTemplate路由 */ public abstract class AbstractMongoDBRoutingMongoTemplate implements InitializingBean { /** * xml配置:mongoTemplate map(必选) */ private Map<Object, Object> targetMongoTemplates; /** * xml配置:默认的mongoTemplate(必选) */ private Object defaultTargetMongoTemplate; /** * 已经初始化的mongoTemplate,数据来源:targetMongoTemplates. */ private Map<Object, MongoTemplate> resolvedMongoTemplates; /** * 默认的mongoTemplate,数据来源:defaultTargetMongoTemplate. */ private MongoTemplate resolvedDefaultMongoTemplate; public void setTargetMongoTemplates(Map<Object, Object> targetMongoTemplates) { this.targetMongoTemplates = targetMongoTemplates; } public void setDefaultTargetMongoTemplate(Object defaultTargetMongoTemplate) { this.defaultTargetMongoTemplate = defaultTargetMongoTemplate; } @Override public void afterPropertiesSet() { if (this.targetMongoTemplates == null) { throw new IllegalArgumentException("Property 'targetMongoTemplates' is required"); } this.resolvedMongoTemplates = new HashMap<>(); for (Map.Entry<Object, Object> entry : this.targetMongoTemplates.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); MongoTemplate mongoTemplate = resolveSpecifiedMongoTemplate(entry.getValue()); this.resolvedMongoTemplates.put(lookupKey, mongoTemplate); } if (this.defaultTargetMongoTemplate != null) { this.resolvedDefaultMongoTemplate = resolveSpecifiedMongoTemplate(this.defaultTargetMongoTemplate); } } protected Object resolveSpecifiedLookupKey(Object lookupKey) { return lookupKey; } protected MongoTemplate resolveSpecifiedMongoTemplate(Object mongoTemplate) throws IllegalArgumentException { if (mongoTemplate instanceof MongoTemplate) { return (MongoTemplate) mongoTemplate; } else { throw new IllegalArgumentException( "Illegal data source value - only [org.springframework.data.mongodb.core.MongoTemplate] and String supported: " + mongoTemplate); } } protected MongoTemplate determineMongoTemplate() { Assert.notNull(this.resolvedMongoTemplates, "mongoTemplate router not initialized"); Object lookupKey = determineCurrentLookupKey(); MongoTemplate mongoTemplate = this.resolvedMongoTemplates.get(lookupKey); if (mongoTemplate == null && (lookupKey == null)) { mongoTemplate = this.resolvedDefaultMongoTemplate; } if (mongoTemplate == null) { throw new IllegalStateException("Cannot determine target MongoTemplate for lookup key [" + lookupKey + "]"); } return mongoTemplate; } protected abstract Object determineCurrentLookupKey(); }
package net.xuele.common.mongo.dao.multi; import org.springframework.util.StringUtils; import java.util.Map; /** * 保存线程级别的mongoTemplate */ public class MongodbTemplateContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); /** * 设置当前使用的数据库 * * @param mongodbTemplateType 对应xml配置targetMongoTemplates的key * @see AbstractMongoDBRoutingMongoTemplate#setTargetMongoTemplates(Map) */ public static void setMongoDBTemplate(String mongodbTemplateType) { if (StringUtils.isEmpty(mongodbTemplateType)) { throw new NullPointerException("mongodbTemplateType is null"); } contextHolder.set(mongodbTemplateType); } public static String getMongoDBTemplate() { return contextHolder.get(); } /** * 当一次完整调用结束后,需要清除当前线程上下文. */ public static void clearMongoDBTemplate() { contextHolder.remove(); } }
以上是获取MongoTemplate的逻辑,主要是参考网上的写法适当做修改,下面代码主要负责aop设置哪个template的逻辑。
package net.xuele.common.mongo.aop; import net.xuele.common.mongo.aop.annotation.MultiMongo; import net.xuele.common.mongo.aop.handler.DecisionHandler; import net.xuele.common.mongo.dao.multi.MongodbTemplateContextHolder; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import javax.annotation.PostConstruct; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; /** * 切换数据库的aop流程框架. * * @see DecisionHandler */ public class DBDecisionDaoAop implements ApplicationContextAware { private static Logger logger = LoggerFactory.getLogger(DBDecisionDaoAop.class); private ApplicationContext applicationContext; private List<DecisionHandler> handlerList; /** * 设置需要查询的某个表对应的数据库. * * @param joinPoint 切入点 * @return 原方法返回的值 * @throws Throwable 原方法抛出的异常 */ public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); MultiMongo multiMongo = method.getAnnotation(MultiMongo.class); if (multiMongo == null) { return joinPoint.proceed(); } DecisionInfo decisionInfo = new DecisionInfo(); decisionInfo.setStrategy(multiMongo.strategy()); decisionInfo.setSpecify(multiMongo.specify()); decisionInfo.setCollectionName(multiMongo.collectionName()); if (multiMongo.paramLocation() >= 0) { Object[] args = joinPoint.getArgs(); decisionInfo.setSpecifyValue(args[multiMongo.paramLocation()]); } try { for (DecisionHandler decisionHandler : handlerList) { if (decisionHandler.support(decisionInfo)) { MongodbTemplateContextHolder.setMongoDBTemplate(decisionHandler.handle(decisionInfo)); break; } } return joinPoint.proceed(); } finally { MongodbTemplateContextHolder.clearMongoDBTemplate(); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @PostConstruct public void postConstruct() { handlerList = new ArrayList<>( applicationContext.getBeansOfType(DecisionHandler.class).values()); } }
package net.xuele.common.mongo.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MultiMongo { /** * 策略需要的附属信息,比如直接指定某个数据库 */ String specify() default ""; /** * 针对的表名. */ String collectionName() default ""; /** * 策略需要的参数值的位置,-1代表非必须 */ int paramLocation() default -1; /** * 使用的策略 * * @return 策略id */ String strategy(); }
package net.xuele.common.mongo.aop; /** * 用哪个数据库的所有可能需要的条件. */ public class DecisionInfo { /** * 用哪个策略的key值 */ private String strategy; /** * 表名 */ private String collectionName; /** * 策略需要的附属信息,比如直接指定某个数据库 */ private String specify; /** * 策略需要的参数值 */ private Object specifyValue; public String getStrategy() { return strategy; } public void setStrategy(String strategy) { this.strategy = strategy; } public String getCollectionName() { return collectionName; } public void setCollectionName(String collectionName) { this.collectionName = collectionName; } public String getSpecify() { return specify; } public void setSpecify(String specify) { this.specify = specify; } public Object getSpecifyValue() { return specifyValue; } public void setSpecifyValue(Object specifyValue) { this.specifyValue = specifyValue; } }
package net.xuele.common.mongo.aop.handler; /** * 业务逻辑 */ public interface DecisionHandler<T> { /** * 该处理器是否支持该参数的处理逻辑,支持,则后续调用handle方法. * * @param obj 传入的参数对象,比如DecisionInfo * @return 是否支持. */ boolean support(T obj); /** * @param obj 数据 * @return 数据库名字. */ String handle(T obj); }
aop会扫描所有实现该接口的bean作为策略的决策者。提供一个demo:
package net.xuele.common.mongo.aop.handler; import net.xuele.common.mongo.aop.DecisionInfo; /** * 指定数据库的策略 */ public class SpecifiedDecisionHandler<T extends DecisionInfo> implements DecisionHandler<T> { public static final String SPECIFIED = "SPECIFIED"; /** * 该处理器是否支持该参数的处理逻辑,支持,则后续调用handle方法. * * @param obj 传入的参数对象,比如DecisionInfo * @return 是否支持. */ @Override public boolean support(DecisionInfo obj) { if (obj.getStrategy().equals(SPECIFIED)) { return true; } return false; } /** * @param obj 数据 * @return 数据库名字. */ @Override public String handle(DecisionInfo obj) { return obj.getSpecify(); } }
注解在实现类的方法上:
@MultiMongo(specify = "dao1",strategy= SpecifiedDecisionHandler.SPECIFIED)