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