深入探究Java @MapperScan实现原理
程序员小潘 人气:01. 前言
MyBatis在整合Spring的时候,只需要加如下 注解,就可以将Mapper实例注册到IOC容器交给Spring管理,它是怎么做到的呢???
@MapperScan("com.xxx.mapper")
提出几个问题:
- Mapper接口不能实例化,对象是怎么来的?
- Mapper接口没有加任何Spring相关注解,Spring凭什么管理这些Bean?
2. ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar是Spring提供的接口,属于Spring的扩展点之一。该接口会暴露 BeanDefinitionRegistry对象,Spring允许我们手动往容器注册自定义的BeanDefinition。
public interface ImportBeanDefinitionRegistrar { /** * 注册自定义BeanDefinition * * @param importingClassMetadata 导入类的元数据,被谁导入的 * @param registry BeanDefinition注册器 */ void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); }
使用起来也很简单,新建类实现ImportBeanDefinitionRegistrar接口,重写registerBeanDefinitions()方法实现注册自定义BeanDefinition的相关逻辑,然后通过@Import
注解引入即可。
ImportBeanDefinitionRegistrar实例本身并不会注册到容器,Spring仅仅是通过反射实例化对象,然后触发registerBeanDefinitions()
方法而已。
3. ConfigurationClassPostProcessor
ImportBeanDefinitionRegistrar扩展点是在哪里被触发的呢???
AnnotationConfigApplicationContext类的构造函数里会创建AnnotatedBeanDefinitionReader对象用来读取并注册基于注解的BeanDefinition,AnnotatedBeanDefinitionReader的构造函数有一个特别重要的功能,就是往容器手动注册Spring内置的几个非常重要的,用来支撑Spring底层核心功能的BeanDefinition,分别是:
- ConfigurationClassPostProcessor
- AutowiredAnnotationBeanPostProcessor
- CommonAnnotationBeanPostProcessor
- PersistenceAnnotationBeanPostProcessor
- EventListenerMethodProcessor
- DefaultEventListenerFactory
ConfigurationClassPostProcessor这个类特别特别重要,它做的事情包括:
- 解析
@ComponentScan
注解扫描自定义的Bean。 - 解析
@PropertySources
和@Value
注解读取配置文件属性。 - 解析
@Import
注解引入自定义类。 - 解析
@ImportResource
注解引入外部Spring配置文件。 - 处理
@Bean
注解方法。
ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor接口。BeanFactoryPostProcessor也是Spring的扩展点之一,它允许开发者对BeanFactory进行扩展;BeanDefinitionRegistryPostProcessor扩展的语义更明确一些,它表示要对BeanFactory完成BeanDefinition的注册。BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()
会比BeanFactoryPostProcessor#postProcessBeanFactory()
先执行。
Spring启动时,准备好BeanFactory后就会开始触发BeanFactoryPostProcessor扩展点,ConfigurationClassPostProcessor因为在构造函数里已经被注册到容器中,所以会被执行到。它会去解析ConfigurationClass是否有加@Import
注解,如果加了该注解,且引入的类是ImportBeanDefinitionRegistrar子类,就会去实例化子类对象,然后执行它的registerBeanDefinitions()
方法。
4. MapperScannerRegistrar
查看@MapperScan
注解发现,它的确加了@Import
注解,且引入的MapperScannerRegistrar类就是ImportBeanDefinitionRegistrar的子类。
@Retention(RetentionPolicy . RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
也就是说Spring在启动时,触发ImportBeanDefinitionRegistrar扩展点的时候,会执行MyBatis写的MapperScannerRegistrar的扩展逻辑。其实从名字就可以看的出来,这个类的作用是完成MapperScanner的注册工作,MapperScanner是啥?就是Mapper接口的扫描器了。
MapperScannerRegistrar的扩展逻辑很简单,创建自定义的Bean扫描器ClassPathMapperScanner,然后扫描@MapperScan
注解指定的包路径。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { private ResourceLoader resourceLoader; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 注解属性 AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); // 创建自定义的Mapper扫描器,用来扫描Mapper接口 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)) { // 如果指定了注解,则只扫描加了指定注解的Mapper接口 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)) { // 指定BeanName生成器,如果有 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)); } // 注册过滤器,定义Bean的扫描规则 scanner.registerFilters(); // 开始扫描 scanner.doScan(StringUtils.toStringArray(basePackages)); } /** * {@inheritDoc} */ @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } }
具体的扫描工作交给了ClassPathMapperScanner类,它继承自Spring提供的ClassPathBeanDefinitionScanner,就不用自己去实现扫描Class的逻辑了,这里用到了模板方法模式,子类通过重写部分方法,来自定义Bean的扫描和注册规则。
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 父类完成扫描,得到一组BeanDefinition Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { // 没有符合的Bean,不做处理 logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { // 处理BeanDefinition,因为Mapper接口不能被实例化 processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
调用父类的doScan()
方法完成扫描得到一组BeanDefinition,如果有符合规则的BeanDefinition,这里需要做处理,不能直接返回。因为此时BeanDefinition的beanClass指向的是Mapper接口,直接注册到容器的话,Spring不知道怎么实例化Bean。 所以,MyBatis还需要做点小动作,对BeanDefinition做一些修改。主要是重设beanClass,将其指向MapperFactoryBean。因为MapperFactoryBean是类,可以被实例化。
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); // MapperFactoryBean构造函数需要MapperClass // 这里是告诉Spring实例化MapperFactoryBean时构造函数传哪个Class definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // 重设beanClass 指向MapperFactoryBean definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { if (logger.isDebugEnabled()) { logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); } definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }
5. MapperFactoryBean
Mapper接口不能被实例化,所以MyBatis会将扫描到的属于MapperClass的BeanDefinition做些修改,将beanClass指向MapperFactoryBean,这样Spring在实例化Bean的时候就会去创建MapperFactoryBean实例了。
MapperFactoryBean实现了FactoryBean接口,SpringgetBean()
时会判断,如果一个BeanClass实现了FactoryBean接口,则不直接返回bean,而是返回FactoryBean#getObject()
方法返回的对象。也就是说,本该由Spring完成的Bean实例化过程,交给了MyBatis自己来实现。
@Override public T getObject() throws Exception { // 基于Mapper接口生成代理对象 return getSqlSession().getMapper(this.mapperInterface); }
通过MapperFactoryBean#getObject()
发现,MyBatis会调用SqlSession#getMapper()
方法基于Mapper接口创建JDK动态代理对象。也就是说,Mapper接口对应的BeanDefinition,对应的在Spring容器里的对象是MyBatis生成的代理对象。
加载全部内容