Coverage Report - org.mybatis.spring.mapper.MapperScannerConfigurer
 
Classes in this File Line Coverage Branch Coverage Complexity
MapperScannerConfigurer
84%
58/69
62%
10/16
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  
 }