MapperScannerConfigurer.java

  1. /*
  2.  * Copyright 2010-2024 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. import static org.springframework.util.Assert.notNull;

  18. import java.lang.annotation.Annotation;
  19. import java.util.ArrayList;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.Optional;
  23. import java.util.regex.Pattern;

  24. import org.apache.ibatis.session.SqlSessionFactory;
  25. import org.mybatis.spring.SqlSessionTemplate;
  26. import org.springframework.beans.BeanUtils;
  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.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.core.env.Environment;
  41. import org.springframework.core.type.filter.AnnotationTypeFilter;
  42. import org.springframework.core.type.filter.AspectJTypeFilter;
  43. import org.springframework.core.type.filter.AssignableTypeFilter;
  44. import org.springframework.core.type.filter.RegexPatternTypeFilter;
  45. import org.springframework.core.type.filter.TypeFilter;
  46. import org.springframework.lang.Nullable;
  47. import org.springframework.util.ClassUtils;
  48. import org.springframework.util.StringUtils;

  49. /**
  50.  * BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and
  51.  * registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered;
  52.  * concrete classes will be ignored.
  53.  * <p>
  54.  * This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to
  55.  * {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the
  56.  * details.
  57.  * <p>
  58.  * The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons.
  59.  * <p>
  60.  * This class supports filtering the mappers created by either specifying a marker interface or an annotation. The
  61.  * {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property
  62.  * specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that
  63.  * match <em>either</em> criteria. By default, these two properties are null, so all interfaces in the given
  64.  * {@code basePackage} are added as mappers.
  65.  * <p>
  66.  * This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the
  67.  * proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory}
  68.  * in the application, however, autowiring cannot be used. In this case you must explicitly specify either an
  69.  * {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the <em>bean name</em> properties. Bean names
  70.  * are used rather than actual objects because Spring does not initialize property placeholders until after this class
  71.  * is processed.
  72.  * <p>
  73.  * Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers
  74.  * actual object creation until later in the startup process, after all placeholder substitution is completed. However,
  75.  * note that this configurer does support property placeholders of its <em>own</em> properties. The
  76.  * <code>basePackage</code> and bean name properties all support <code>${property}</code> style substitution.
  77.  * <p>
  78.  * Configuration sample:
  79.  *
  80.  * <pre class="code">
  81.  * {@code
  82.  *   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  83.  *       <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
  84.  *       <!-- optional unless there are multiple session factories defined -->
  85.  *       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
  86.  *   </bean>
  87.  * }
  88.  * </pre>
  89.  *
  90.  * @author Hunter Presnall
  91.  * @author Eduardo Macarron
  92.  *
  93.  * @see MapperFactoryBean
  94.  * @see ClassPathMapperScanner
  95.  */
  96. public class MapperScannerConfigurer
  97.     implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

  98.   private String basePackage;

  99.   private boolean addToConfig = true;

  100.   private String lazyInitialization;

  101.   private SqlSessionFactory sqlSessionFactory;

  102.   private SqlSessionTemplate sqlSessionTemplate;

  103.   private String sqlSessionFactoryBeanName;

  104.   private String sqlSessionTemplateBeanName;

  105.   private Class<? extends Annotation> annotationClass;

  106.   private Class<?> markerInterface;

  107.   private List<TypeFilter> excludeFilters;

  108.   private List<Map<String, String>> rawExcludeFilters;

  109.   private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;

  110.   private ApplicationContext applicationContext;

  111.   private String beanName;

  112.   private boolean processPropertyPlaceHolders;

  113.   private BeanNameGenerator nameGenerator;

  114.   private String defaultScope;

  115.   /**
  116.    * This property lets you set the base package for your mapper interface files.
  117.    * <p>
  118.    * You can set more than one package by using a semicolon or comma as a separator.
  119.    * <p>
  120.    * Mappers will be searched for recursively starting in the specified package(s).
  121.    *
  122.    * @param basePackage
  123.    *          base package name
  124.    */
  125.   public void setBasePackage(String basePackage) {
  126.     this.basePackage = basePackage;
  127.   }

  128.   /**
  129.    * Same as {@code MapperFactoryBean#setAddToConfig(boolean)}.
  130.    *
  131.    * @param addToConfig
  132.    *          a flag that whether add mapper to MyBatis or not
  133.    *
  134.    * @see MapperFactoryBean#setAddToConfig(boolean)
  135.    */
  136.   public void setAddToConfig(boolean addToConfig) {
  137.     this.addToConfig = addToConfig;
  138.   }

  139.   /**
  140.    * Set whether enable lazy initialization for mapper bean.
  141.    * <p>
  142.    * Default is {@code false}.
  143.    * </p>
  144.    *
  145.    * @param lazyInitialization
  146.    *          Set the @{code true} to enable
  147.    *
  148.    * @since 2.0.2
  149.    */
  150.   public void setLazyInitialization(String lazyInitialization) {
  151.     this.lazyInitialization = lazyInitialization;
  152.   }

  153.   /**
  154.    * This property specifies the annotation that the scanner will search for.
  155.    * <p>
  156.    * The scanner will register all interfaces in the base package that also have the specified annotation.
  157.    * <p>
  158.    * Note this can be combined with markerInterface.
  159.    *
  160.    * @param annotationClass
  161.    *          annotation class
  162.    */
  163.   public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
  164.     this.annotationClass = annotationClass;
  165.   }

  166.   /**
  167.    * This property specifies the parent that the scanner will search for.
  168.    * <p>
  169.    * The scanner will register all interfaces in the base package that also have the specified interface class as a
  170.    * parent.
  171.    * <p>
  172.    * Note this can be combined with annotationClass.
  173.    *
  174.    * @param superClass
  175.    *          parent class
  176.    */
  177.   public void setMarkerInterface(Class<?> superClass) {
  178.     this.markerInterface = superClass;
  179.   }

  180.   /**
  181.    * Specifies which types are not eligible for the mapper scanner.
  182.    * <p>
  183.    * The scanner will exclude types that define with excludeFilters.
  184.    *
  185.    * @since 3.0.3
  186.    *
  187.    * @param excludeFilters
  188.    *          list of TypeFilter
  189.    */
  190.   public void setExcludeFilters(List<TypeFilter> excludeFilters) {
  191.     this.excludeFilters = excludeFilters;
  192.   }

  193.   /**
  194.    * In order to support process PropertyPlaceHolders.
  195.    * <p>
  196.    * After parsed, it will be added to excludeFilters.
  197.    *
  198.    * @since 3.0.3
  199.    *
  200.    * @param rawExcludeFilters
  201.    *          list of rawExcludeFilter
  202.    */
  203.   public void setRawExcludeFilters(List<Map<String, String>> rawExcludeFilters) {
  204.     this.rawExcludeFilters = rawExcludeFilters;
  205.   }

  206.   /**
  207.    * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
  208.    * Usually this is only needed when you have more than one datasource.
  209.    * <p>
  210.    *
  211.    * @deprecated Use {@link #setSqlSessionTemplateBeanName(String)} instead
  212.    *
  213.    * @param sqlSessionTemplate
  214.    *          a template of SqlSession
  215.    */
  216.   @Deprecated
  217.   public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
  218.     this.sqlSessionTemplate = sqlSessionTemplate;
  219.   }

  220.   /**
  221.    * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
  222.    * Usually this is only needed when you have more than one datasource.
  223.    * <p>
  224.    * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
  225.    * it is too early to build mybatis object instances.
  226.    *
  227.    * @since 1.1.0
  228.    *
  229.    * @param sqlSessionTemplateName
  230.    *          Bean name of the {@code SqlSessionTemplate}
  231.    */
  232.   public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) {
  233.     this.sqlSessionTemplateBeanName = sqlSessionTemplateName;
  234.   }

  235.   /**
  236.    * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
  237.    * Usually this is only needed when you have more than one datasource.
  238.    * <p>
  239.    *
  240.    * @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead.
  241.    *
  242.    * @param sqlSessionFactory
  243.    *          a factory of SqlSession
  244.    */
  245.   @Deprecated
  246.   public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
  247.     this.sqlSessionFactory = sqlSessionFactory;
  248.   }

  249.   /**
  250.    * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
  251.    * Usually this is only needed when you have more than one datasource.
  252.    * <p>
  253.    * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
  254.    * it is too early to build mybatis object instances.
  255.    *
  256.    * @since 1.1.0
  257.    *
  258.    * @param sqlSessionFactoryName
  259.    *          Bean name of the {@code SqlSessionFactory}
  260.    */
  261.   public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) {
  262.     this.sqlSessionFactoryBeanName = sqlSessionFactoryName;
  263.   }

  264.   /**
  265.    * Specifies a flag that whether execute a property placeholder processing or not.
  266.    * <p>
  267.    * The default is {@literal false}. This means that a property placeholder processing does not execute.
  268.    *
  269.    * @since 1.1.1
  270.    *
  271.    * @param processPropertyPlaceHolders
  272.    *          a flag that whether execute a property placeholder processing or not
  273.    */
  274.   public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
  275.     this.processPropertyPlaceHolders = processPropertyPlaceHolders;
  276.   }

  277.   /**
  278.    * The class of the {@link MapperFactoryBean} to return a mybatis proxy as spring bean.
  279.    *
  280.    * @param mapperFactoryBeanClass
  281.    *          The class of the MapperFactoryBean
  282.    *
  283.    * @since 2.0.1
  284.    */
  285.   public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
  286.     this.mapperFactoryBeanClass = mapperFactoryBeanClass;
  287.   }

  288.   @Override
  289.   public void setApplicationContext(ApplicationContext applicationContext) {
  290.     this.applicationContext = applicationContext;
  291.   }

  292.   @Override
  293.   public void setBeanName(String name) {
  294.     this.beanName = name;
  295.   }

  296.   /**
  297.    * Gets beanNameGenerator to be used while running the scanner.
  298.    *
  299.    * @return the beanNameGenerator BeanNameGenerator that has been configured
  300.    *
  301.    * @since 1.2.0
  302.    */
  303.   public BeanNameGenerator getNameGenerator() {
  304.     return nameGenerator;
  305.   }

  306.   /**
  307.    * Sets beanNameGenerator to be used while running the scanner.
  308.    *
  309.    * @param nameGenerator
  310.    *          the beanNameGenerator to set
  311.    *
  312.    * @since 1.2.0
  313.    */
  314.   public void setNameGenerator(BeanNameGenerator nameGenerator) {
  315.     this.nameGenerator = nameGenerator;
  316.   }

  317.   /**
  318.    * Sets the default scope of scanned mappers.
  319.    * <p>
  320.    * Default is {@code null} (equiv to singleton).
  321.    * </p>
  322.    *
  323.    * @param defaultScope
  324.    *          the default scope
  325.    *
  326.    * @since 2.0.6
  327.    */
  328.   public void setDefaultScope(String defaultScope) {
  329.     this.defaultScope = defaultScope;
  330.   }

  331.   @Override
  332.   public void afterPropertiesSet() throws Exception {
  333.     notNull(this.basePackage, "Property 'basePackage' is required");
  334.   }

  335.   @Override
  336.   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  337.     // left intentionally blank
  338.   }

  339.   @Override
  340.   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  341.     if (this.processPropertyPlaceHolders) {
  342.       processPropertyPlaceHolders();
  343.     }

  344.     var scanner = new ClassPathMapperScanner(registry, getEnvironment());
  345.     scanner.setAddToConfig(this.addToConfig);
  346.     scanner.setAnnotationClass(this.annotationClass);
  347.     scanner.setMarkerInterface(this.markerInterface);
  348.     scanner.setExcludeFilters(this.excludeFilters = mergeExcludeFilters());
  349.     scanner.setSqlSessionFactory(this.sqlSessionFactory);
  350.     scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  351.     scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  352.     scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  353.     scanner.setResourceLoader(this.applicationContext);
  354.     scanner.setBeanNameGenerator(this.nameGenerator);
  355.     scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  356.     if (StringUtils.hasText(lazyInitialization)) {
  357.       scanner.setLazyInitialization(Boolean.parseBoolean(lazyInitialization));
  358.     }
  359.     if (StringUtils.hasText(defaultScope)) {
  360.       scanner.setDefaultScope(defaultScope);
  361.     }
  362.     scanner.registerFilters();
  363.     scanner.scan(
  364.         StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  365.   }

  366.   /*
  367.    * BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
  368.    * PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
  369.    * fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
  370.    * definition. Then update the values.
  371.    */
  372.   private void processPropertyPlaceHolders() {
  373.     Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,
  374.         false, false);

  375.     if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
  376.       var mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
  377.           .getBeanDefinition(beanName);

  378.       // PropertyResourceConfigurer does not expose any methods to explicitly perform
  379.       // property placeholder substitution. Instead, create a BeanFactory that just
  380.       // contains this mapper scanner and post process the factory.
  381.       var factory = new DefaultListableBeanFactory();
  382.       factory.registerBeanDefinition(beanName, mapperScannerBean);

  383.       for (PropertyResourceConfigurer prc : prcs.values()) {
  384.         prc.postProcessBeanFactory(factory);
  385.       }

  386.       PropertyValues values = mapperScannerBean.getPropertyValues();

  387.       this.basePackage = getPropertyValue("basePackage", values);
  388.       this.sqlSessionFactoryBeanName = getPropertyValue("sqlSessionFactoryBeanName", values);
  389.       this.sqlSessionTemplateBeanName = getPropertyValue("sqlSessionTemplateBeanName", values);
  390.       this.lazyInitialization = getPropertyValue("lazyInitialization", values);
  391.       this.defaultScope = getPropertyValue("defaultScope", values);
  392.       this.rawExcludeFilters = getPropertyValueForTypeFilter("rawExcludeFilters", values);
  393.     }
  394.     this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
  395.     this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
  396.         .map(getEnvironment()::resolvePlaceholders).orElse(null);
  397.     this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
  398.         .map(getEnvironment()::resolvePlaceholders).orElse(null);
  399.     this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
  400.         .orElse(null);
  401.     this.defaultScope = Optional.ofNullable(this.defaultScope).map(getEnvironment()::resolvePlaceholders).orElse(null);
  402.   }

  403.   private Environment getEnvironment() {
  404.     return this.applicationContext.getEnvironment();
  405.   }

  406.   private String getPropertyValue(String propertyName, PropertyValues values) {
  407.     var property = values.getPropertyValue(propertyName);

  408.     if (property == null) {
  409.       return null;
  410.     }

  411.     var value = property.getValue();

  412.     if (value == null) {
  413.       return null;
  414.     }
  415.     if (value instanceof String) {
  416.       return value.toString();
  417.     }
  418.     if (value instanceof TypedStringValue) {
  419.       return ((TypedStringValue) value).getValue();
  420.     }
  421.     return null;
  422.   }

  423.   @SuppressWarnings("unchecked")
  424.   private List<Map<String, String>> getPropertyValueForTypeFilter(String propertyName, PropertyValues values) {
  425.     var property = values.getPropertyValue(propertyName);
  426.     Object value;
  427.     if (property == null || (value = property.getValue()) == null || !(value instanceof List<?>)) {
  428.       return null;
  429.     }
  430.     return (List<Map<String, String>>) value;
  431.   }

  432.   private List<TypeFilter> mergeExcludeFilters() {
  433.     List<TypeFilter> typeFilters = new ArrayList<>();
  434.     if (this.rawExcludeFilters == null || this.rawExcludeFilters.isEmpty()) {
  435.       return this.excludeFilters;
  436.     }
  437.     if (this.excludeFilters != null && !this.excludeFilters.isEmpty()) {
  438.       typeFilters.addAll(this.excludeFilters);
  439.     }
  440.     try {
  441.       for (Map<String, String> typeFilter : this.rawExcludeFilters) {
  442.         typeFilters.add(
  443.             createTypeFilter(typeFilter.get("type"), typeFilter.get("expression"), this.getClass().getClassLoader()));
  444.       }
  445.     } catch (ClassNotFoundException exception) {
  446.       throw new RuntimeException("ClassNotFoundException occur when to load the Specified excludeFilter classes.",
  447.           exception);
  448.     }
  449.     return typeFilters;
  450.   }

  451.   @SuppressWarnings("unchecked")
  452.   private TypeFilter createTypeFilter(String filterType, String expression, @Nullable ClassLoader classLoader)
  453.       throws ClassNotFoundException {

  454.     if (this.processPropertyPlaceHolders) {
  455.       expression = this.getEnvironment().resolvePlaceholders(expression);
  456.     }

  457.     switch (filterType) {
  458.       case "annotation":
  459.         Class<?> filterAnno = ClassUtils.forName(expression, classLoader);
  460.         if (!Annotation.class.isAssignableFrom(filterAnno)) {
  461.           throw new IllegalArgumentException(
  462.               "Class is not assignable to [" + Annotation.class.getName() + "]: " + expression);
  463.         }
  464.         return new AnnotationTypeFilter((Class<Annotation>) filterAnno);
  465.       case "custom":
  466.         Class<?> filterClass = ClassUtils.forName(expression, classLoader);
  467.         if (!TypeFilter.class.isAssignableFrom(filterClass)) {
  468.           throw new IllegalArgumentException(
  469.               "Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
  470.         }
  471.         return (TypeFilter) BeanUtils.instantiateClass(filterClass);
  472.       case "assignable":
  473.         return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
  474.       case "regex":
  475.         return new RegexPatternTypeFilter(Pattern.compile(expression));
  476.       case "aspectj":
  477.         return new AspectJTypeFilter(expression, classLoader);
  478.       default:
  479.         throw new IllegalArgumentException("Unsupported filter type: " + filterType);
  480.     }
  481.   }

  482. }