View Javadoc
1   /**
2    * Copyright 2010-2019 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    *    http://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   /**
123    * This property lets you set the base package for your mapper interface files.
124    * <p>
125    * You can set more than one package by using a semicolon or comma as a separator.
126    * <p>
127    * Mappers will be searched for recursively starting in the specified package(s).
128    *
129    * @param basePackage
130    *          base package name
131    */
132   public void setBasePackage(String basePackage) {
133     this.basePackage = basePackage;
134   }
135 
136   /**
137    * Same as {@code MapperFactoryBean#setAddToConfig(boolean)}.
138    *
139    * @param addToConfig
140    *          a flag that whether add mapper to MyBatis or not
141    * @see MapperFactoryBean#setAddToConfig(boolean)
142    */
143   public void setAddToConfig(boolean addToConfig) {
144     this.addToConfig = addToConfig;
145   }
146 
147   /**
148    * Set whether enable lazy initialization for mapper bean.
149    * <p>
150    * Default is {@code false}.
151    * </p>
152    *
153    * @param lazyInitialization
154    *          Set the @{code true} to enable
155    * @since 2.0.2
156    */
157   public void setLazyInitialization(String lazyInitialization) {
158     this.lazyInitialization = lazyInitialization;
159   }
160 
161   /**
162    * This property specifies the annotation that the scanner will search for.
163    * <p>
164    * The scanner will register all interfaces in the base package that also have the specified annotation.
165    * <p>
166    * Note this can be combined with markerInterface.
167    *
168    * @param annotationClass
169    *          annotation class
170    */
171   public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
172     this.annotationClass = annotationClass;
173   }
174 
175   /**
176    * This property specifies the parent that the scanner will search for.
177    * <p>
178    * The scanner will register all interfaces in the base package that also have the specified interface class as a
179    * parent.
180    * <p>
181    * Note this can be combined with annotationClass.
182    *
183    * @param superClass
184    *          parent class
185    */
186   public void setMarkerInterface(Class<?> superClass) {
187     this.markerInterface = superClass;
188   }
189 
190   /**
191    * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
192    * Usually this is only needed when you have more than one datasource.
193    * <p>
194    * 
195    * @deprecated Use {@link #setSqlSessionTemplateBeanName(String)} instead
196    *
197    * @param sqlSessionTemplate
198    *          a template of SqlSession
199    */
200   @Deprecated
201   public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
202     this.sqlSessionTemplate = sqlSessionTemplate;
203   }
204 
205   /**
206    * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
207    * Usually this is only needed when you have more than one datasource.
208    * <p>
209    * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
210    * it is too early to build mybatis object instances.
211    *
212    * @since 1.1.0
213    *
214    * @param sqlSessionTemplateName
215    *          Bean name of the {@code SqlSessionTemplate}
216    */
217   public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) {
218     this.sqlSessionTemplateBeanName = sqlSessionTemplateName;
219   }
220 
221   /**
222    * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
223    * Usually this is only needed when you have more than one datasource.
224    * <p>
225    * 
226    * @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead.
227    *
228    * @param sqlSessionFactory
229    *          a factory of SqlSession
230    */
231   @Deprecated
232   public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
233     this.sqlSessionFactory = sqlSessionFactory;
234   }
235 
236   /**
237    * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
238    * Usually this is only needed when you have more than one datasource.
239    * <p>
240    * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
241    * it is too early to build mybatis object instances.
242    *
243    * @since 1.1.0
244    *
245    * @param sqlSessionFactoryName
246    *          Bean name of the {@code SqlSessionFactory}
247    */
248   public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) {
249     this.sqlSessionFactoryBeanName = sqlSessionFactoryName;
250   }
251 
252   /**
253    * Specifies a flag that whether execute a property placeholder processing or not.
254    * <p>
255    * The default is {@literal false}. This means that a property placeholder processing does not execute.
256    * 
257    * @since 1.1.1
258    *
259    * @param processPropertyPlaceHolders
260    *          a flag that whether execute a property placeholder processing or not
261    */
262   public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
263     this.processPropertyPlaceHolders = processPropertyPlaceHolders;
264   }
265 
266   /**
267    * The class of the {@link MapperFactoryBean} to return a mybatis proxy as spring bean.
268    *
269    * @param mapperFactoryBeanClass
270    *          The class of the MapperFactoryBean
271    * @since 2.0.1
272    */
273   public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
274     this.mapperFactoryBeanClass = mapperFactoryBeanClass;
275   }
276 
277   /**
278    * {@inheritDoc}
279    */
280   @Override
281   public void setApplicationContext(ApplicationContext applicationContext) {
282     this.applicationContext = applicationContext;
283   }
284 
285   /**
286    * {@inheritDoc}
287    */
288   @Override
289   public void setBeanName(String name) {
290     this.beanName = name;
291   }
292 
293   /**
294    * Gets beanNameGenerator to be used while running the scanner.
295    *
296    * @return the beanNameGenerator BeanNameGenerator that has been configured
297    * @since 1.2.0
298    */
299   public BeanNameGenerator getNameGenerator() {
300     return nameGenerator;
301   }
302 
303   /**
304    * Sets beanNameGenerator to be used while running the scanner.
305    *
306    * @param nameGenerator
307    *          the beanNameGenerator to set
308    * @since 1.2.0
309    */
310   public void setNameGenerator(BeanNameGenerator nameGenerator) {
311     this.nameGenerator = nameGenerator;
312   }
313 
314   /**
315    * {@inheritDoc}
316    */
317   @Override
318   public void afterPropertiesSet() throws Exception {
319     notNull(this.basePackage, "Property 'basePackage' is required");
320   }
321 
322   /**
323    * {@inheritDoc}
324    */
325   @Override
326   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
327     // left intentionally blank
328   }
329 
330   /**
331    * {@inheritDoc}
332    * 
333    * @since 1.0.2
334    */
335   @Override
336   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
337     if (this.processPropertyPlaceHolders) {
338       processPropertyPlaceHolders();
339     }
340 
341     ClassPathMapperScannerner.html#ClassPathMapperScanner">ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
342     scanner.setAddToConfig(this.addToConfig);
343     scanner.setAnnotationClass(this.annotationClass);
344     scanner.setMarkerInterface(this.markerInterface);
345     scanner.setSqlSessionFactory(this.sqlSessionFactory);
346     scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
347     scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
348     scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
349     scanner.setResourceLoader(this.applicationContext);
350     scanner.setBeanNameGenerator(this.nameGenerator);
351     scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
352     if (StringUtils.hasText(lazyInitialization)) {
353       scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
354     }
355     scanner.registerFilters();
356     scanner.scan(
357         StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
358   }
359 
360   /*
361    * BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
362    * PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
363    * fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
364    * definition. Then update the values.
365    */
366   private void processPropertyPlaceHolders() {
367     Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
368 
369     if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
370       BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
371           .getBeanDefinition(beanName);
372 
373       // PropertyResourceConfigurer does not expose any methods to explicitly perform
374       // property placeholder substitution. Instead, create a BeanFactory that just
375       // contains this mapper scanner and post process the factory.
376       DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
377       factory.registerBeanDefinition(beanName, mapperScannerBean);
378 
379       for (PropertyResourceConfigurer prc : prcs.values()) {
380         prc.postProcessBeanFactory(factory);
381       }
382 
383       PropertyValues values = mapperScannerBean.getPropertyValues();
384 
385       this.basePackage = updatePropertyValue("basePackage", values);
386       this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
387       this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
388       this.lazyInitialization = updatePropertyValue("lazyInitialization", values);
389     }
390     this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
391     this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
392         .map(getEnvironment()::resolvePlaceholders).orElse(null);
393     this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
394         .map(getEnvironment()::resolvePlaceholders).orElse(null);
395     this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
396         .orElse(null);
397   }
398 
399   private Environment getEnvironment() {
400     return this.applicationContext.getEnvironment();
401   }
402 
403   private String updatePropertyValue(String propertyName, PropertyValues values) {
404     PropertyValue property = values.getPropertyValue(propertyName);
405 
406     if (property == null) {
407       return null;
408     }
409 
410     Object value = property.getValue();
411 
412     if (value == null) {
413       return null;
414     } else if (value instanceof String) {
415       return value.toString();
416     } else if (value instanceof TypedStringValue) {
417       return ((TypedStringValue) value).getValue();
418     } else {
419       return null;
420     }
421   }
422 
423 }