• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / spring / 1842

09 May 2026 05:41PM UTC coverage: 90.335% (+0.09%) from 90.242%
1842

push

github

web-flow
Merge pull request #1239 from DragonFSKY/fix-aot-skip-runtime-mapper-scan-master

Skip mapper scanning in AOT generated runtime

321 of 382 branches covered (84.03%)

2 of 2 new or added lines in 1 file covered. (100.0%)

11 existing lines in 1 file now uncovered.

944 of 1045 relevant lines covered (90.33%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.05
/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java
1
/*
2
 * Copyright 2010-2026 the original author or authors.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *    https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package org.mybatis.spring.mapper;
17

18
import java.lang.annotation.Annotation;
19
import java.util.Arrays;
20
import java.util.List;
21
import java.util.Optional;
22
import java.util.Set;
23

24
import org.apache.ibatis.io.Resources;
25
import org.apache.ibatis.session.SqlSessionFactory;
26
import org.mybatis.logging.Logger;
27
import org.mybatis.logging.LoggerFactory;
28
import org.mybatis.spring.SqlSessionTemplate;
29
import org.springframework.aop.scope.ScopedProxyFactoryBean;
30
import org.springframework.aop.scope.ScopedProxyUtils;
31
import org.springframework.aot.AotDetector;
32
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
33
import org.springframework.beans.factory.config.BeanDefinition;
34
import org.springframework.beans.factory.config.BeanDefinitionHolder;
35
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
36
import org.springframework.beans.factory.config.ConstructorArgumentValues;
37
import org.springframework.beans.factory.config.RuntimeBeanReference;
38
import org.springframework.beans.factory.support.AbstractBeanDefinition;
39
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
40
import org.springframework.beans.factory.support.RootBeanDefinition;
41
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
42
import org.springframework.core.NativeDetector;
43
import org.springframework.core.env.Environment;
44
import org.springframework.core.type.filter.AnnotationTypeFilter;
45
import org.springframework.core.type.filter.AssignableTypeFilter;
46
import org.springframework.core.type.filter.TypeFilter;
47
import org.springframework.util.StringUtils;
48

49
/**
50
 * A {@link ClassPathBeanDefinitionScanner} that registers Mappers by {@code basePackage}, {@code annotationClass}, or
51
 * {@code markerInterface}. If an {@code annotationClass} and/or {@code markerInterface} is specified, only the
52
 * specified types will be searched (searching for all interfaces will be disabled).
53
 * <p>
54
 * This functionality was previously a private class of {@link MapperScannerConfigurer}, but was broken out in version
55
 * 1.2.0.
56
 *
57
 * @author Hunter Presnall
58
 * @author Eduardo Macarron
59
 *
60
 * @see MapperFactoryBean
61
 *
62
 * @since 1.2.0
63
 */
64
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
65

66
  private static final Logger LOGGER = LoggerFactory.getLogger(ClassPathMapperScanner.class);
1✔
67

68
  // Copy of FactoryBean#OBJECT_TYPE_ATTRIBUTE which was added in Spring 5.2
69
  static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
70

71
  private boolean addToConfig = true;
1✔
72

73
  private boolean lazyInitialization;
74

75
  private boolean printWarnLogIfNotFoundMappers = true;
1✔
76

77
  private SqlSessionFactory sqlSessionFactory;
78

79
  private SqlSessionTemplate sqlSessionTemplate;
80

81
  private String sqlSessionTemplateBeanName;
82

83
  private String sqlSessionFactoryBeanName;
84

85
  private Class<? extends Annotation> annotationClass;
86

87
  private Class<?> markerInterface;
88

89
  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
1✔
90

91
  private String defaultScope;
92
  private List<TypeFilter> excludeFilters;
93

94
  /**
95
   * Instantiates a new class path mapper scanner.
96
   *
97
   * @param registry
98
   *          the registry
99
   * @param environment
100
   *          the environment
101
   */
102
  public ClassPathMapperScanner(BeanDefinitionRegistry registry, Environment environment) {
103
    super(registry, false, environment);
1✔
104
    setIncludeAnnotationConfig(!AotDetector.useGeneratedArtifacts());
1!
105
    setPrintWarnLogIfNotFoundMappers(!NativeDetector.inNativeImage());
1!
106
  }
1✔
107

108
  /**
109
   * Instantiates a new class path mapper scanner.
110
   *
111
   * @param registry
112
   *          the registry
113
   *
114
   * @deprecated Please use the {@link #ClassPathMapperScanner(BeanDefinitionRegistry, Environment)}.
115
   */
116
  @Deprecated(since = "3.0.4", forRemoval = true)
117
  public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
118
    super(registry, false);
×
119
    setIncludeAnnotationConfig(!AotDetector.useGeneratedArtifacts());
×
120
    setPrintWarnLogIfNotFoundMappers(!NativeDetector.inNativeImage());
×
UNCOV
121
  }
×
122

123
  /**
124
   * Sets the adds the to config.
125
   *
126
   * @param addToConfig
127
   *          the new adds the to config
128
   */
129
  public void setAddToConfig(boolean addToConfig) {
130
    this.addToConfig = addToConfig;
1✔
131
  }
1✔
132

133
  /**
134
   * Sets the annotation class.
135
   *
136
   * @param annotationClass
137
   *          the new annotation class
138
   */
139
  public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
140
    this.annotationClass = annotationClass;
1✔
141
  }
1✔
142

143
  /**
144
   * Set whether enable lazy initialization for mapper bean.
145
   * <p>
146
   * Default is {@code false}.
147
   *
148
   * @param lazyInitialization
149
   *          Set the @{code true} to enable
150
   *
151
   * @since 2.0.2
152
   */
153
  public void setLazyInitialization(boolean lazyInitialization) {
154
    this.lazyInitialization = lazyInitialization;
1✔
155
  }
1✔
156

157
  /**
158
   * Set whether print warning log if not found mappers that matches conditions.
159
   * <p>
160
   * Default is {@code true}. But {@code false} when running in native image.
161
   *
162
   * @param printWarnLogIfNotFoundMappers
163
   *          Set the @{code true} to print
164
   *
165
   * @since 3.0.1
166
   */
167
  public void setPrintWarnLogIfNotFoundMappers(boolean printWarnLogIfNotFoundMappers) {
168
    this.printWarnLogIfNotFoundMappers = printWarnLogIfNotFoundMappers;
1✔
169
  }
1✔
170

171
  /**
172
   * Sets the marker interface.
173
   *
174
   * @param markerInterface
175
   *          the new marker interface
176
   */
177
  public void setMarkerInterface(Class<?> markerInterface) {
178
    this.markerInterface = markerInterface;
1✔
179
  }
1✔
180

181
  /**
182
   * Sets the exclude filters.
183
   *
184
   * @param excludeFilters
185
   *          the new exclude filters
186
   */
187
  public void setExcludeFilters(List<TypeFilter> excludeFilters) {
188
    this.excludeFilters = excludeFilters;
1✔
189
  }
1✔
190

191
  /**
192
   * Sets the sql session factory.
193
   *
194
   * @param sqlSessionFactory
195
   *          the new sql session factory
196
   */
197
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
198
    this.sqlSessionFactory = sqlSessionFactory;
1✔
199
  }
1✔
200

201
  /**
202
   * Sets the sql session template.
203
   *
204
   * @param sqlSessionTemplate
205
   *          the new sql session template
206
   */
207
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
208
    this.sqlSessionTemplate = sqlSessionTemplate;
1✔
209
  }
1✔
210

211
  /**
212
   * Sets the sql session template bean name.
213
   *
214
   * @param sqlSessionTemplateBeanName
215
   *          the new sql session template bean name
216
   */
217
  public void setSqlSessionTemplateBeanName(String sqlSessionTemplateBeanName) {
218
    this.sqlSessionTemplateBeanName = sqlSessionTemplateBeanName;
1✔
219
  }
1✔
220

221
  /**
222
   * Sets the sql session factory bean name.
223
   *
224
   * @param sqlSessionFactoryBeanName
225
   *          the new sql session factory bean name
226
   */
227
  public void setSqlSessionFactoryBeanName(String sqlSessionFactoryBeanName) {
228
    this.sqlSessionFactoryBeanName = sqlSessionFactoryBeanName;
1✔
229
  }
1✔
230

231
  /**
232
   * Sets the mapper factory bean.
233
   *
234
   * @param mapperFactoryBean
235
   *          the new mapper factory bean
236
   *
237
   * @deprecated Since 2.0.1, Please use the {@link #setMapperFactoryBeanClass(Class)}.
238
   */
239
  @Deprecated(since = "2.0.1", forRemoval = true)
240
  public void setMapperFactoryBean(MapperFactoryBean<?> mapperFactoryBean) {
241
    this.mapperFactoryBeanClass = mapperFactoryBean == null ? MapperFactoryBean.class : mapperFactoryBean.getClass();
×
UNCOV
242
  }
×
243

244
  /**
245
   * Set the {@code MapperFactoryBean} class.
246
   *
247
   * @param mapperFactoryBeanClass
248
   *          the {@code MapperFactoryBean} class
249
   *
250
   * @since 2.0.1
251
   */
252
  public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
253
    this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
1✔
254
  }
1✔
255

256
  /**
257
   * Set the default scope of scanned mappers.
258
   * <p>
259
   * Default is {@code null} (equiv to singleton).
260
   *
261
   * @param defaultScope
262
   *          the scope
263
   *
264
   * @since 2.0.6
265
   */
266
  public void setDefaultScope(String defaultScope) {
267
    this.defaultScope = defaultScope;
1✔
268
  }
1✔
269

270
  /**
271
   * Configures parent scanner to search for the right interfaces. It can search for all interfaces or just for those
272
   * that extends a markerInterface or/and those annotated with the annotationClass
273
   */
274
  public void registerFilters() {
275
    var acceptAllInterfaces = true;
1✔
276

277
    // if specified, use the given annotation and / or marker interface
278
    if (this.annotationClass != null) {
1✔
279
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
1✔
280
      acceptAllInterfaces = false;
1✔
281
    }
282

283
    // override AssignableTypeFilter to ignore matches on the actual marker interface
284
    if (this.markerInterface != null) {
1✔
285
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
1✔
286
        @Override
287
        protected boolean matchClassName(String className) {
288
          return false;
1✔
289
        }
290
      });
291
      acceptAllInterfaces = false;
1✔
292
    }
293

294
    if (acceptAllInterfaces) {
1✔
295
      // default include filter that accepts all classes
296
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
1✔
297
    }
298

299
    // exclude package-info.java
300
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
1✔
301
      var className = metadataReader.getClassMetadata().getClassName();
1✔
302
      return className.endsWith("package-info");
1✔
303
    });
304

305
    // exclude types declared by MapperScan.excludeFilters
306
    if (excludeFilters != null && excludeFilters.size() > 0) {
1!
307
      for (TypeFilter excludeFilter : excludeFilters) {
1✔
308
        addExcludeFilter(excludeFilter);
1✔
309
      }
1✔
310
    }
311
  }
1✔
312

313
  /**
314
   * Calls the parent search that will search and register all the candidates. Then the registered objects are post
315
   * processed to set them as MapperFactoryBeans
316
   */
317
  @Override
318
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
319
    var beanDefinitions = super.doScan(basePackages);
1✔
320

321
    if (beanDefinitions.isEmpty()) {
1!
322
      if (printWarnLogIfNotFoundMappers) {
×
UNCOV
323
        LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
×
324
            + "' package. Please check your configuration.");
325
      }
326
    } else {
327
      processBeanDefinitions(beanDefinitions);
1✔
328
    }
329

330
    return beanDefinitions;
1✔
331
  }
332

333
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
334
    AbstractBeanDefinition definition;
335
    var registry = getRegistry();
1✔
336
    for (BeanDefinitionHolder holder : beanDefinitions) {
1✔
337
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
1✔
338
      var scopedProxy = false;
1✔
339
      if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
1✔
340
        definition = (AbstractBeanDefinition) Optional
1✔
341
            .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
1✔
342
            .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
1✔
343
                "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
344
        scopedProxy = true;
1✔
345
      }
346
      var beanClassName = definition.getBeanClassName();
1✔
347
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
1✔
348
          + "' mapperInterface");
349

350
      // the mapper interface is the original class of the bean
351
      // but, the actual class of the bean is MapperFactoryBean
352
      var constructorArgument = new ConstructorArgumentValues.ValueHolder(beanClassName); // issue #59
1✔
353
      definition.getConstructorArgumentValues().addGenericArgumentValue(constructorArgument);
1✔
354
      try {
355
        Class<?> beanClass = Resources.classForName(beanClassName);
1✔
356
        if (shouldUseClassConstructorArgument(this.mapperFactoryBeanClass)) {
1✔
357
          constructorArgument.setValue(beanClass);
1✔
358
        }
359
        // Attribute for MockitoPostProcessor
360
        // https://github.com/mybatis/spring-boot-starter/issues/475
361
        definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClass);
1✔
362
        // for spring-native
363
        definition.getPropertyValues().add("mapperInterface", beanClass);
1✔
UNCOV
364
      } catch (ClassNotFoundException ignore) {
×
365
        // ignore
366
      }
1✔
367

368
      definition.setBeanClass(this.mapperFactoryBeanClass);
1✔
369

370
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
1✔
371

372
      var explicitFactoryUsed = false;
1✔
373
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
1✔
374
        definition.getPropertyValues().add("sqlSessionFactory",
1✔
375
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
376
        explicitFactoryUsed = true;
1✔
377
      } else if (this.sqlSessionFactory != null) {
1!
UNCOV
378
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
×
379
        explicitFactoryUsed = true;
×
380
      }
381

382
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
1✔
383
        if (explicitFactoryUsed) {
1!
UNCOV
384
          LOGGER.warn(
×
UNCOV
385
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
×
386
        }
387
        definition.getPropertyValues().add("sqlSessionTemplate",
1✔
388
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
389
        explicitFactoryUsed = true;
1✔
390
      } else if (this.sqlSessionTemplate != null) {
1!
391
        if (explicitFactoryUsed) {
×
UNCOV
392
          LOGGER.warn(
×
UNCOV
393
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
×
394
        }
UNCOV
395
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
×
UNCOV
396
        explicitFactoryUsed = true;
×
397
      }
398

399
      if (!explicitFactoryUsed) {
1✔
400
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
1✔
401
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
1✔
402
      }
403

404
      definition.setLazyInit(lazyInitialization);
1✔
405

406
      if (scopedProxy) {
1✔
407
        continue;
1✔
408
      }
409

410
      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
1✔
411
        definition.setScope(defaultScope);
1✔
412
      }
413

414
      if (!definition.isSingleton()) {
1✔
415
        var proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
1✔
416
        if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
1!
417
          registry.removeBeanDefinition(proxyHolder.getBeanName());
1✔
418
        }
419
        registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
1✔
420
      }
421

422
    }
1✔
423
  }
1✔
424

425
  @Override
426
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
427
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
1!
428
  }
429

430
  private boolean shouldUseClassConstructorArgument(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
431
    return hasSingleArgumentConstructorAccepting(mapperFactoryBeanClass, Class.class)
1✔
432
        && !hasSingleArgumentConstructorAccepting(mapperFactoryBeanClass, String.class);
1✔
433
  }
434

435
  private boolean hasSingleArgumentConstructorAccepting(Class<? extends MapperFactoryBean> mapperFactoryBeanClass,
436
      Class<?> argumentType) {
437
    return Arrays.stream(mapperFactoryBeanClass.getDeclaredConstructors())
1✔
438
        .anyMatch(constructor -> constructor.getParameterCount() == 1
1✔
439
            && constructor.getParameterTypes()[0].isAssignableFrom(argumentType));
1✔
440
  }
441

442
  @Override
443
  protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
444
    if (super.checkCandidate(beanName, beanDefinition)) {
1✔
445
      return true;
1✔
446
    }
447
    LOGGER.warn(() -> "Skipping MapperFactoryBean with name '" + beanName + "' and '"
1✔
448
        + beanDefinition.getBeanClassName() + "' mapperInterface" + ". Bean already defined with the same name!");
1✔
449
    return false;
1✔
450
  }
451

452
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc