MapperScannerRegistrar.java
/*
* Copyright 2010-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mybatis.spring.annotation;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using
* an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing
* {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
*
* @author Michael Lanyon
* @author Eduardo Macarron
* @author Putthiphong Boonphong
*
* @see MapperFactoryBean
* @see ClassPathMapperScanner
*
* @since 1.2.0
*/
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
// Note: Do not move resourceLoader via cleanup
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
var mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
var builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", annoAttrs.getBoolean("processPropertyPlaceHolders"));
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
var sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
var sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
List<String> basePackages = new ArrayList<>(Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
var excludeFilterArray = annoAttrs.getAnnotationArray("excludeFilters");
if (excludeFilterArray.length > 0) {
List<TypeFilter> typeFilters = new ArrayList<>();
List<Map<String, String>> rawTypeFilters = new ArrayList<>();
for (AnnotationAttributes excludeFilters : excludeFilterArray) {
if (excludeFilters.getStringArray("pattern").length > 0) {
// in oder to apply placeholder resolver
rawTypeFilters.addAll(parseFiltersHasPatterns(excludeFilters));
} else {
typeFilters.addAll(typeFiltersFor(excludeFilters));
}
}
builder.addPropertyValue("excludeFilters", typeFilters);
builder.addPropertyValue("rawExcludeFilters", rawTypeFilters);
}
var lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
var defaultScope = annoAttrs.getString("defaultScope");
if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
builder.addPropertyValue("defaultScope", defaultScope);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
// for spring-native
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
/**
* Parse excludeFilters which FilterType is REGEX or ASPECTJ
*
* @param filterAttributes
* AnnotationAttributes of excludeFilters
*
* @since 3.0.3
*/
private List<Map<String, String>> parseFiltersHasPatterns(AnnotationAttributes filterAttributes) {
List<Map<String, String>> rawTypeFilters = new ArrayList<>();
FilterType filterType = filterAttributes.getEnum("type");
var expressionArray = filterAttributes.getStringArray("pattern");
for (String expression : expressionArray) {
switch (filterType) {
case REGEX:
case ASPECTJ:
Map<String, String> typeFilter = new HashMap<>(16);
typeFilter.put("type", filterType.name().toLowerCase());
typeFilter.put("expression", expression);
rawTypeFilters.add(typeFilter);
break;
default:
throw new IllegalArgumentException("Cannot specify the 'pattern' attribute if use the " + filterType
+ " FilterType in exclude filter of @MapperScan");
}
}
return rawTypeFilters;
}
/**
* Parse excludeFilters which FilterType is ANNOTATION ASSIGNABLE or CUSTOM
*
* @param filterAttributes
* AnnotationAttributes of excludeFilters
*
* @since 3.0.3
*/
private List<TypeFilter> typeFiltersFor(AnnotationAttributes filterAttributes) {
List<TypeFilter> typeFilters = new ArrayList<>();
FilterType filterType = filterAttributes.getEnum("type");
for (Class<?> filterClass : filterAttributes.getClassArray("value")) {
switch (filterType) {
case ANNOTATION:
Assert.isAssignable(Annotation.class, filterClass,
"Specified an unsupported type in 'ANNOTATION' exclude filter of @MapperScan");
@SuppressWarnings("unchecked")
var annoClass = (Class<Annotation>) filterClass;
typeFilters.add(new AnnotationTypeFilter(annoClass));
break;
case ASSIGNABLE_TYPE:
typeFilters.add(new AssignableTypeFilter(filterClass));
break;
case CUSTOM:
Assert.isAssignable(TypeFilter.class, filterClass,
"An error occured when processing a @ComponentScan " + "CUSTOM type filter: ");
typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
break;
default:
throw new IllegalArgumentException("Cannot specify the 'value' or 'classes' attribute if use the "
+ filterType + " FilterType in exclude filter of @MapperScan");
}
}
return typeFilters;
}
private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
}
private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
return ClassUtils.getPackageName(importingClassMetadata.getClassName());
}
/**
* A {@link MapperScannerRegistrar} for {@link MapperScans}.
*
* @since 2.0.0
*/
static class RepeatingRegistrar extends MapperScannerRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
var mapperScansAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
if (mapperScansAttrs != null) {
var annotations = mapperScansAttrs.getAnnotationArray("value");
for (var i = 0; i < annotations.length; i++) {
registerBeanDefinitions(importingClassMetadata, annotations[i], registry,
generateBaseBeanName(importingClassMetadata, i));
}
}
}
}
}