解決Spring使用@MapperScan問題

問題場景

今天小編在MyBatis 整合Spring 的時候,使用到瞭@MapperScan,在啟動期出現瞭一個錯誤:

Invalid default: public abstract java.lang.Class org.mybatis.spring.annotation.MapperScan.factoryBean()

對於這個錯誤,小編也是倍感無奈,怎麼會出現這個錯誤呢,看一下我的依賴有沒有錯誤:

    compile(project(":spring-context"))
//    compile(project(":spring-instrument"))
    // https://mvnrepository.com/artifact/cglib/cglib
    compile group: 'cglib', name: 'cglib', version: '2.2.2'
    // https://mvnrepository.com/artifact/mysql/mysql-connector-java
    compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.21'
    // https://mvnrepository.com/artifact/org.mybatis/mybatis
    compile group: 'org.mybatis', name: 'mybatis', version: '3.5.2'
    // https://mvnrepository.com/artifact/org.mybatis/mybatis-spring
    compile group: 'org.mybatis', name: 'mybatis-spring', version: '2.0.2'

完全沒得毛病呀,他媽的,究竟是哪裡出現瞭問題呢,小編這時將SpringFramework 源碼從github 上clone 下來瞭,導入idea 開始尋找問題所在。。。

問題根源

經過不懈的努力,小編終於找到瞭問題根源,在AnnotationsScanner 類中的這個方法:

static Annotation[] getDeclaredAnnotations(AnnotatedElement source, boolean defensive) {
  boolean cached = false;
  Annotation[] annotations = declaredAnnotationCache.get(source);
  if (annotations != null) {
   cached = true;
  }
  else {
   // 使用@MapperScan 的時候會報錯,引入spring-jdbc 即可
   // 具體原因暫時還不清楚
   // Invalid default: public abstract java.lang.Class org.mybatis.spring.annotation.MapperScan.factoryBean()
   annotations = source.getDeclaredAnnotations();
   if (annotations.length != 0) {
    boolean allIgnored = true;
    for (int i = 0; i < annotations.length; i++) {
     Annotation annotation = annotations[i];
     if (isIgnorable(annotation.annotationType()) ||
       !AttributeMethods.forAnnotationType(annotation.annotationType()).isValid(annotation)) {
      annotations[i] = null;
     }
     else {
      allIgnored = false;
     }
    }
    annotations = (allIgnored ? NO_ANNOTATIONS : annotations);
    if (source instanceof Class || source instanceof Member) {
     declaredAnnotationCache.put(source, annotations);
     cached = true;
    }
   }
  }
  if (!defensive || annotations.length == 0 || !cached) {
   return annotations;
  }
  return annotations.clone();
 }

AnnotatedElement 對於這個類,小編也是不太清楚,這時也倍感無奈,這個方法主要做的事情就是將Appconfig 配置類中的註解掃描下來,那會不會是註解的問題呢,在@MapperScan 註解的註釋中發現小編的代碼與MyBatis-Spring 開發團隊提供的實例代碼一致,這就讓人想不明白瞭:

 * <pre class="code">
 * @Configuration
 * @MapperScan("org.mybatis.spring.sample.mapper")
 * public class AppConfig {
 *
 *   @Bean
 *   public DataSource dataSource() {
 *     return new EmbeddedDatabaseBuilder().addScript("schema.sql").build();
 *   }
 *
 *   @Bean
 *   public DataSourceTransactionManager transactionManager() {
 *     return new DataSourceTransactionManager(dataSource());
 *   }
 *
 *   @Bean
 *   public SqlSessionFactory sqlSessionFactory() throws Exception {
 *     SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
 *     sessionFactory.setDataSource(dataSource());
 *     return sessionFactory.getObject();
 *   }
 * }
 * </pre>

問題追溯

我這個時候被起瘋瞭都,從GitHub 上由把mybatis-spring 的源碼clone 瞭下來,導入idea,首先看一下mybatis-spring 的依賴:

 <dependencies>
    <!-- Compile dependencies -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>${mybatis.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.batch</groupId>
      <artifactId>spring-batch-infrastructure</artifactId>
      <version>${spring-batch.version}</version>
      <scope>provided</scope>
    </dependency>

從依賴中看到瞭什麼,tmd,<scope>provided</scope>,這個怎麼講,太無語瞭,其中它引入瞭spring-jdbc 的依賴。想象一下,@MapperScan 註解中是不是也使用到瞭spring-jdbc 的類,然後在使用@MapperScan 的時候沒有加入spring-jdbc 的依賴,導致註解在掃描期間的錯誤,沒毛病解釋的通,找一下@MapperScan 中是否使用到瞭spring-jdbc 的依賴。

問題解決

果不其然,下面用一張圖來看吧。

所以呢,在項目中加入spring-jdbc 完美解決。

SpringBoot  @MapperScan的註意事項

錯誤代碼如下:

[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error invoking SqlProvider method (tk.mybatis.mapper.provider.base.BaseSelectProvider.dynamicSQL). Cause: java.lang.InstantiationException: tk.mybatis.mapper.provider.base.BaseSelectProvider] with root cause

檢查@MapperScan導入的包是否為

import tk.mybatis.spring.annotation.MapperScan;

切記不要導成

import org.mybatis.spring.annotation.MapperScan

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: