大家知道,在Springboot+Spring Data Jpa的项目里,dao层只需要继承JpaRepository接口,就可以实现MyBatis中@Repository+mApper的效果,不需要任何多余的配置,就可以将dao层注入bean。类似于这样:
public interface BookRepository extends JpaRepository<Book, Long>
这样一句话,就可以实现很多的增删改查效果,例如findAll(),findById()等等,可以说是非常的简单高效。
那么很多刚开始用Spring Data Jpa的同学就会很不理解,为什么这样一句话,就可以实现那么多的功能呢,不添加一个@Repository,心里总有些不踏实的感觉。
那么我们来看一下,Spring Data Jpa是怎么做到的。
一、JpaRepository的继承结构
首先我们来看看JpaRepository的继承结构。很容易看到JpaRepository的定义:
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T>
可以看到JpaRepository继承了两个接口,一个
PagingAndSortingRepository和一个QueryByExampleExecutor。
这两个接口的定义分别是:
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID>
和public interface QueryByExampleExecutor<T>
CrudRepository的定义为
public interface CrudRepository<T, ID> extends Repository<T, ID>
可以看到,最终的继承结果继承到了Repository里面。
而这一系列的继承,就为我们提供了
save(S entity);
saveAll(Iterable<S> entities);
findById(ID id);
existsById(ID id);
findAll();
findAllById(Iterable<ID> ids);
count();
deleteById(ID id);
delete(T entity);
deleteAll(Iterable<? extends T> entities);
deleteAll();
findOne(Example<S> example);
findAll(Example<S> example);
findAll(Example<S> example, Sort sort);
findAll(Example<S> example, Pageable pageable);
count(Example<S> example);
exists(Example<S> example);
findAll(Sort sort);
findAll(Pageable pageable);
等很多的功能。
二、JpaRepository为什么不需要@Repository注解
经过简单的Debug,我们就可以轻松定位到Spring注入bean的位置,是在
org.springframework.context.annotation包里面的
ClassPathScanningCandidateComponentProvider类中的
scanCandidateComponents方法里面,其中关键的代码在下面标蓝的
isCandidateComponent(metadataReader)判断里面。
而这个函数会将目标接口及其父接口一层层地往上对比,如果与该类自身的
includeFilters中的某个filter比中,则会返回true,意味着该实现将会作为bean被Spring管理起来,从而可以直接用@Autowired引用。
那么我们先来看看includeFilters里面到底有些什么东西,查询代码可以看到,该类在初始化的时候,添加了Component和ManagedBean。很显然,这与我们的Repository还是毫无关系的。事实上也是如此,在Spring启动时,第一遍扫描并没有把我们的BookRepository注入bean。
直到
org.springframework.data.repository.config包中的
RepositoryConfigurationDelegate执行的时候,才会开始扫描,而这个类执行的时候,会启动一个继承了ClassPathScanningCandidateComponentProvider类的RepositoryComponentProvider。
而在这个类里面,我们可以看到Repository最终被加载到了includeFilters里面。
此时,再扫描对应的包的时候,继承自Repository的所有dao层类,就被会注入成bean,供人们调用了。