Spring中AOP的探索与实践(二)之Mongo多数据源切换

在之前的一片文章中介绍了使用AOP的方式来实现Redis的多数据源切换。而今天这一篇则是主要讲述Mongo的多数据源切换。

使用AOP来实现Mongo的数据源切换与Redis的AOP切换相同,不同之处是需要替换MongoRepository里面的MongoOperations,从而实现多数据源的切换

代码示例

配置类,读取Mongo的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class MongoMultiProperties {

private static final Logger LOGGER = LoggerFactory.getLogger(MongoMultiProperties.class);

@Bean(name="mongodb1")
@Primary
@ConfigurationProperties(prefix = "spring.data.mongodb.db1")
public MongoProperties db1Properties(){
LOGGER.info("正在初始化db1");
return new MongoProperties();
}

@Bean(name = "mongodb2")
@ConfigurationProperties(prefix = "spring.data.mongodb.db2")
public MongoProperties db2Properties(){
LOGGER.info("正在初始化db2");
return new MongoProperties();
}

}

配置MongoRepository

以下是为了演示,所以配置了两个MongoRepository,实际上使用了AOP的方式实现的多数据源,只需要配置一个默认的MongoRepository即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
@EnableMongoRepositories(mongoTemplateRef = "mongoDB2")
public class DB2Template {

@Autowired
@Qualifier("mongodb2")
private MongoProperties mongoProperties;

@Bean("mongoDB2")
public MongoTemplate db2Template(){
return new MongoTemplate(db2Factory(mongoProperties));
}

@Bean
public MongoDbFactory db2Factory(MongoProperties mongoProperties){
return new SimpleMongoDbFactory(new MongoClient(mongoProperties.getHost(),mongoProperties.getPort()),mongoProperties.getDatabase());
}

}
1
2
3
4
@Repository
public interface DB2Repository extends MongoRepository<MongoDB2,String>{
}

省略DB1Template的配置,基本上都是差不多的

一般使用

上述的配置如果都OK的话,则可以直接使用@Autowired注解使用。

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class MongoService {
@Autowired
DB2Repository db2Repository;

@Autowired
DB1Repository db1Repository;

public void mongoUpdate(){
db2Repository.save(new MongoDB2());
}
}

但是使用AOP的方式的话,切Service还是Repository是需要选择的,首先因为在业务使用中,肯定是包含许多的Service的,如果以后需要再添加其他的Service,还需要添加切点,比较麻烦。

如果是切Repository的话,那么这就好办了,直接配置一个主Repository,然后切这个主Repository,这样就可以将Service和AOP进行解耦。从而在Service里面,可以随意使用其他的数据源,例如:Mysql数据源,Redis数据源等。更加灵活

切面写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Slf4j
@Component
@Aspect
@Order(-1)
public class MongoAspect implements ApplicationContextAware {

private ApplicationContext applicationContext;


@Around("execution(* com.somersames.config.mongo.db2.DB2Repository.*(..))")
public Object doSwitch(ProceedingJoinPoint joinPoint) throws Throwable {
return aopTest1(joinPoint);
// aopTest2(joinPoint);
}


private Object aopTest1(ProceedingJoinPoint joinPoint) throws Throwable {
Field methodInvocationField = joinPoint.getClass().getDeclaredField("methodInvocation");
methodInvocationField.setAccessible(true);
ReflectiveMethodInvocation o = (ReflectiveMethodInvocation) methodInvocationField.get(joinPoint);
Field targetField = o.getClass().getDeclaredField("target");
targetField.setAccessible(true);
Object target = targetField.get(o);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
Object singletonTarget = AopProxyUtils.getSingletonTarget(target);
Field mongoOperationsField = singletonTarget.getClass().getDeclaredField("mongoOperations");
mongoOperationsField.setAccessible(true);
//需要移除final修饰的变量
modifiersField.setInt(mongoOperationsField,mongoOperationsField.getModifiers()&~Modifier.FINAL);
mongoOperationsField.set(singletonTarget, applicationContext.getBean("mongoDB1"));
return joinPoint.proceed();
}


private Object aopTest2(ProceedingJoinPoint joinPoint) throws Throwable {
Field methodInvocationField = joinPoint.getClass().getDeclaredField("methodInvocation");
methodInvocationField.setAccessible(true);
ReflectiveMethodInvocation o = (ReflectiveMethodInvocation) methodInvocationField.get(joinPoint);
Field h = o.getProxy().getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(o.getProxy());
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object o2 = advised.get(aopProxy);
if (o2 instanceof Advised) {
Object o1 = ((Advised) o2).getTargetSource().getTarget();
Object o3 = AopProxyUtils.getSingletonTarget(o1);
System.out.println(o3);
Field mongoOperationsField = o3.getClass().getDeclaredField("mongoOperations");
mongoOperationsField.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
//需要移除final修饰的变量
modifiersField.setInt(mongoOperationsField, mongoOperationsField.getModifiers() & ~Modifier.FINAL);
mongoOperationsField.set(o3, applicationContext.getBean("mongoDB1"));
}
return joinPoint.proceed();
}

@Override
public void setApplicationContext(org.springframework.context.ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

在上述的代码中,提供了两种的AOP的写法,但是最终都是获取mongoOperations,然后通过applicationContext来替换。

对比AOP的Redis写法,这里可以看到在Spring中的AOP实现,最起码使用JDK动态代理Cglib。所以在本文中,使用的是

Field h = o.getProxy().getClass().getSuperclass().getDeclaredField(“h”);

这个就是获取JDK动态代理的对象

至此,mongo的两种代理方式9最初版的代码编写完毕,后续可能需要对代码进行优化,从而避免每一次修改application.yml都需要手动添加Repository

完整代码可以访问https://github.com/Somersames/Multi-Resource

作者

Somersames

发布于

2019-03-13

更新于

2021-12-05

许可协议

评论