对于Java开发来说,mybatis对于我们是经常用到的,我们知道我们只要定义相应的接口,配置好扫包的路径,就会自动生成相关的接口实现代理类,那么mybatis是如何借助Spring完成这些操作的呢,在这里我就主要分享一下我在阅读了部分Mybatis源码后的一些总结
package com.xp.config; import com.xp.dao.XXX; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportResource; @Configuration @ComponentScan("com.xp") //@Import({XXX.class}) @MapperScan("com.xp") public class AppConfig { }
这个MapperScan大家应该很熟悉,也就是mybatis的扫包注解,这也是mybatis如何让Spring动态管理包路径下面的接口,我们点进去可以看到
明显可以看出这个MapperScan是一个复合注解,关键的一个Import注解导入了一个叫MapperScannerRegistar的类,点进去
可以看到这个MapperScannerRegistar类实现的Spring提供的 ImportBeanDefinitichonRegistrar这个接口类,相信看过Spring源码的朋友都知道这个类是Spring提供的可以动态创建bean的一个扩展点,由于MapperScannerRegistar实现了ImportBeanDefinitionRegistar这个接口,也覆盖重写了里面的registerBeanDefinitions方法,从名字也可以看出这个方法是用来注册BeanDefinition的,对于什么是BeanDefinition这里就不做过多说明,其实就是Spring用来描述它需要管理的Java对象,然后封装成BeanDefinition这个数据类型,我们来看看这个registerBeanDefinitions做了些什么操作
/** * {@inheritDoc} */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // this check is needed in Spring 3.1 if (resourceLoader != null) { scanner.setResourceLoader(resourceLoader); } Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { scanner.setAnnotationClass(annotationClass); } Class<?> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { scanner.setMarkerInterface(markerInterface); } Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); } Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass)); } scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List<String> basePackages = new ArrayList<String>(); for (String pkg : annoAttrs.getStringArray("value")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : annoAttrs.getStringArray("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(basePackages)); }
首先这个方法new 了一个 ClassPathMapperScanner 的扫描器,这个ClassPathMapperScanner 继承了Spring提供的
ClassPathBeanDefinitionScanner这个类
然后调用了doScan方法,我跟进去看代码
/** * Calls the parent search that will search and register all the candidates. * Then the registered objects are post processed to set them as * MapperFactoryBeans */ @Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
然后会调用父类的doScan方法,也就是Spring提供的ClassPathBeanDefinitionScanner的doScan方法,我们来看父类的这个doScan方法
/** * Perform a scan within the specified base packages, * returning the registered bean definitions. * <p>This method does <i>not</i> register an annotation config processor * but rather leaves this up to the caller. * @param basePackages the packages to check for annotated classes * @return set of beans registered if any for tooling registration purposes (never {@code null}) */ protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
里面关键的一个findCandidateComponents(basePackage)会调用scanCandidateComponents方法,在这个方法里面完成对包路径下面的类的扫描,并判断该类文件是不是需要被Spring容器管理的Bean,然后放到这个candidates的set集合返回回去
/** * Scan the class path for candidate components. * @param basePackage the package to check for annotated classes * @return a corresponding Set of autodetected bean definitions */ public Set<BeanDefinition> findCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { return scanCandidateComponents(basePackage); } }
private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } else { if (debugEnabled) { logger.debug("Ignored because not a concrete top-level class: " + resource); } } } else { if (traceEnabled) { logger.trace("Ignored because not matching any filter: " + resource); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate component class: " + resource, ex); } } else { if (traceEnabled) { logger.trace("Ignored because not readable: " + resource); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } return candidates; }
拿到这些需要被Spring管理的Bean集合,接着会走下面的代码
从上图可以看到,程序会继续往下面执行,大概说明一下
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
方法会解析这些bean上面的lazy一些常用的注解属性,接着会执行一个关键的方法
registerBeanDefinition我们跟进去看看
/** * Register the specified bean with the given registry. * <p>Can be overridden in subclasses, e.g. to adapt the registration * process or to register further bean definitions for each scanned bean. * @param definitionHolder the bean definition plus bean name for the bean * @param registry the BeanDefinitionRegistry to register the bean with */ protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry); }
/** * Register the given bean definition with the given bean factory. * @param definitionHolder the bean definition including name and aliases * @param registry the bean factory to register with * @throws BeanDefinitionStoreException if registration failed */ public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
首先会调用BeanDefinitionReaderUtils的registerBeanDefinition方法,然后执行registry的registerBeanDefinition方法把扫描的Bean方法Spring容器的一个beanDefinitionMap的map容器里面,这些数据会在之后Spring生命周期过程中完成初始化,这就是mybatis如何借助Spring完成相关类的管理,当然这里面扫包的到的Bean的类型是一个特殊的工,厂Bean,也就是FactoryBean这个类里面有一个getObject方法,完成一个动态代理,有兴趣的朋友可以去看看mybatis的源码,当然整个这个registerBeanDefinitions的方法是在spring容器refesh里面的 invokeBeanFactoryPostProcessors方法里面的一个工厂后置处理器ConfigurationClassPostProcessor的类完成解析和触发的,有兴趣的朋友可以去研究一下,这里就不做过多描述,以上就是我的一点点理解