mybatis-spring注解MapperScan的原理
很多开发者应该都知道,我们只使用@MapperScan这个注解就可以把我们写的Mybatis的Mapper接口加载到Spring的容器中,不需要对每个Mapper接口加@Mapper这个注解了,加快了我们开发的效率。如下:
就可以把我们写在io.renren.mapper这个包下的Mapper接口加载到我们的Spring容器中。当然mybatis-spring能使用这样的注解还是因为的大神开发者给我们提供了大量的可扩展的接口。下面就聊聊它的原理就是@MapperScan这个注解如下:
/* * @since 1.2.0 * @see MapperScannerRegistrar * @see MapperFactoryBean // 这个类贴出来它很重要 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan {
好家伙又是@Import这个类帮助我们导入了MapperScannerRegistrar【PS这个类前面文章也多次用到,它的3个作用我前面的文章也都说过了,有兴趣可以翻看我的前面写的文章】,这个类的代码如下:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); // 注册了一个 MapperScannerConfigurer的BeanDefinition registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }
它主要是帮我们注入了一个MapperScannerConfigurer类型的BeanDefinition 。那我们就先看这个类的相关代码如下:
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor{ @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); scanner.registerFilters(); // 开始扫描指定类下的Mapper接口 scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
如上面的代码 MapperScannerConfigurer这个类实现了BeanDefinitionRegistryPostProcessor【以前的文章也有提到】
是Spring提供给开发者供开发者实现这个接口然后可以介入Spring的启动周期,具体Spring是什么时候运行这段代码的呢?教大家一个好的方法如下:
我们断点打到上面的位置,然后Debug启动项目,然就可以看到这个方法的调用路径如下,经过5步就调用到这个方法了。
第一个方法AbstractApplicationContext.refresh方法应该是Spring中最重要的方法了,Spring的很多很多功能都是在这个方法里面完成的,有兴趣的可以读一下,下面贴出来直接回调的这个方法如下:
/** * Invoke the given BeanDefinitionRegistryPostProcessor beans. * 这个方法就会回调MapperScannerConfigurer的 * postProcessBeanDefinitionRegistry方法 */ private static void invokeBeanDefinitionRegistryPostProcessors( Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) { for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanDefinitionRegistry(registry); } }
帮大家找到Spring回调的方法了,就还回到MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法。我们主要看scan【扫描】这个方法,进入这个方法如下:
Spring大神写代码有个特点方法前面带do就是真正工作的方法【PS我们写复杂业务的时候也可以模仿do....方法】因为do有做,执行的意思。代码如下:
/** 在我们指定的包内执行 * Perform a scan within the specified base packages, // 返回扫描到的 bean definitions. * returning the registered bean definitions. */ protected Set<BeanDefinitionHolder> doScan(String... basePackages) { // 扫描到的beanDefinitions 放到这里 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // 获取候选的组件【我们写的Mapper接口】,主要的逻辑都在这里 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); beanDefinitions.add(definitionHolder); } return beanDefinitions; }
findCandidateComponents方法如下:
private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { // 根据我们写的包名获取Mapper接口在项目中的实际路径 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; // 获取包下的所有接口的资源 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); for (Resource resource : resources) { if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); if (isCandidateComponent(sbd)) { // 把我们写的Dao或者Mapper接口放入candidates中 candidates.add(sbd); } } } // 把封装的Mapper的 BeanDefinition返回 return candidates; }
把封装好的BeanDefinition封装好返回后,后面就会进入Spring Bean的生命周期进行Bean的初始化,然后就提供给开发者直接使用了。到这里有的小伙伴该问了,我们写的是接口Spring怎么能给接口初始化呢?上面这些都是瞎聊的。如果能这样问说明你的Java基础很不错哦,值得鼓励。那小伙伴是否记得我文章开始说 MapperFactoryBean这个类呢?我还特别强调这个类很重要,这个类是FactoryBean。Mybatis是把我们写的接口生成了的代理对象MapperProxy 。由于我使用的是MybatisPlus,它又封装一下MapperProxy变成了MybaitsMapperProxy,这个类直接copy了Mybatis的代码。我们通过MapperFactoryBean的getObject()方法就能获取MapperProxy了,它不是接口。
FactoryBean和BeanFactory的区别:
FactoryBean,是Spring提供的一个扩展点,用于复杂Bean的创建。mybatsi在跟Spring做整合时候就用到了这个扩展点。并且FactoryBean所创建的Bean跟普通的Bean不一样。它有getObject()方法才可以获取具体的Bean
BeanFactory是Spring IOC容器的顶级接口,其实现类有XMLBeanFactory ,DefaultListBeanFactory。BeanFactory为Srping管理Bean提供了一套通用的接口规范。
如果不信可以看如下,图中解释的很明白了:
上面就是MapperScan的整个原理,如果跟着上面的代码走一遍流程相信你对它的原理就更懂了,你写代码的时候对写的Mapper接口应该就知道不加@Mapper这个注解Spring也能帮你完成你写的Mapper接口的初始化。
再分享一个Spring生成UUID的很高效的工具类:AlternativeJdkIdGenerator。
大意是它使用了SecureRandom作为种子,来替换调用UUID#randomUUID()。它提供了一个更好、更高性能的表现 发现仅仅循环生成1000万次,Spring提供的算法性能远远高于JDK的。因此建议大家以后使用AlternativeJdkIdGenerator去生成UUID,性能会更好一点
缺点是:还需要new对象才能使用,不能通过类名直接调用静态方法,当然我们可以二次封装。另外,一般输出串我们都会进一步这么处理:.toString().replace("-", "")
如下对比一下就知道它的性能有多好了。
/** * 直接调用java的那个UUID工具 */ JdkIdGenerator jdkIdGenerator = new JdkIdGenerator(); AlternativeJdkIdGenerator alternativeJdkIdGenerator = new AlternativeJdkIdGenerator(); Instant start; Instant end; int count = 10000000; //jdkIdGenerator start = Instant.now(); for (int i = 0; i < count; i++) { jdkIdGenerator.generateId(); } end = Instant.now(); System.out.println("jdkIdGenerator循环" + count + "次耗时:" + Duration.between(start, end).toMillis() + "ms"); //alternativeJdkIdGenerator start = Instant.now(); for (int i = 0; i < count; i++) { alternativeJdkIdGenerator.generateId(); } end = Instant.now(); System.out.println("alternativeJdkIdGenerator循环" + count + "次耗时:" + Duration.between(start, end).toMillis() + "ms");
如下结果如下Spring的性能太高了。