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

mybatis / spring / #1458

27 Apr 2024 06:56PM UTC coverage: 89.305% (-0.4%) from 89.689%
#1458

push

github

web-flow
Merge pull request #939 from luozhenyu/property-sources

Add environment to ClassPathMapperScanner to get properties from PropertySources

256 of 312 branches covered (82.05%)

5 of 5 new or added lines in 2 files covered. (100.0%)

4 existing lines in 1 file now uncovered.

835 of 935 relevant lines covered (89.3%)

0.89 hits per line

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

84.35
/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java
1
/*
2
 * Copyright 2010-2024 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.Optional;
21
import java.util.Set;
22

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

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

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

65
  // Copy of FactoryBean#OBJECT_TYPE_ATTRIBUTE which was added in Spring 5.2
66
  static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
67

68
  private boolean addToConfig = true;
1✔
69

70
  private boolean lazyInitialization;
71

72
  private boolean printWarnLogIfNotFoundMappers = true;
1✔
73

74
  private SqlSessionFactory sqlSessionFactory;
75

76
  private SqlSessionTemplate sqlSessionTemplate;
77

78
  private String sqlSessionTemplateBeanName;
79

80
  private String sqlSessionFactoryBeanName;
81

82
  private Class<? extends Annotation> annotationClass;
83

84
  private Class<?> markerInterface;
85

86
  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
1✔
87

88
  private String defaultScope;
89

90
  public ClassPathMapperScanner(BeanDefinitionRegistry registry, Environment environment) {
91
    super(registry, false, environment);
1✔
92
    setIncludeAnnotationConfig(!AotDetector.useGeneratedArtifacts());
1!
93
    setPrintWarnLogIfNotFoundMappers(!NativeDetector.inNativeImage());
1!
94
  }
1✔
95

96
  /**
97
   * @deprecated Please use the {@link #ClassPathMapperScanner(BeanDefinitionRegistry, Environment)}.
98
   */
99
  @Deprecated(since = "3.0.4", forRemoval = true)
100
  public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
UNCOV
101
    super(registry, false);
×
UNCOV
102
    setIncludeAnnotationConfig(!AotDetector.useGeneratedArtifacts());
×
UNCOV
103
    setPrintWarnLogIfNotFoundMappers(!NativeDetector.inNativeImage());
×
UNCOV
104
  }
×
105

106
  public void setAddToConfig(boolean addToConfig) {
107
    this.addToConfig = addToConfig;
1✔
108
  }
1✔
109

110
  public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
111
    this.annotationClass = annotationClass;
1✔
112
  }
1✔
113

114
  /**
115
   * Set whether enable lazy initialization for mapper bean.
116
   * <p>
117
   * Default is {@code false}.
118
   * </p>
119
   *
120
   * @param lazyInitialization
121
   *          Set the @{code true} to enable
122
   *
123
   * @since 2.0.2
124
   */
125
  public void setLazyInitialization(boolean lazyInitialization) {
126
    this.lazyInitialization = lazyInitialization;
1✔
127
  }
1✔
128

129
  /**
130
   * Set whether print warning log if not found mappers that matches conditions.
131
   * <p>
132
   * Default is {@code true}. But {@code false} when running in native image.
133
   * </p>
134
   *
135
   * @param printWarnLogIfNotFoundMappers
136
   *          Set the @{code true} to print
137
   *
138
   * @since 3.0.1
139
   */
140
  public void setPrintWarnLogIfNotFoundMappers(boolean printWarnLogIfNotFoundMappers) {
141
    this.printWarnLogIfNotFoundMappers = printWarnLogIfNotFoundMappers;
1✔
142
  }
1✔
143

144
  public void setMarkerInterface(Class<?> markerInterface) {
145
    this.markerInterface = markerInterface;
1✔
146
  }
1✔
147

148
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
149
    this.sqlSessionFactory = sqlSessionFactory;
1✔
150
  }
1✔
151

152
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
153
    this.sqlSessionTemplate = sqlSessionTemplate;
1✔
154
  }
1✔
155

156
  public void setSqlSessionTemplateBeanName(String sqlSessionTemplateBeanName) {
157
    this.sqlSessionTemplateBeanName = sqlSessionTemplateBeanName;
1✔
158
  }
1✔
159

160
  public void setSqlSessionFactoryBeanName(String sqlSessionFactoryBeanName) {
161
    this.sqlSessionFactoryBeanName = sqlSessionFactoryBeanName;
1✔
162
  }
1✔
163

164
  /**
165
   * @deprecated Since 2.0.1, Please use the {@link #setMapperFactoryBeanClass(Class)}.
166
   */
167
  @Deprecated
168
  public void setMapperFactoryBean(MapperFactoryBean<?> mapperFactoryBean) {
169
    this.mapperFactoryBeanClass = mapperFactoryBean == null ? MapperFactoryBean.class : mapperFactoryBean.getClass();
×
170
  }
×
171

172
  /**
173
   * Set the {@code MapperFactoryBean} class.
174
   *
175
   * @param mapperFactoryBeanClass
176
   *          the {@code MapperFactoryBean} class
177
   *
178
   * @since 2.0.1
179
   */
180
  public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
181
    this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
1✔
182
  }
1✔
183

184
  /**
185
   * Set the default scope of scanned mappers.
186
   * <p>
187
   * Default is {@code null} (equiv to singleton).
188
   * </p>
189
   *
190
   * @param defaultScope
191
   *          the scope
192
   *
193
   * @since 2.0.6
194
   */
195
  public void setDefaultScope(String defaultScope) {
196
    this.defaultScope = defaultScope;
1✔
197
  }
1✔
198

199
  /**
200
   * Configures parent scanner to search for the right interfaces. It can search for all interfaces or just for those
201
   * that extends a markerInterface or/and those annotated with the annotationClass
202
   */
203
  public void registerFilters() {
204
    boolean acceptAllInterfaces = true;
1✔
205

206
    // if specified, use the given annotation and / or marker interface
207
    if (this.annotationClass != null) {
1✔
208
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
1✔
209
      acceptAllInterfaces = false;
1✔
210
    }
211

212
    // override AssignableTypeFilter to ignore matches on the actual marker interface
213
    if (this.markerInterface != null) {
1✔
214
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
1✔
215
        @Override
216
        protected boolean matchClassName(String className) {
217
          return false;
1✔
218
        }
219
      });
220
      acceptAllInterfaces = false;
1✔
221
    }
222

223
    if (acceptAllInterfaces) {
1✔
224
      // default include filter that accepts all classes
225
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
1✔
226
    }
227

228
    // exclude package-info.java
229
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
1✔
230
      String className = metadataReader.getClassMetadata().getClassName();
1✔
231
      return className.endsWith("package-info");
1✔
232
    });
233
  }
1✔
234

235
  /**
236
   * Calls the parent search that will search and register all the candidates. Then the registered objects are post
237
   * processed to set them as MapperFactoryBeans
238
   */
239
  @Override
240
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
241
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
1✔
242

243
    if (beanDefinitions.isEmpty()) {
1!
244
      if (printWarnLogIfNotFoundMappers) {
×
245
        LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
×
246
            + "' package. Please check your configuration.");
247
      }
248
    } else {
249
      processBeanDefinitions(beanDefinitions);
1✔
250
    }
251

252
    return beanDefinitions;
1✔
253
  }
254

255
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
256
    AbstractBeanDefinition definition;
257
    BeanDefinitionRegistry registry = getRegistry();
1✔
258
    for (BeanDefinitionHolder holder : beanDefinitions) {
1✔
259
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
1✔
260
      boolean scopedProxy = false;
1✔
261
      if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
1✔
262
        definition = (AbstractBeanDefinition) Optional
1✔
263
            .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
1✔
264
            .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
1✔
265
                "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
266
        scopedProxy = true;
1✔
267
      }
268
      String beanClassName = definition.getBeanClassName();
1✔
269
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
1✔
270
          + "' mapperInterface");
271

272
      // the mapper interface is the original class of the bean
273
      // but, the actual class of the bean is MapperFactoryBean
274
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
1✔
275
      try {
276
        Class<?> beanClass = Resources.classForName(beanClassName);
1✔
277
        // Attribute for MockitoPostProcessor
278
        // https://github.com/mybatis/spring-boot-starter/issues/475
279
        definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClass);
1✔
280
        // for spring-native
281
        definition.getPropertyValues().add("mapperInterface", beanClass);
1✔
282
      } catch (ClassNotFoundException ignore) {
×
283
        // ignore
284
      }
1✔
285

286
      definition.setBeanClass(this.mapperFactoryBeanClass);
1✔
287

288
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
1✔
289

290
      boolean explicitFactoryUsed = false;
1✔
291
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
1✔
292
        definition.getPropertyValues().add("sqlSessionFactory",
1✔
293
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
294
        explicitFactoryUsed = true;
1✔
295
      } else if (this.sqlSessionFactory != null) {
1!
296
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
×
297
        explicitFactoryUsed = true;
×
298
      }
299

300
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
1✔
301
        if (explicitFactoryUsed) {
1!
302
          LOGGER.warn(
×
303
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
×
304
        }
305
        definition.getPropertyValues().add("sqlSessionTemplate",
1✔
306
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
307
        explicitFactoryUsed = true;
1✔
308
      } else if (this.sqlSessionTemplate != null) {
1!
309
        if (explicitFactoryUsed) {
×
310
          LOGGER.warn(
×
311
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
×
312
        }
313
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
×
314
        explicitFactoryUsed = true;
×
315
      }
316

317
      if (!explicitFactoryUsed) {
1✔
318
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
1✔
319
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
1✔
320
      }
321

322
      definition.setLazyInit(lazyInitialization);
1✔
323

324
      if (scopedProxy) {
1✔
325
        continue;
1✔
326
      }
327

328
      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
1✔
329
        definition.setScope(defaultScope);
1✔
330
      }
331

332
      if (!definition.isSingleton()) {
1✔
333
        BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
1✔
334
        if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
1!
335
          registry.removeBeanDefinition(proxyHolder.getBeanName());
1✔
336
        }
337
        registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
1✔
338
      }
339

340
    }
1✔
341
  }
1✔
342

343
  /**
344
   * {@inheritDoc}
345
   */
346
  @Override
347
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
348
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
1!
349
  }
350

351
  /**
352
   * {@inheritDoc}
353
   */
354
  @Override
355
  protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
356
    if (super.checkCandidate(beanName, beanDefinition)) {
1✔
357
      return true;
1✔
358
    } else {
359
      LOGGER.warn(() -> "Skipping MapperFactoryBean with name '" + beanName + "' and '"
1✔
360
          + beanDefinition.getBeanClassName() + "' mapperInterface" + ". Bean already defined with the same name!");
1✔
361
      return false;
1✔
362
    }
363
  }
364

365
}
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

© 2025 Coveralls, Inc