View Javadoc
1   /*
2    * Copyright 2010-2022 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 static org.springframework.util.Assert.notNull;
19  
20  import java.lang.annotation.Annotation;
21  import java.util.Map;
22  import java.util.Optional;
23  
24  import org.apache.ibatis.session.SqlSessionFactory;
25  import org.mybatis.spring.SqlSessionTemplate;
26  import org.springframework.beans.PropertyValue;
27  import org.springframework.beans.PropertyValues;
28  import org.springframework.beans.factory.BeanNameAware;
29  import org.springframework.beans.factory.InitializingBean;
30  import org.springframework.beans.factory.config.BeanDefinition;
31  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
32  import org.springframework.beans.factory.config.PropertyResourceConfigurer;
33  import org.springframework.beans.factory.config.TypedStringValue;
34  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
35  import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
36  import org.springframework.beans.factory.support.BeanNameGenerator;
37  import org.springframework.beans.factory.support.DefaultListableBeanFactory;
38  import org.springframework.context.ApplicationContext;
39  import org.springframework.context.ApplicationContextAware;
40  import org.springframework.context.ConfigurableApplicationContext;
41  import org.springframework.core.env.Environment;
42  import org.springframework.util.StringUtils;
43  
44  /**
45   * BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and
46   * registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered;
47   * concrete classes will be ignored.
48   * <p>
49   * This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to
50   * {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the
51   * details.
52   * <p>
53   * The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons.
54   * <p>
55   * This class supports filtering the mappers created by either specifying a marker interface or an annotation. The
56   * {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property
57   * specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that
58   * match <em>either</em> criteria. By default, these two properties are null, so all interfaces in the given
59   * {@code basePackage} are added as mappers.
60   * <p>
61   * This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the
62   * proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory}
63   * in the application, however, autowiring cannot be used. In this case you must explicitly specify either an
64   * {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the <em>bean name</em> properties. Bean names
65   * are used rather than actual objects because Spring does not initialize property placeholders until after this class
66   * is processed.
67   * <p>
68   * Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers
69   * actual object creation until later in the startup process, after all placeholder substitution is completed. However,
70   * note that this configurer does support property placeholders of its <em>own</em> properties. The
71   * <code>basePackage</code> and bean name properties all support <code>${property}</code> style substitution.
72   * <p>
73   * Configuration sample:
74   *
75   * <pre class="code">
76   * {@code
77   *   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
78   *       <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
79   *       <!-- optional unless there are multiple session factories defined -->
80   *       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
81   *   </bean>
82   * }
83   * </pre>
84   *
85   * @author Hunter Presnall
86   * @author Eduardo Macarron
87   *
88   * @see MapperFactoryBean
89   * @see ClassPathMapperScanner
90   */
91  public class MapperScannerConfigurer
92      implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
93  
94    private String basePackage;
95  
96    private boolean addToConfig = true;
97  
98    private String lazyInitialization;
99  
100   private SqlSessionFactory sqlSessionFactory;
101 
102   private SqlSessionTemplate sqlSessionTemplate;
103 
104   private String sqlSessionFactoryBeanName;
105 
106   private String sqlSessionTemplateBeanName;
107 
108   private Class<? extends Annotation> annotationClass;
109 
110   private Class<?> markerInterface;
111 
112   private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;
113 
114   private ApplicationContext applicationContext;
115 
116   private String beanName;
117 
118   private boolean processPropertyPlaceHolders;
119 
120   private BeanNameGenerator nameGenerator;
121 
122   private String defaultScope;
123 
124   /**
125    * This property lets you set the base package for your mapper interface files.
126    * <p>
127    * You can set more than one package by using a semicolon or comma as a separator.
128    * <p>
129    * Mappers will be searched for recursively starting in the specified package(s).
130    *
131    * @param basePackage
132    *          base package name
133    */
134   public void setBasePackage(String basePackage) {
135     this.basePackage = basePackage;
136   }
137 
138   /**
139    * Same as {@code MapperFactoryBean#setAddToConfig(boolean)}.
140    *
141    * @param addToConfig
142    *          a flag that whether add mapper to MyBatis or not
143    *
144    * @see MapperFactoryBean#setAddToConfig(boolean)
145    */
146   public void setAddToConfig(boolean addToConfig) {
147     this.addToConfig = addToConfig;
148   }
149 
150   /**
151    * Set whether enable lazy initialization for mapper bean.
152    * <p>
153    * Default is {@code false}.
154    * </p>
155    *
156    * @param lazyInitialization
157    *          Set the @{code true} to enable
158    *
159    * @since 2.0.2
160    */
161   public void setLazyInitialization(String lazyInitialization) {
162     this.lazyInitialization = lazyInitialization;
163   }
164 
165   /**
166    * This property specifies the annotation that the scanner will search for.
167    * <p>
168    * The scanner will register all interfaces in the base package that also have the specified annotation.
169    * <p>
170    * Note this can be combined with markerInterface.
171    *
172    * @param annotationClass
173    *          annotation class
174    */
175   public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
176     this.annotationClass = annotationClass;
177   }
178 
179   /**
180    * This property specifies the parent that the scanner will search for.
181    * <p>
182    * The scanner will register all interfaces in the base package that also have the specified interface class as a
183    * parent.
184    * <p>
185    * Note this can be combined with annotationClass.
186    *
187    * @param superClass
188    *          parent class
189    */
190   public void setMarkerInterface(Class<?> superClass) {
191     this.markerInterface = superClass;
192   }
193 
194   /**
195    * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
196    * Usually this is only needed when you have more than one datasource.
197    * <p>
198    *
199    * @deprecated Use {@link #setSqlSessionTemplateBeanName(String)} instead
200    *
201    * @param sqlSessionTemplate
202    *          a template of SqlSession
203    */
204   @Deprecated
205   public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
206     this.sqlSessionTemplate = sqlSessionTemplate;
207   }
208 
209   /**
210    * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
211    * Usually this is only needed when you have more than one datasource.
212    * <p>
213    * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
214    * it is too early to build mybatis object instances.
215    *
216    * @since 1.1.0
217    *
218    * @param sqlSessionTemplateName
219    *          Bean name of the {@code SqlSessionTemplate}
220    */
221   public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) {
222     this.sqlSessionTemplateBeanName = sqlSessionTemplateName;
223   }
224 
225   /**
226    * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
227    * Usually this is only needed when you have more than one datasource.
228    * <p>
229    *
230    * @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead.
231    *
232    * @param sqlSessionFactory
233    *          a factory of SqlSession
234    */
235   @Deprecated
236   public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
237     this.sqlSessionFactory = sqlSessionFactory;
238   }
239 
240   /**
241    * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
242    * Usually this is only needed when you have more than one datasource.
243    * <p>
244    * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
245    * it is too early to build mybatis object instances.
246    *
247    * @since 1.1.0
248    *
249    * @param sqlSessionFactoryName
250    *          Bean name of the {@code SqlSessionFactory}
251    */
252   public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) {
253     this.sqlSessionFactoryBeanName = sqlSessionFactoryName;
254   }
255 
256   /**
257    * Specifies a flag that whether execute a property placeholder processing or not.
258    * <p>
259    * The default is {@literal false}. This means that a property placeholder processing does not execute.
260    *
261    * @since 1.1.1
262    *
263    * @param processPropertyPlaceHolders
264    *          a flag that whether execute a property placeholder processing or not
265    */
266   public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
267     this.processPropertyPlaceHolders = processPropertyPlaceHolders;
268   }
269 
270   /**
271    * The class of the {@link MapperFactoryBean} to return a mybatis proxy as spring bean.
272    *
273    * @param mapperFactoryBeanClass
274    *          The class of the MapperFactoryBean
275    *
276    * @since 2.0.1
277    */
278   public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
279     this.mapperFactoryBeanClass = mapperFactoryBeanClass;
280   }
281 
282   /**
283    * {@inheritDoc}
284    */
285   @Override
286   public void setApplicationContext(ApplicationContext applicationContext) {
287     this.applicationContext = applicationContext;
288   }
289 
290   /**
291    * {@inheritDoc}
292    */
293   @Override
294   public void setBeanName(String name) {
295     this.beanName = name;
296   }
297 
298   /**
299    * Gets beanNameGenerator to be used while running the scanner.
300    *
301    * @return the beanNameGenerator BeanNameGenerator that has been configured
302    *
303    * @since 1.2.0
304    */
305   public BeanNameGenerator getNameGenerator() {
306     return nameGenerator;
307   }
308 
309   /**
310    * Sets beanNameGenerator to be used while running the scanner.
311    *
312    * @param nameGenerator
313    *          the beanNameGenerator to set
314    *
315    * @since 1.2.0
316    */
317   public void setNameGenerator(BeanNameGenerator nameGenerator) {
318     this.nameGenerator = nameGenerator;
319   }
320 
321   /**
322    * Sets the default scope of scanned mappers.
323    * <p>
324    * Default is {@code null} (equiv to singleton).
325    * </p>
326    *
327    * @param defaultScope
328    *          the default scope
329    *
330    * @since 2.0.6
331    */
332   public void setDefaultScope(String defaultScope) {
333     this.defaultScope = defaultScope;
334   }
335 
336   /**
337    * {@inheritDoc}
338    */
339   @Override
340   public void afterPropertiesSet() throws Exception {
341     notNull(this.basePackage, "Property 'basePackage' is required");
342   }
343 
344   /**
345    * {@inheritDoc}
346    */
347   @Override
348   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
349     // left intentionally blank
350   }
351 
352   /**
353    * {@inheritDoc}
354    *
355    * @since 1.0.2
356    */
357   @Override
358   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
359     if (this.processPropertyPlaceHolders) {
360       processPropertyPlaceHolders();
361     }
362 
363     ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
364     scanner.setAddToConfig(this.addToConfig);
365     scanner.setAnnotationClass(this.annotationClass);
366     scanner.setMarkerInterface(this.markerInterface);
367     scanner.setSqlSessionFactory(this.sqlSessionFactory);
368     scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
369     scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
370     scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
371     scanner.setResourceLoader(this.applicationContext);
372     scanner.setBeanNameGenerator(this.nameGenerator);
373     scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
374     if (StringUtils.hasText(lazyInitialization)) {
375       scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
376     }
377     if (StringUtils.hasText(defaultScope)) {
378       scanner.setDefaultScope(defaultScope);
379     }
380     scanner.registerFilters();
381     scanner.scan(
382         StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
383   }
384 
385   /*
386    * BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
387    * PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
388    * fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
389    * definition. Then update the values.
390    */
391   private void processPropertyPlaceHolders() {
392     Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,
393         false, false);
394 
395     if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
396       BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
397           .getBeanDefinition(beanName);
398 
399       // PropertyResourceConfigurer does not expose any methods to explicitly perform
400       // property placeholder substitution. Instead, create a BeanFactory that just
401       // contains this mapper scanner and post process the factory.
402       DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
403       factory.registerBeanDefinition(beanName, mapperScannerBean);
404 
405       for (PropertyResourceConfigurer prc : prcs.values()) {
406         prc.postProcessBeanFactory(factory);
407       }
408 
409       PropertyValues values = mapperScannerBean.getPropertyValues();
410 
411       this.basePackage = getPropertyValue("basePackage", values);
412       this.sqlSessionFactoryBeanName = getPropertyValue("sqlSessionFactoryBeanName", values);
413       this.sqlSessionTemplateBeanName = getPropertyValue("sqlSessionTemplateBeanName", values);
414       this.lazyInitialization = getPropertyValue("lazyInitialization", values);
415       this.defaultScope = getPropertyValue("defaultScope", values);
416     }
417     this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
418     this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
419         .map(getEnvironment()::resolvePlaceholders).orElse(null);
420     this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
421         .map(getEnvironment()::resolvePlaceholders).orElse(null);
422     this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
423         .orElse(null);
424     this.defaultScope = Optional.ofNullable(this.defaultScope).map(getEnvironment()::resolvePlaceholders).orElse(null);
425   }
426 
427   private Environment getEnvironment() {
428     return this.applicationContext.getEnvironment();
429   }
430 
431   private String getPropertyValue(String propertyName, PropertyValues values) {
432     PropertyValue property = values.getPropertyValue(propertyName);
433 
434     if (property == null) {
435       return null;
436     }
437 
438     Object value = property.getValue();
439 
440     if (value == null) {
441       return null;
442     } else if (value instanceof String) {
443       return value.toString();
444     } else if (value instanceof TypedStringValue) {
445       return ((TypedStringValue) value).getValue();
446     } else {
447       return null;
448     }
449   }
450 
451 }