MapperScannerRegistrar.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.annotation;

  17. import java.lang.annotation.Annotation;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.stream.Collectors;

  24. import org.mybatis.spring.mapper.ClassPathMapperScanner;
  25. import org.mybatis.spring.mapper.MapperFactoryBean;
  26. import org.mybatis.spring.mapper.MapperScannerConfigurer;
  27. import org.springframework.beans.BeanUtils;
  28. import org.springframework.beans.factory.config.BeanDefinition;
  29. import org.springframework.beans.factory.support.AbstractBeanDefinition;
  30. import org.springframework.beans.factory.support.BeanDefinitionBuilder;
  31. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  32. import org.springframework.beans.factory.support.BeanNameGenerator;
  33. import org.springframework.context.ResourceLoaderAware;
  34. import org.springframework.context.annotation.FilterType;
  35. import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
  36. import org.springframework.core.annotation.AnnotationAttributes;
  37. import org.springframework.core.io.ResourceLoader;
  38. import org.springframework.core.type.AnnotationMetadata;
  39. import org.springframework.core.type.filter.AnnotationTypeFilter;
  40. import org.springframework.core.type.filter.AssignableTypeFilter;
  41. import org.springframework.core.type.filter.TypeFilter;
  42. import org.springframework.util.Assert;
  43. import org.springframework.util.ClassUtils;
  44. import org.springframework.util.StringUtils;

  45. /**
  46.  * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using
  47.  * an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing
  48.  * {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
  49.  *
  50.  * @author Michael Lanyon
  51.  * @author Eduardo Macarron
  52.  * @author Putthiphong Boonphong
  53.  *
  54.  * @see MapperFactoryBean
  55.  * @see ClassPathMapperScanner
  56.  *
  57.  * @since 1.2.0
  58.  */
  59. public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  60.   // Note: Do not move resourceLoader via cleanup
  61.   private ResourceLoader resourceLoader;

  62.   @Override
  63.   public void setResourceLoader(ResourceLoader resourceLoader) {
  64.     this.resourceLoader = resourceLoader;
  65.   }

  66.   @Override
  67.   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  68.     var mapperScanAttrs = AnnotationAttributes
  69.         .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  70.     if (mapperScanAttrs != null) {
  71.       registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
  72.           generateBaseBeanName(importingClassMetadata, 0));
  73.     }
  74.   }

  75.   void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
  76.       BeanDefinitionRegistry registry, String beanName) {

  77.     var builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  78.     builder.addPropertyValue("processPropertyPlaceHolders", annoAttrs.getBoolean("processPropertyPlaceHolders"));

  79.     Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  80.     if (!Annotation.class.equals(annotationClass)) {
  81.       builder.addPropertyValue("annotationClass", annotationClass);
  82.     }

  83.     Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  84.     if (!Class.class.equals(markerInterface)) {
  85.       builder.addPropertyValue("markerInterface", markerInterface);
  86.     }

  87.     Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  88.     if (!BeanNameGenerator.class.equals(generatorClass)) {
  89.       builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
  90.     }

  91.     Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  92.     if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
  93.       builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
  94.     }

  95.     var sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
  96.     if (StringUtils.hasText(sqlSessionTemplateRef)) {
  97.       builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
  98.     }

  99.     var sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
  100.     if (StringUtils.hasText(sqlSessionFactoryRef)) {
  101.       builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
  102.     }

  103.     List<String> basePackages = new ArrayList<>(Arrays.stream(annoAttrs.getStringArray("basePackages"))
  104.         .filter(StringUtils::hasText).collect(Collectors.toList()));

  105.     basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
  106.         .collect(Collectors.toList()));

  107.     if (basePackages.isEmpty()) {
  108.       basePackages.add(getDefaultBasePackage(annoMeta));
  109.     }

  110.     var excludeFilterArray = annoAttrs.getAnnotationArray("excludeFilters");
  111.     if (excludeFilterArray.length > 0) {
  112.       List<TypeFilter> typeFilters = new ArrayList<>();
  113.       List<Map<String, String>> rawTypeFilters = new ArrayList<>();
  114.       for (AnnotationAttributes excludeFilters : excludeFilterArray) {
  115.         if (excludeFilters.getStringArray("pattern").length > 0) {
  116.           // in oder to apply placeholder resolver
  117.           rawTypeFilters.addAll(parseFiltersHasPatterns(excludeFilters));
  118.         } else {
  119.           typeFilters.addAll(typeFiltersFor(excludeFilters));
  120.         }
  121.       }
  122.       builder.addPropertyValue("excludeFilters", typeFilters);
  123.       builder.addPropertyValue("rawExcludeFilters", rawTypeFilters);
  124.     }

  125.     var lazyInitialization = annoAttrs.getString("lazyInitialization");
  126.     if (StringUtils.hasText(lazyInitialization)) {
  127.       builder.addPropertyValue("lazyInitialization", lazyInitialization);
  128.     }

  129.     var defaultScope = annoAttrs.getString("defaultScope");
  130.     if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
  131.       builder.addPropertyValue("defaultScope", defaultScope);
  132.     }

  133.     builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

  134.     // for spring-native
  135.     builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

  136.     registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  137.   }

  138.   /**
  139.    * Parse excludeFilters which FilterType is REGEX or ASPECTJ
  140.    *
  141.    * @param filterAttributes
  142.    *          AnnotationAttributes of excludeFilters
  143.    *
  144.    * @since 3.0.3
  145.    */
  146.   private List<Map<String, String>> parseFiltersHasPatterns(AnnotationAttributes filterAttributes) {

  147.     List<Map<String, String>> rawTypeFilters = new ArrayList<>();
  148.     FilterType filterType = filterAttributes.getEnum("type");
  149.     var expressionArray = filterAttributes.getStringArray("pattern");
  150.     for (String expression : expressionArray) {
  151.       switch (filterType) {
  152.         case REGEX:
  153.         case ASPECTJ:
  154.           Map<String, String> typeFilter = new HashMap<>(16);
  155.           typeFilter.put("type", filterType.name().toLowerCase());
  156.           typeFilter.put("expression", expression);
  157.           rawTypeFilters.add(typeFilter);
  158.           break;
  159.         default:
  160.           throw new IllegalArgumentException("Cannot specify the 'pattern' attribute if use the " + filterType
  161.               + " FilterType in exclude filter of @MapperScan");
  162.       }
  163.     }
  164.     return rawTypeFilters;
  165.   }

  166.   /**
  167.    * Parse excludeFilters which FilterType is ANNOTATION ASSIGNABLE or CUSTOM
  168.    *
  169.    * @param filterAttributes
  170.    *          AnnotationAttributes of excludeFilters
  171.    *
  172.    * @since 3.0.3
  173.    */
  174.   private List<TypeFilter> typeFiltersFor(AnnotationAttributes filterAttributes) {

  175.     List<TypeFilter> typeFilters = new ArrayList<>();
  176.     FilterType filterType = filterAttributes.getEnum("type");

  177.     for (Class<?> filterClass : filterAttributes.getClassArray("value")) {
  178.       switch (filterType) {
  179.         case ANNOTATION:
  180.           Assert.isAssignable(Annotation.class, filterClass,
  181.               "Specified an unsupported type in 'ANNOTATION' exclude filter of @MapperScan");
  182.           @SuppressWarnings("unchecked")
  183.           var annoClass = (Class<Annotation>) filterClass;
  184.           typeFilters.add(new AnnotationTypeFilter(annoClass));
  185.           break;
  186.         case ASSIGNABLE_TYPE:
  187.           typeFilters.add(new AssignableTypeFilter(filterClass));
  188.           break;
  189.         case CUSTOM:
  190.           Assert.isAssignable(TypeFilter.class, filterClass,
  191.               "An error occured when processing a @ComponentScan " + "CUSTOM type filter: ");
  192.           typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
  193.           break;
  194.         default:
  195.           throw new IllegalArgumentException("Cannot specify the 'value' or 'classes' attribute if use the "
  196.               + filterType + " FilterType in exclude filter of @MapperScan");
  197.       }
  198.     }
  199.     return typeFilters;
  200.   }

  201.   private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
  202.     return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
  203.   }

  204.   private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
  205.     return ClassUtils.getPackageName(importingClassMetadata.getClassName());
  206.   }

  207.   /**
  208.    * A {@link MapperScannerRegistrar} for {@link MapperScans}.
  209.    *
  210.    * @since 2.0.0
  211.    */
  212.   static class RepeatingRegistrar extends MapperScannerRegistrar {
  213.     @Override
  214.     public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  215.       var mapperScansAttrs = AnnotationAttributes
  216.           .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
  217.       if (mapperScansAttrs != null) {
  218.         var annotations = mapperScansAttrs.getAnnotationArray("value");
  219.         for (var i = 0; i < annotations.length; i++) {
  220.           registerBeanDefinitions(importingClassMetadata, annotations[i], registry,
  221.               generateBaseBeanName(importingClassMetadata, i));
  222.         }
  223.       }
  224.     }
  225.   }

  226. }