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 }