Mybatis 接口编程中dao 层接口没有注解和<bean> 为什么能被实例化为bean??

Mybatis 接口编程中dao 层接口没有注解和 为什么能被实例化为bean??

相信不少人有过这个疑问,我自己带着这个疑问好久了!我自己写dao 层接口都是自己加上@Repository这个注解,但是项目组的其他同事不写的情况也可以正常注入?带着这个疑问我一点一点查找资料。最终发现 MapperScannerConfigurer 帮我们做了实例化bean的工作。

在Spring配置Mybatis的文件中我们可以看到如下代码:
[html] view plain copy 在CODE上查看代码片派生到我的代码片

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
<property name="basePackage" value="org.tarena.note.dao">  
</property>  

MapperScannerConfigurer,让它扫描特定的包,自动帮我们成批地创建映射器。这样就大大减少了配置的工作量。
basePackage属性是让你为映射器接口文件设置基本的包路径。可以使用分号或逗号作为分隔符设置多于一个的包路径。每个映射器都会在指定的包路径中递归地被搜索到。被发现的映射器将会使用spring对自动侦测组件默认的命名策略来命名。也就是说,如果没有发现注解,它就会使用映射器的非大写的非完全限定类名。如果发现了@Component或JSR-330@Named注解,它会获取名称。
通过上面的配置,spring就会帮助我们对test.mybatis.dao下面所有接口进行自动的注入,而不需要为每个接口重复在Spring配置文件中进行声明了。那么这个功能如何做到的呢?MapperScanner Configurer中又有哪些核心操作呢?同样首先看下这个类实现了InitializingBean接口。马上查找afterPropertiesSet方法来看看类的初始化逻辑。
[java] view plain copy 在CODE上查看代码片派生到我的代码片

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {  

但没有任何有意义的实现,我们看到MapperScannerConfigurer还实现了接口BeanDefinitionRegistryPostProcessor接口的方法:
[java] view plain copy 在CODE上查看代码片派生到我的代码片

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {  
  if (this.processPropertyPlaceHolders) {  
    processPropertyPlaceHolders();  
  }  

  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.registerFilters();  
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));  
}  

正是这里。大致看下代码实现,正是完成了对指定路径扫描的逻辑。那么,我们就以此为入口,详细地分析MapperScannerConfigurer所提供的逻辑实现
1.processPropertyPlaceHolders属性的处理
首先,难题就是processPropertyPlaceHolders属性的处理。或许许多人并未接触过此属性。我们只能查看processPropertyPlaceHolders()函数来反推此属性所代表的功能。
[java] view plain copy 在CODE上查看代码片派生到我的代码片

private void processPropertyPlaceHolders() {  
  Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);  

  if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {  
    BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext)  
        .getBeanFactory().getBeanDefinition(beanName);  

    // PropertyResourceConfigurer does not expose any methods to explicitly perform  
    // property placeholder substitution. Instead, create a BeanFactory that just  
    // contains this mapper scanner and post process the factory.  
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();  
    factory.registerBeanDefinition(beanName, mapperScannerBean);  

    for (PropertyResourceConfigurer prc : prcs.values()) {  
      prc.postProcessBeanFactory(factory);  
    }  

    PropertyValues values = mapperScannerBean.getPropertyValues();  

    this.basePackage = updatePropertyValue("basePackage", values);  
    this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);  
    this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);  
  }  
}  

此函数的说明给我嗯进行了说明:BeanDefinitionRegistries会在应用启动的时候调用,并且会早于BeanFactoryPostProcessors的调用,这就意味着PropertiesResourceConfigurers还没有被加载所有对于属性文件的引用将会失效,为避免此种情况发生,此方法手动地找出定义的PropertyResourceConfigurers并进行调用以以保证对于属性的引用可以正常工作。
下面我们举个例子说明:
如果在配置文件中添加如下代码 :

<bean class="org.mybatis.Spring.mapper.MapperScannerConfigurer">  
      <property name="basePackage" value="${basePackage}"/>  
</bean>  

此时你会发现这个配置并没有生效。因为在解析MapperScannerConfigurer这个bean的时候,配置文件还没有被加载,为了解决这个问题,Spring提供了processPropertyPlaceHloder属性,你需要这样配置MapperScannerConfigurer类型的bean:

<bean class="org.mybatis.Spring.mapper.MapperScannerConfigurer">  
      <property name="basePackage" value="${basePackage}"/>  
      <property name="processPropertyPlaceHolders" value="true">  
</bean>  

通过processPropertyPlaceHolders属性的配置,将程序引入我们正在分析的processPropertyPlaceHolders函数中来完成属性文件的加载。至此,我们理清了这个函数的属性,再回顾看下这个函数做的事情:
(1)找到所有已经注册的PropertyResourceConfigurer类型的bean。
(2)模拟Spring的环境来用处理器。这里通过使用呢new DefaultlistableBeanFactory()来模拟Spring中的环境(完成处理器的调用后便失效),将映射的bean,也就是MapperScannerConfigurer类型bean注册到环境中来进行后处理器的调用。
2.根据配置属性生成过滤器
在postProcessBeanDefinitionRegistry方法中可以看到,配置中支持很多属性的设定,但是我们感兴趣的或者说影响扫描结果的并不多,属性设置后通过在scanner.registerFilters()代码中生成对应的过滤器来控制扫描结果。

public void registerFilters() {  
  boolean acceptAllInterfaces = true;  

  // if specified, use the given annotation and / or marker interface  
  //对于annotationClass属性的处理  
  /* 
   *如果annotationClass不为空,表示用户设置了此属性,那么就要根据此属性生成过滤器以保证达到用户 
   *想要的效果,而封装此属性的过滤器就是AnnotationTypeFilter.AnnotationTypeFilter保证在扫描对应 
   *java文件时只接受标记有注解为annotationClass接口  
   */  
  if (this.annotationClass != null) {  
    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));  
    acceptAllInterfaces = false;  
  }  

  // override AssignableTypeFilter to ignore matches on the actual marker interface  
  //对于markerInterface属性的处理  
  if (this.markerInterface != null) {  
    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {  
      @Override  
      protected boolean matchClassName(String className) {  
        return false;  
      }  
    });  
    acceptAllInterfaces = false;  
  }  

//全局默认处理
/*
* 在上面两个属性中如果存在其中任何属性,acceptAllInterfaces的值将会被改变,但是如果用户没有设定以上的属性
* 那么,Spring会为我们增加一个默认的过滤器实现TypeFilter接口的局部类,旨在接受所有接口文件。
*/

  if (acceptAllInterfaces) {  
    // default include filter that accepts all classes  
    addIncludeFilter(new TypeFilter() {  
      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {  
        return true;  
      }  
    });  
  }  

  // exclude package-info.java  
  //不扫描package-info.java文件  
  addExcludeFilter(new TypeFilter() {  
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {  
      String className = metadataReader.getClassMetadata().getClassName();  
      return className.endsWith("package-info");  
    }  
  });  
}  

从上面的函数我们可以看出,控制扫描文件Spring通过不同的过滤器完成,这些定义的过滤器记录在了includeFilters和excludeFilters属性中。

public void addIncludeFilter(TypeFilter includeFilter){  
       this.includeFilters.add(includeFilter);  
}  

public void addExcludeFilter(TypeFilter excludeFilter){  
       this.excludeFilters.add(0,excludeFilter);  
}  

至于过滤器为什么会在扫描过程中起作用,我们在后面分析扫描实现的时候再深入研究。
3.扫描Java文件
设置了相关属性以及生成了对应的过滤器后就可以进行文件的扫描了,扫描工作是有ClassPathMapperScanner类的父类ClassPathBeanDefinitionScanner的scan方法完成的。

public int scan(String... basePackages) {  
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();  

    doScan(basePackages);  

    // Register annotation config processors, if necessary.  
    if (this.includeAnnotationConfig) {  
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);  
    }  

    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);  
}  

scan是个全局方法,扫描工作通过
[java] view plain copy 在CODE上查看代码片派生到我的代码片
doScan(basePackages);
委托给了doScan方法,同时,还包括了includeAnnotationConfig属性的处理,AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);代码主要是完成对于注解处理器的简单注册,我们下面主要分析下扫描功能的实现 。

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 {  
    for (BeanDefinitionHolder holder : beanDefinitions) {  
      GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();  

      if (logger.isDebugEnabled()) {  
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()   
            + "' and '" + definition.getBeanClassName() + "' mapperInterface");  
      }  

      // the mapper interface is the original class of the bean  
      // but, the actual class of the bean is MapperFactoryBean  
      //开始构造MapperFactoryBean类型的bean.  
      definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());  
      definition.setBeanClass(MapperFactoryBean.class);  

      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);  
      }  
    }  
  }  

  return beanDefinitions;  
}  

此时,虽然还没有完成介绍到扫描的过程,但是我们也应该理解了Spring中对于自动扫描的注册,声明MapperScannerConfigurer类型的bean目的是不需要我们对于每个接口都注册一个MapperFactoryBean类型的对应的bean,但是,不再配置文件中注册并不代表这个bean不存在,而是在扫描的过程中通过编码的方式动态注册。实现过程我们在上面的函数中可以看得非常清楚 。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {  
    Assert.notEmpty(basePackages, "At least one base package must be specified");  
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();  
    for (String basePackage : basePackages) {  
        //扫描basePackage路径下的java文件  
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);  
        for (BeanDefinition candidate : candidates) {  
            //解析scope属性  
            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) {  
                //如果是AnnotationBeanDefinition类型的bean需要检测下常用注解如:Primary,Lazy等。  
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);  
            }  
            //检测当前bean是否已经注册  
            if (checkCandidate(beanName, candidate)) {  
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);  
                //如果当前bean是用于生成代理的bean那么需要进一步处理  
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);  
                beanDefinitions.add(definitionHolder);  
                registerBeanDefinition(definitionHolder, this.registry);  
            }  
        }  
    }  
    return beanDefinitions;  
}  
public Set<BeanDefinition> findCandidateComponents(String basePackage) {  
    Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();  
    try {  
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +  
                resolveBasePackage(basePackage) + "/" + this.resourcePattern;  
        Resource[] resources = this.resourcePatternResolver.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 = this.metadataReaderFactory.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;  
}  

findCandidateComponents方法根据传入的包路径信息并结合类文件路径拼接成文件的绝对路径,同时完成了文件的扫描过程并且根据对应的文件生成了对应的bean,使用ScannedGenericBeanDefinition类型的bean承载信息,bean中值记录了resource和source信息。这里,我们更感兴趣的是isCandidateCompanent(metadataReader),此句代码用于判断当前扫描的文件是否符合要求,而我们之前注册的过滤器也是在此派上用场的。

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {  
    for (TypeFilter tf : this.excludeFilters) {  
        if (tf.match(metadataReader, this.metadataReaderFactory)) {  
            return false;  
        }  
    }  
    for (TypeFilter tf : this.includeFilters) {  
        if (tf.match(metadataReader, this.metadataReaderFactory)) {  
            AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();  
            if (!metadata.isAnnotated(Profile.class.getName())) {  
                return true;  
            }  
            AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);  
            return this.environment.acceptsProfiles(profile.getStringArray("value"));  
        }  
    }  
    return false;  
}  
  • 26
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
这段代码是一个Spring MVC项目的配置文件,用于配置一些关键的组件和功能。让我一一解释一下: 1. `<mvc:annotation-driven/>`:启用Spring MVC注解驱动,使得控制器注解生效。 2. `<context:component-scan base-package="com.dgy"/>`:指定需要扫描的包,将被注解标记的类作为组件进行扫描。 3. `<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">`:配置视图解析器,用于解析并定位JSP视图文件。 4. `<context:property-placeholder location="classpath:jdbc.properties"/>`:加载属性文件jdbc.properties的配置信息。 5. `<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">`:配置数据库连接池,使用阿里巴巴Druid连接池。 6. `<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">`:配置MyBatis的SqlSessionFactory,指定数据源。 7. `<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">`:配置MyBatis的Mapper接口扫描器,用于自动扫描并实例化Mapper接口的实现类。 8. `<tx:annotation-driven/>`:启用Spring事务注解驱动,使得注解标记的方法可以被事务管理。 9. `<aop:aspectj-autoproxy/>`:启用AOP自动代理,用于支持基于切面的编程。 10. `<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">`:配置事务管理器,使用Spring的DataSourceTransactionManager,将数据源注入事务管理器。 希望以上解释对您有所帮助!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值