背景
由于项目已经很重的使用了mongodb, 对于mongodb的访问主要采用spring-data-mongo,该框架对于一些简单的查询语句都能直接通过Query methods,需要手工写查询语句的情况也很少,这种magic般的使用方法也引起了我的好奇,以前写查询语句的时候规范也是说,对于进行sql查询的方法应当和查询的条件保持一致,例如findByXXX之类,没想到spring data只需要按照类似这种规范,就能直接生成对应的查询语句,那么底层是怎么实现的?
源码分析
目的
了解spring接口类方法的原理。
准备工作
通过spring boot快速搭建demo,该demo包含一个MongoRepository,通过main函数执行一些简单的查询方法。
1 | /** |
源码分析
Spring Boot配置
要了解spring data mongo原理,最直接的方法就是看repository是怎么初始化的,由于该demo是基于spring boot,按照spring boot的特点,初始化的控制都是由各种AutoConfiguration引导。
直接在IDE里搜索 *Mongo*AutoConfiguration, 可以发现以下配置类
相关配置类
- org.springframework.boot.autoconfigure.mongo,MongoClient,MongoTemplate相关配置
- org.springframework.boot.autoconfigure.data.mongo,data mongo相关配置,涉及到核心类Repository
spring data mongo主要是由这两个package下的配置类初始化,对于Repository类的初始化,核心类是
MongoRepositoriesAutoConfigureRegistrar
1 | class MongoRepositoriesAutoConfigureRegistrar |
该类继承了AbstractRepositoryConfigurationSourceSupport并且实现了三个抽象方法,查看了AbstractRepositoryConfigurationSourceSupport的子类,可以发现spring data很多模块都是继承该类,所以AbstractRepositoryConfigurationSourceSupport是Spring data初始化的核心
- AbstractRepositoryConfigurationSourceSupport
- CassandraRepositoriesAutoConfigureRegistrar
- MongoRepositoriesAutoConfigureRegistrar
- ElasticsearchRepositoriesRegistrar
- LdapRepositoriesRegistrar
- CassandraReactiveRepositoriesAutoConfigureRegistrar
- CouchbaseRepositoriesRegistrar
- JpaRepositoriesAutoConfigureRegistrar
- CouchbaseReactiveRepositoriesRegistrar
- RedisRepositoriesAutoConfigureRegistrar
- Neo4jRepositoriesAutoConfigureRegistrar
- SolrRepositoriesRegistrar
- MongoReactiveRepositoriesAutoConfigureRegistrar
MongoRepositoriesAutoConfigureRegistrar
该类实现的三个方法都是返回了配置类POJO,这里主要有两个对象
- EnableMongoRepositories 配置类,方法getRepositoryFactoryBeanClassName返回MongoRepositoryFactoryBean,该工厂bean返回了Repositoryd的代理
- RepositoryConfigurationExtension 该类主要是在Spring Data通用的bean初始化阶段加入mongodb特有的一些配置,其大部分方法都是接受了BeanDefinitionBuilder进行一些
EnableMongoRepositories
1 | public EnableMongoRepositories { |
EnableMongoRepositoriesConfiguration
1 |
|
RepositoryConfigurationExtension
1 | public interface RepositoryConfigurationExtension { |
初始化流程
入口
MongoRepositoriesAutoConfigureRegistrar的父类AbstractRepositoryConfigurationSourceSupport
AbstractRepositoryConfigurationSourceSupport,该类主要实现了ImportBeanDefinitionRegistrar的registerBeanDefinitions方法,registerBeanDefinitions是Spring初始化的时候调用,让程序能够自主注册一些bean到spring容器里
1 |
|
流程
registerBeanDefinitions. registerBeanDefinitions调用了RepositoryConfigurationDelegate.registerRepositoriesIn方法
这个方法包含了spring data mongo对于mongo repository的初始化的整个流程,主要逻辑是通过configurationSource(由EnableMongoRepositories构造),以及RepositoryConfigurationExtension扫描BasePackages下面的类,构造beanDefinition并且注册到spring registry。
1 | /** |
BeanDefinition构造
registerRepositoriesIn调用了RepositoryBeanDefinitionBuilder.build方法,注意到BeanDefinitionBuilder
.rootBeanDefinition(configuration.getRepositoryFactoryBeanClassName()),这里把MongoRepositoryFactoryBean设置为BeanDefinition的class,意味着Bean的初始化由MongoRepositoryFactoryBean进行控制
1 | public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) { |
MongoRepositoryFactoryBean
MongoRepositoriesAutoConfigureRegistrar的职责最终就是把所有扫到的Repository构造成BeanDefinition注册到Registry, BeanDefinition的BaseClass是MongoRepositoryFactoryBean,所以该FactoryBean是spring data mongo的核心。
从该Sequence Diagram可以看出Repository最终的实现是通过Proxy,这里只列出了两个关键的Interceptor
- QueryExecutorMethodInterceptor - 该类主要实现了Query methods
- ImplementationMethodExecutionInterceptor - 该类主要实现了repositories.custom-implementations
Sequence Diagram
Class Diagram
QueryExecutorMethodInterceptor
从该Sequence Diagram可看出QueryExecutorMethodInterceptor最终构造了PartTree,PartTree又Predicate组成
Sequence Diagram
Class Diagram
PartTree
PartTree首先通过方法名(source参数)解析出语句类型(查询/更新/删除),然后把余下的部分传给了Predicate, 例如findByAuthorOrTitle(以下的解析把这个方法名作为例子),那么传给Predicate的就是AuthorOrTitle
下面这些静态变量则是定义了合法的方法名前缀,从这些变量可知道查询方法的前缀不仅仅只支持“find”,😊同时也支持“read/get/query/steam”.这些可在官方文档没提及。
1 | private static final String KEYWORD_TEMPLATE = "(%s)(?=(\\p{Lu}|\\P{InBASIC_LATIN}))"; |
Predicate
那么Predicate的逻辑又是怎样的呢?Predicate主要是根据repository的方法名,通过关键字“Or”,把方法名split成多个子字符串,构造OrPart, 多个OrPart最终在mongo的查询里就会解析成了$or.例如AuthorOrTitle就会被拆成Author和Title然后构造成两个OrPart对象。
1 | public Predicate(String predicate, Class<?> domainClass) { |
OrPart
从OrPart的构造函数可以看出,这里是把Source拆成构造成List\<Part>,以Author作为例子,由于也不包含”And”,所以只会有一个Part对象。
1 | OrPart(String source, Class<?> domainClass, boolean alwaysIgnoreCase) { |
Part
Part主要设置了两个成员变量
1 | public Part(String source, Class<?> clazz, boolean alwaysIgnoreCase) { |
总结
- 扫描路径下继承Repository的接口(当有多个spring data模块存在时,则会限定到MongoRepository)的类生成代理。
- 解析接口名生成PartTree,执行的时候根据对应PartTree生成Criteria条件,传给MongoTemplate.
彩蛋
- 😆看源码过程中发现SurroundingTransactionDetectorMethodInterceptor枚举类可继承接口