View Javadoc
1   /*
2    * Copyright 2010-2023 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.type.filter.AnnotationTypeFilter;
42  import org.springframework.core.type.filter.AssignableTypeFilter;
43  import org.springframework.util.StringUtils;
44  
45  /**
46   * A {@link ClassPathBeanDefinitionScanner} that registers Mappers by {@code basePackage}, {@code annotationClass}, or
47   * {@code markerInterface}. If an {@code annotationClass} and/or {@code markerInterface} is specified, only the
48   * specified types will be searched (searching for all interfaces will be disabled).
49   * <p>
50   * This functionality was previously a private class of {@link MapperScannerConfigurer}, but was broken out in version
51   * 1.2.0.
52   *
53   * @author Hunter Presnall
54   * @author Eduardo Macarron
55   *
56   * @see MapperFactoryBean
57   *
58   * @since 1.2.0
59   */
60  public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
61  
62    private static final Logger LOGGER = LoggerFactory.getLogger(ClassPathMapperScanner.class);
63  
64    // Copy of FactoryBean#OBJECT_TYPE_ATTRIBUTE which was added in Spring 5.2
65    static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
66  
67    private boolean addToConfig = true;
68  
69    private boolean lazyInitialization;
70  
71    private boolean printWarnLogIfNotFoundMappers = true;
72  
73    private SqlSessionFactory sqlSessionFactory;
74  
75    private SqlSessionTemplate sqlSessionTemplate;
76  
77    private String sqlSessionTemplateBeanName;
78  
79    private String sqlSessionFactoryBeanName;
80  
81    private Class<? extends Annotation> annotationClass;
82  
83    private Class<?> markerInterface;
84  
85    private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
86  
87    private String defaultScope;
88  
89    public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
90      super(registry, false);
91      setIncludeAnnotationConfig(!AotDetector.useGeneratedArtifacts());
92      setPrintWarnLogIfNotFoundMappers(!NativeDetector.inNativeImage());
93    }
94  
95    public void setAddToConfig(boolean addToConfig) {
96      this.addToConfig = addToConfig;
97    }
98  
99    public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
100     this.annotationClass = annotationClass;
101   }
102 
103   /**
104    * Set whether enable lazy initialization for mapper bean.
105    * <p>
106    * Default is {@code false}.
107    * </p>
108    *
109    * @param lazyInitialization
110    *          Set the @{code true} to enable
111    *
112    * @since 2.0.2
113    */
114   public void setLazyInitialization(boolean lazyInitialization) {
115     this.lazyInitialization = lazyInitialization;
116   }
117 
118   /**
119    * Set whether print warning log if not found mappers that matches conditions.
120    * <p>
121    * Default is {@code true}. But {@code false} when running in native image.
122    * </p>
123    *
124    * @param printWarnLogIfNotFoundMappers
125    *          Set the @{code true} to print
126    *
127    * @since 3.0.1
128    */
129   public void setPrintWarnLogIfNotFoundMappers(boolean printWarnLogIfNotFoundMappers) {
130     this.printWarnLogIfNotFoundMappers = printWarnLogIfNotFoundMappers;
131   }
132 
133   public void setMarkerInterface(Class<?> markerInterface) {
134     this.markerInterface = markerInterface;
135   }
136 
137   public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
138     this.sqlSessionFactory = sqlSessionFactory;
139   }
140 
141   public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
142     this.sqlSessionTemplate = sqlSessionTemplate;
143   }
144 
145   public void setSqlSessionTemplateBeanName(String sqlSessionTemplateBeanName) {
146     this.sqlSessionTemplateBeanName = sqlSessionTemplateBeanName;
147   }
148 
149   public void setSqlSessionFactoryBeanName(String sqlSessionFactoryBeanName) {
150     this.sqlSessionFactoryBeanName = sqlSessionFactoryBeanName;
151   }
152 
153   /**
154    * @deprecated Since 2.0.1, Please use the {@link #setMapperFactoryBeanClass(Class)}.
155    */
156   @Deprecated
157   public void setMapperFactoryBean(MapperFactoryBean<?> mapperFactoryBean) {
158     this.mapperFactoryBeanClass = mapperFactoryBean == null ? MapperFactoryBean.class : mapperFactoryBean.getClass();
159   }
160 
161   /**
162    * Set the {@code MapperFactoryBean} class.
163    *
164    * @param mapperFactoryBeanClass
165    *          the {@code MapperFactoryBean} class
166    *
167    * @since 2.0.1
168    */
169   public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
170     this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
171   }
172 
173   /**
174    * Set the default scope of scanned mappers.
175    * <p>
176    * Default is {@code null} (equiv to singleton).
177    * </p>
178    *
179    * @param defaultScope
180    *          the scope
181    *
182    * @since 2.0.6
183    */
184   public void setDefaultScope(String defaultScope) {
185     this.defaultScope = defaultScope;
186   }
187 
188   /**
189    * Configures parent scanner to search for the right interfaces. It can search for all interfaces or just for those
190    * that extends a markerInterface or/and those annotated with the annotationClass
191    */
192   public void registerFilters() {
193     boolean acceptAllInterfaces = true;
194 
195     // if specified, use the given annotation and / or marker interface
196     if (this.annotationClass != null) {
197       addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
198       acceptAllInterfaces = false;
199     }
200 
201     // override AssignableTypeFilter to ignore matches on the actual marker interface
202     if (this.markerInterface != null) {
203       addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
204         @Override
205         protected boolean matchClassName(String className) {
206           return false;
207         }
208       });
209       acceptAllInterfaces = false;
210     }
211 
212     if (acceptAllInterfaces) {
213       // default include filter that accepts all classes
214       addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
215     }
216 
217     // exclude package-info.java
218     addExcludeFilter((metadataReader, metadataReaderFactory) -> {
219       String className = metadataReader.getClassMetadata().getClassName();
220       return className.endsWith("package-info");
221     });
222   }
223 
224   /**
225    * Calls the parent search that will search and register all the candidates. Then the registered objects are post
226    * processed to set them as MapperFactoryBeans
227    */
228   @Override
229   public Set<BeanDefinitionHolder> doScan(String... basePackages) {
230     Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
231 
232     if (beanDefinitions.isEmpty()) {
233       if (printWarnLogIfNotFoundMappers) {
234         LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
235             + "' package. Please check your configuration.");
236       }
237     } else {
238       processBeanDefinitions(beanDefinitions);
239     }
240 
241     return beanDefinitions;
242   }
243 
244   private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
245     AbstractBeanDefinition definition;
246     BeanDefinitionRegistry registry = getRegistry();
247     for (BeanDefinitionHolder holder : beanDefinitions) {
248       definition = (AbstractBeanDefinition) holder.getBeanDefinition();
249       boolean scopedProxy = false;
250       if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
251         definition = (AbstractBeanDefinition) Optional
252             .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
253             .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
254                 "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
255         scopedProxy = true;
256       }
257       String beanClassName = definition.getBeanClassName();
258       LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
259           + "' mapperInterface");
260 
261       // the mapper interface is the original class of the bean
262       // but, the actual class of the bean is MapperFactoryBean
263       definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
264       try {
265         Class<?> beanClass = Resources.classForName(beanClassName);
266         // Attribute for MockitoPostProcessor
267         // https://github.com/mybatis/spring-boot-starter/issues/475
268         definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClass);
269         // for spring-native
270         definition.getPropertyValues().add("mapperInterface", beanClass);
271       } catch (ClassNotFoundException ignore) {
272         // ignore
273       }
274 
275       definition.setBeanClass(this.mapperFactoryBeanClass);
276 
277       definition.getPropertyValues().add("addToConfig", this.addToConfig);
278 
279       boolean explicitFactoryUsed = false;
280       if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
281         definition.getPropertyValues().add("sqlSessionFactory",
282             new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
283         explicitFactoryUsed = true;
284       } else if (this.sqlSessionFactory != null) {
285         definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
286         explicitFactoryUsed = true;
287       }
288 
289       if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
290         if (explicitFactoryUsed) {
291           LOGGER.warn(
292               () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
293         }
294         definition.getPropertyValues().add("sqlSessionTemplate",
295             new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
296         explicitFactoryUsed = true;
297       } else if (this.sqlSessionTemplate != null) {
298         if (explicitFactoryUsed) {
299           LOGGER.warn(
300               () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
301         }
302         definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
303         explicitFactoryUsed = true;
304       }
305 
306       if (!explicitFactoryUsed) {
307         LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
308         definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
309       }
310 
311       definition.setLazyInit(lazyInitialization);
312 
313       if (scopedProxy) {
314         continue;
315       }
316 
317       if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
318         definition.setScope(defaultScope);
319       }
320 
321       if (!definition.isSingleton()) {
322         BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
323         if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
324           registry.removeBeanDefinition(proxyHolder.getBeanName());
325         }
326         registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
327       }
328 
329     }
330   }
331 
332   /**
333    * {@inheritDoc}
334    */
335   @Override
336   protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
337     return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
338   }
339 
340   /**
341    * {@inheritDoc}
342    */
343   @Override
344   protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
345     if (super.checkCandidate(beanName, beanDefinition)) {
346       return true;
347     } else {
348       LOGGER.warn(() -> "Skipping MapperFactoryBean with name '" + beanName + "' and '"
349           + beanDefinition.getBeanClassName() + "' mapperInterface" + ". Bean already defined with the same name!");
350       return false;
351     }
352   }
353 
354 }