diff --git a/spring/src/main/java/tk/mybatis/spring/annotation/MapperScan.java b/spring/src/main/java/tk/mybatis/spring/annotation/MapperScan.java
index 0dab98301..f26f24b17 100644
--- a/spring/src/main/java/tk/mybatis/spring/annotation/MapperScan.java
+++ b/spring/src/main/java/tk/mybatis/spring/annotation/MapperScan.java
@@ -1,34 +1,41 @@
-/**
- * Copyright 2010-2016 the original author or authors.
- *
+/*
+ * Copyright 2010-2023 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
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
+ * 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 tk.mybatis.spring.annotation;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanNameGenerator;
+import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
+import org.springframework.core.annotation.AliasFor;
import tk.mybatis.spring.mapper.MapperFactoryBean;
import tk.mybatis.spring.mapper.MapperScannerConfigurer;
import java.lang.annotation.*;
/**
- * Use this annotation to register MyBatis mapper interfaces when using Java
- * Config. It performs when same work as {@link MapperScannerConfigurer} via
- * {@link tk.mybatis.spring.annotation.MapperScannerRegistrar}.
+ * Use this annotation to register MyBatis mapper interfaces when using Java Config. It performs when same work as
+ * {@link MapperScannerConfigurer} via {@link MapperScannerRegistrar}.
+ *
+ * Either {@link #basePackageClasses} or {@link #basePackages} (or its alias {@link #value}) may be specified to define
+ * specific packages to scan. Since 2.0.4, If specific packages are not defined, scanning will occur from the package of
+ * the class that declares this annotation.
+ *
+ * Configuration example:
+ *
*
- * Configuration example:
*
* @Configuration
* @MapperScan("org.mybatis.spring.sample.mapper")
@@ -36,9 +43,7 @@
*
* @Bean
* public DataSource dataSource() {
- * return new EmbeddedDatabaseBuilder()
- * .addScript("schema.sql")
- * .build();
+ * return new EmbeddedDatabaseBuilder().addScript("schema.sql").build();
* }
*
* @Bean
@@ -57,86 +62,139 @@
*
* @author Michael Lanyon
* @author Eduardo Macarron
- *
- * @since 1.2.0
- * @see tk.mybatis.spring.annotation.MapperScannerRegistrar
+ * @author Qimiao Chen
+ * @see MapperScannerRegistrar
* @see MapperFactoryBean
+ * @since 1.2.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
-@Import(tk.mybatis.spring.annotation.MapperScannerRegistrar.class)
+@Import(MapperScannerRegistrar.class)
+@Repeatable(MapperScans.class)
public @interface MapperScan {
/**
- * Alias for the {@link #basePackages()} attribute. Allows for more concise
- * annotation declarations e.g.:
- * {@code @EnableMyBatisMapperScanner("org.my.pkg")} instead of {@code
- * @EnableMyBatisMapperScanner(basePackages= "org.my.pkg"})}.
+ * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
+ * {@code @MapperScan("org.my.pkg")} instead of {@code @MapperScan(basePackages = "org.my.pkg"})}.
+ *
+ * @return base package names
*/
+ @AliasFor("basePackages")
String[] value() default {};
/**
- * Base packages to scan for MyBatis interfaces. Note that only interfaces
- * with at least one method will be registered; concrete classes will be
- * ignored.
+ * Base packages to scan for MyBatis interfaces. Note that only interfaces with at least one method will be
+ * registered; concrete classes will be ignored.
+ *
+ * @return base package names for scanning mapper interface
*/
+ @AliasFor("value")
String[] basePackages() default {};
/**
- * Type-safe alternative to {@link #basePackages()} for specifying the packages
- * to scan for annotated components. The package of each class specified will be scanned.
- * Consider creating a special no-op marker class or interface in each package
- * that serves no purpose other than being referenced by this attribute.
+ * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The
+ * package of each class specified will be scanned.
+ *
+ * Consider creating a special no-op marker class or interface in each package that serves no purpose other than being
+ * referenced by this attribute.
+ *
+ * @return classes that indicate base package for scanning mapper interface
*/
Class>[] basePackageClasses() default {};
/**
- * The {@link BeanNameGenerator} class to be used for naming detected components
- * within the Spring container.
+ * The {@link BeanNameGenerator} class to be used for naming detected components within the Spring container.
+ *
+ * @return the class of {@link BeanNameGenerator}
*/
Class extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* This property specifies the annotation that the scanner will search for.
*
- * The scanner will register all interfaces in the base package that also have
- * the specified annotation.
+ * The scanner will register all interfaces in the base package that also have the specified annotation.
*
* Note this can be combined with markerInterface.
+ *
+ * @return the annotation that the scanner will search for
*/
Class extends Annotation> annotationClass() default Annotation.class;
/**
* This property specifies the parent that the scanner will search for.
*
- * The scanner will register all interfaces in the base package that also have
- * the specified interface class as a parent.
+ * The scanner will register all interfaces in the base package that also have the specified interface class as a
+ * parent.
*
* Note this can be combined with annotationClass.
+ *
+ * @return the parent that the scanner will search for
*/
Class> markerInterface() default Class.class;
/**
- * Specifies which {@code SqlSessionTemplate} to use in the case that there is
- * more than one in the spring context. Usually this is only needed when you
- * have more than one datasource.
+ * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
+ * Usually this is only needed when you have more than one datasource.
+ *
+ * @return the bean name of {@code SqlSessionTemplate}
*/
String sqlSessionTemplateRef() default "";
/**
- * Specifies which {@code SqlSessionFactory} to use in the case that there is
- * more than one in the spring context. Usually this is only needed when you
- * have more than one datasource.
+ * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
+ * Usually this is only needed when you have more than one datasource.
+ *
+ * @return the bean name of {@code SqlSessionFactory}
*/
String sqlSessionFactoryRef() default "";
/**
* Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean.
*
+ * @return the class of {@code MapperFactoryBean}
*/
Class extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
+ /**
+ * Whether enable lazy initialization of mapper bean.
+ *
+ * Default is {@code false}.
+ *
+ *
+ * @return set {@code true} to enable lazy initialization
+ * @since 2.0.2
+ */
+ String lazyInitialization() default "";
+
+ /**
+ * Specifies the default scope of scanned mappers.
+ *
+ * Default is {@code ""} (equiv to singleton).
+ *
+ *
+ * @return the default scope
+ */
+ String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;
+
+ /**
+ * Specifies a flag that whether execute a property placeholder processing or not.
+ *
+ * The default is {@literal true}. This means that a property placeholder processing execute.
+ *
+ * @return a flag that whether execute a property placeholder processing or not
+ * @since 3.0.3
+ */
+ boolean processPropertyPlaceHolders() default true;
+
+ /**
+ * Specifies which types are not eligible for mapper scanning.
+ *
+ * @return array of customized mapper excludeFilter
+ * @since 3.0.3
+ */
+ ComponentScan.Filter[] excludeFilters() default {};
+
/**
* 通用 Mapper 的配置,一行一个配置
*
@@ -150,11 +208,4 @@
* @return
*/
String mapperHelperRef() default "";
-
- /**
- * Whether enable lazy initialization of mapper bean.
- * Default is {@code false}.
- * @return set {@code true} to enable lazy initialization
- */
- String lazyInitialization() default "";
-}
\ No newline at end of file
+}
diff --git a/spring/src/main/java/tk/mybatis/spring/annotation/MapperScannerRegistrar.java b/spring/src/main/java/tk/mybatis/spring/annotation/MapperScannerRegistrar.java
index a06bb6a55..6f6101a22 100644
--- a/spring/src/main/java/tk/mybatis/spring/annotation/MapperScannerRegistrar.java
+++ b/spring/src/main/java/tk/mybatis/spring/annotation/MapperScannerRegistrar.java
@@ -1,106 +1,165 @@
-/**
- * Copyright 2010-2016 the original author or authors.
- *
+/*
+ * 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
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
+ * 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 tk.mybatis.spring.annotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.EnvironmentAware;
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.env.Environment;
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;
import tk.mybatis.spring.mapper.ClassPathMapperScanner;
import tk.mybatis.spring.mapper.MapperFactoryBean;
+import tk.mybatis.spring.mapper.MapperScannerConfigurer;
import java.lang.annotation.Annotation;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
+/**
+ * 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, EnvironmentAware {
- public static final Logger LOGGER = LoggerFactory.getLogger(MapperScannerRegistrar.class);
- private ResourceLoader resourceLoader;
+ public static final Logger LOGGER = LoggerFactory.getLogger(MapperScannerRegistrar.class);
+
+ // Note: Do not move resourceLoader via cleanup
+ private ResourceLoader resourceLoader;
private Environment environment;
@Override
- public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
+ public void setResourceLoader(ResourceLoader resourceLoader) {
+ this.resourceLoader = resourceLoader;
+ }
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
- AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
- ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
- // this check is needed in Spring 3.1
- if (resourceLoader != null) {
- scanner.setResourceLoader(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)) {
- scanner.setAnnotationClass(annotationClass);
+ builder.addPropertyValue("annotationClass", annotationClass);
}
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
- scanner.setMarkerInterface(markerInterface);
+ builder.addPropertyValue("markerInterface", markerInterface);
}
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
- scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
+ builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
- scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
+ builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
- scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
- scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
+ var sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
+ if (StringUtils.hasText(sqlSessionTemplateRef)) {
+ builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
+ }
- List basePackages = new ArrayList();
- for (String pkg : annoAttrs.getStringArray("value")) {
- if (StringUtils.hasText(pkg)) {
- basePackages.add(pkg);
- }
+ var sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
+ if (StringUtils.hasText(sqlSessionFactoryRef)) {
+ builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
- for (String pkg : annoAttrs.getStringArray("basePackages")) {
- if (StringUtils.hasText(pkg)) {
- basePackages.add(pkg);
- }
+
+ List 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));
}
- for (Class> clazz : annoAttrs.getClassArray("basePackageClasses")) {
- basePackages.add(ClassUtils.getPackageName(clazz));
+
+ var excludeFilterArray = annoAttrs.getAnnotationArray("excludeFilters");
+ if (excludeFilterArray.length > 0) {
+ List typeFilters = new ArrayList<>();
+ List> 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);
}
+
//优先级 mapperHelperRef > properties > springboot
String mapperHelperRef = annoAttrs.getString("mapperHelperRef");
String[] properties = annoAttrs.getStringArray("properties");
if (StringUtils.hasText(mapperHelperRef)) {
- scanner.setMapperHelperBeanName(mapperHelperRef);
+ builder.addPropertyValue("mapperHelperBeanName", mapperHelperRef);
} else if (properties != null && properties.length > 0) {
- scanner.setMapperProperties(properties);
+ builder.addPropertyValue("mapperProperties", properties);
} else {
try {
- scanner.setMapperProperties(this.environment);
+ builder.addPropertyValue("mapperProperties", this.environment);
} catch (Exception e) {
LOGGER.warn("只有 Spring Boot 环境中可以通过 Environment(配置文件,环境变量,运行参数等方式) 配置通用 Mapper," +
"其他环境请通过 @MapperScan 注解中的 mapperHelperRef 或 properties 参数进行配置!" +
@@ -108,22 +167,119 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
}
}
- String lazyInitialization = annoAttrs.getString("lazyInitialization");
+ var lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
- scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
+ builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
- scanner.registerFilters();
- scanner.doScan(StringUtils.toStringArray(basePackages));
+ 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());
+
}
- @Override
- public void setEnvironment(Environment environment) {
- this.environment = environment;
+ /**
+ * Parse excludeFilters which FilterType is REGEX or ASPECTJ
+ *
+ * @param filterAttributes
+ * AnnotationAttributes of excludeFilters
+ *
+ * @since 3.0.3
+ */
+ private List> parseFiltersHasPatterns(AnnotationAttributes filterAttributes) {
+
+ List> rawTypeFilters = new ArrayList<>();
+ FilterType filterType = filterAttributes.getEnum("type");
+ var expressionArray = filterAttributes.getStringArray("pattern");
+ for (String expression : expressionArray) {
+ switch (filterType) {
+ case REGEX:
+ case ASPECTJ:
+ Map 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;
}
- @Override
- public void setResourceLoader(ResourceLoader resourceLoader) {
- this.resourceLoader = resourceLoader;
+ /**
+ * Parse excludeFilters which FilterType is ANNOTATION ASSIGNABLE or CUSTOM
+ *
+ * @param filterAttributes
+ * AnnotationAttributes of excludeFilters
+ *
+ * @since 3.0.3
+ */
+ private List typeFiltersFor(AnnotationAttributes filterAttributes) {
+
+ List 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) 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));
+ }
+ }
+ }
+ }
+
}
diff --git a/spring/src/main/java/tk/mybatis/spring/annotation/MapperScans.java b/spring/src/main/java/tk/mybatis/spring/annotation/MapperScans.java
new file mode 100644
index 000000000..1528e3b9d
--- /dev/null
+++ b/spring/src/main/java/tk/mybatis/spring/annotation/MapperScans.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2022 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 tk.mybatis.spring.annotation;
+
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * The Container annotation that aggregates several {@link tk.mybatis.spring.annotation.MapperScan} annotations.
+ *
+ * Can be used natively, declaring several nested {@link tk.mybatis.spring.annotation.MapperScan} annotations. Can also be used in conjunction with
+ * Java 8's support for repeatable annotations, where {@link tk.mybatis.spring.annotation.MapperScan} can simply be declared several times on the
+ * same method, implicitly generating this container annotation.
+ *
+ * @author Kazuki Shimizu
+ *
+ * @since 2.0.0
+ *
+ * @see tk.mybatis.spring.annotation.MapperScan
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+@Import(MapperScannerRegistrar.RepeatingRegistrar.class)
+public @interface MapperScans {
+ MapperScan[] value();
+}
diff --git a/spring/src/main/java/tk/mybatis/spring/mapper/ClassPathMapperScanner.java b/spring/src/main/java/tk/mybatis/spring/mapper/ClassPathMapperScanner.java
index 11279ddb8..ff9e127d6 100644
--- a/spring/src/main/java/tk/mybatis/spring/mapper/ClassPathMapperScanner.java
+++ b/spring/src/main/java/tk/mybatis/spring/mapper/ClassPathMapperScanner.java
@@ -1,12 +1,12 @@
-/**
- * Copyright 2010-2016 the original author or authors.
- *
+/*
+ * 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
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
+ * 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.
@@ -15,20 +15,25 @@
*/
package tk.mybatis.spring.mapper;
+import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
-import org.mybatis.spring.mapper.MapperScannerConfigurer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.scope.ScopedProxyFactoryBean;
+import org.springframework.aop.scope.ScopedProxyUtils;
+import org.springframework.aot.AotDetector;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
+import org.springframework.core.NativeDetector;
import org.springframework.core.env.Environment;
-import org.springframework.core.type.classreading.MetadataReader;
-import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
@@ -37,33 +42,37 @@
import tk.mybatis.mapper.entity.Config;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
-import java.io.IOException;
import java.lang.annotation.Annotation;
-import java.util.Arrays;
-import java.util.Properties;
-import java.util.Set;
+import java.util.*;
/**
- * A {@link ClassPathBeanDefinitionScanner} that registers Mappers by
- * {@code basePackage}, {@code annotationClass}, or {@code markerInterface}. If
- * an {@code annotationClass} and/or {@code markerInterface} is specified, only
- * the specified types will be searched (searching for all interfaces will be
- * disabled).
+ * A {@link ClassPathBeanDefinitionScanner} that registers Mappers by {@code basePackage}, {@code annotationClass}, or
+ * {@code markerInterface}. If an {@code annotationClass} and/or {@code markerInterface} is specified, only the
+ * specified types will be searched (searching for all interfaces will be disabled).
*
- * This functionality was previously a private class of
- * {@link MapperScannerConfigurer}, but was broken out in version 1.2.0.
+ * This functionality was previously a private class of {@link MapperScannerConfigurer}, but was broken out in version
+ * 1.2.0.
*
* @author Hunter Presnall
* @author Eduardo Macarron
+ *
* @see MapperFactoryBean
+ *
* @since 1.2.0
*/
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ClassPathMapperScanner.class);
+
+ // Copy of FactoryBean#OBJECT_TYPE_ATTRIBUTE which was added in Spring 5.2
+ static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
+
private boolean addToConfig = true;
private boolean lazyInitialization;
+ private boolean printWarnLogIfNotFoundMappers = true;
+
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
@@ -80,19 +89,130 @@ public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
private String mapperHelperBeanName;
- private MapperFactoryBean> mapperFactoryBean = new MapperFactoryBean();
+ private Class extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
+ private String defaultScope;
+ private List excludeFilters;
+
+ public ClassPathMapperScanner(BeanDefinitionRegistry registry, Environment environment) {
+ super(registry, false, environment);
+ setIncludeAnnotationConfig(!AotDetector.useGeneratedArtifacts());
+ setPrintWarnLogIfNotFoundMappers(!NativeDetector.inNativeImage());
+ }
+
+ /**
+ * @deprecated Please use the {@link #ClassPathMapperScanner(BeanDefinitionRegistry, Environment)}.
+ */
+ @Deprecated(since = "3.0.4", forRemoval = true)
public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
super(registry, false);
+ setIncludeAnnotationConfig(!AotDetector.useGeneratedArtifacts());
+ setPrintWarnLogIfNotFoundMappers(!NativeDetector.inNativeImage());
+ }
+
+ public void setAddToConfig(boolean addToConfig) {
+ this.addToConfig = addToConfig;
+ }
+
+ public void setAnnotationClass(Class extends Annotation> annotationClass) {
+ this.annotationClass = annotationClass;
+ }
+
+ /**
+ * Set whether enable lazy initialization for mapper bean.
+ *
+ * Default is {@code false}.
+ *
+ *
+ * @param lazyInitialization
+ * Set the @{code true} to enable
+ *
+ * @since 2.0.2
+ */
+ public void setLazyInitialization(boolean lazyInitialization) {
+ this.lazyInitialization = lazyInitialization;
+ }
+
+ /**
+ * Set whether print warning log if not found mappers that matches conditions.
+ *
+ * Default is {@code true}. But {@code false} when running in native image.
+ *
+ *
+ * @param printWarnLogIfNotFoundMappers
+ * Set the @{code true} to print
+ *
+ * @since 3.0.1
+ */
+ public void setPrintWarnLogIfNotFoundMappers(boolean printWarnLogIfNotFoundMappers) {
+ this.printWarnLogIfNotFoundMappers = printWarnLogIfNotFoundMappers;
+ }
+
+ public void setMarkerInterface(Class> markerInterface) {
+ this.markerInterface = markerInterface;
+ }
+
+ public void setExcludeFilters(List excludeFilters) {
+ this.excludeFilters = excludeFilters;
+ }
+
+ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
+ this.sqlSessionFactory = sqlSessionFactory;
+ }
+
+ public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
+ this.sqlSessionTemplate = sqlSessionTemplate;
+ }
+
+ public void setSqlSessionTemplateBeanName(String sqlSessionTemplateBeanName) {
+ this.sqlSessionTemplateBeanName = sqlSessionTemplateBeanName;
+ }
+
+ public void setSqlSessionFactoryBeanName(String sqlSessionFactoryBeanName) {
+ this.sqlSessionFactoryBeanName = sqlSessionFactoryBeanName;
+ }
+
+ /**
+ * @deprecated Since 2.0.1, Please use the {@link #setMapperFactoryBeanClass(Class)}.
+ */
+ @Deprecated
+ public void setMapperFactoryBean(MapperFactoryBean> mapperFactoryBean) {
+ this.mapperFactoryBeanClass = mapperFactoryBean == null ? MapperFactoryBean.class : mapperFactoryBean.getClass();
+ }
+
+ /**
+ * Set the {@code MapperFactoryBean} class.
+ *
+ * @param mapperFactoryBeanClass
+ * the {@code MapperFactoryBean} class
+ *
+ * @since 2.0.1
+ */
+ public void setMapperFactoryBeanClass(Class extends MapperFactoryBean> mapperFactoryBeanClass) {
+ this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
+ }
+
+ /**
+ * Set the default scope of scanned mappers.
+ *
+ * Default is {@code null} (equiv to singleton).
+ *
+ *
+ * @param defaultScope
+ * the scope
+ *
+ * @since 2.0.6
+ */
+ public void setDefaultScope(String defaultScope) {
+ this.defaultScope = defaultScope;
}
/**
- * Configures parent scanner to search for the right interfaces. It can search
- * for all interfaces or just for those that extends a markerInterface or/and
- * those annotated with the annotationClass
+ * Configures parent scanner to search for the right interfaces. It can search for all interfaces or just for those
+ * that extends a markerInterface or/and those annotated with the annotationClass
*/
public void registerFilters() {
- boolean acceptAllInterfaces = true;
+ var acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
@@ -113,39 +233,40 @@ protected boolean matchClassName(String className) {
if (acceptAllInterfaces) {
// default include filter that accepts all classes
- addIncludeFilter(new TypeFilter() {
- @Override
- public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
- return true;
- }
- });
+ addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
- addExcludeFilter(new TypeFilter() {
- @Override
- public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
- String className = metadataReader.getClassMetadata().getClassName();
- if (className.endsWith("package-info")) {
- return true;
- }
- return metadataReader.getAnnotationMetadata()
- .hasAnnotation("tk.mybatis.mapper.annotation.RegisterMapper");
+ addExcludeFilter((metadataReader, metadataReaderFactory) -> {
+ var className = metadataReader.getClassMetadata().getClassName();
+ if (className.endsWith("package-info")) {
+ return true;
}
+ return metadataReader.getAnnotationMetadata()
+ .hasAnnotation("tk.mybatis.mapper.annotation.RegisterMapper");
});
+
+ // exclude types declared by MapperScan.excludeFilters
+ if (excludeFilters != null && excludeFilters.size() > 0) {
+ for (TypeFilter excludeFilter : excludeFilters) {
+ addExcludeFilter(excludeFilter);
+ }
+ }
}
/**
- * Calls the parent search that will search and register all the candidates.
- * Then the registered objects are post processed to set them as
- * MapperFactoryBeans
+ * Calls the parent search that will search and register all the candidates. Then the registered objects are post
+ * processed to set them as MapperFactoryBeans
*/
@Override
public Set doScan(String... basePackages) {
- Set beanDefinitions = super.doScan(basePackages);
+ var beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
- logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
+ if (printWarnLogIfNotFoundMappers) {
+ LOGGER.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ + "' package. Please check your configuration.");
+ }
} else {
processBeanDefinitions(beanDefinitions);
}
@@ -154,19 +275,38 @@ public Set doScan(String... basePackages) {
}
private void processBeanDefinitions(Set beanDefinitions) {
- GenericBeanDefinition definition;
+ AbstractBeanDefinition definition;
+ var registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {
- definition = (GenericBeanDefinition) holder.getBeanDefinition();
-
- if (logger.isDebugEnabled()) {
- logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
- + "' and '" + definition.getBeanClassName() + "' mapperInterface");
+ definition = (AbstractBeanDefinition) holder.getBeanDefinition();
+ var scopedProxy = false;
+ if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
+ definition = (AbstractBeanDefinition) Optional
+ .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
+ .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
+ "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
+ scopedProxy = true;
}
+ var beanClassName = definition.getBeanClassName();
+ LOGGER.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ + "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
- definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
- definition.setBeanClass(this.mapperFactoryBean.getClass());
+ definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
+ try {
+ Class> beanClass = Resources.classForName(beanClassName);
+ // Attribute for MockitoPostProcessor
+ // https://github.com/mybatis/spring-boot-starter/issues/475
+ definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClass);
+ // for spring-native
+ definition.getPropertyValues().add("mapperInterface", beanClass);
+ } catch (ClassNotFoundException ignore) {
+ // ignore
+ }
+
+ definition.setBeanClass(this.mapperFactoryBeanClass);
+
//设置通用 Mapper
if (StringUtils.hasText(this.mapperHelperBeanName)) {
definition.getPropertyValues().add("mapperHelper", new RuntimeBeanReference(this.mapperHelperBeanName));
@@ -180,9 +320,10 @@ private void processBeanDefinitions(Set beanDefinitions) {
definition.getPropertyValues().add("addToConfig", this.addToConfig);
- boolean explicitFactoryUsed = false;
+ var explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
- definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
+ definition.getPropertyValues().add("sqlSessionFactory",
+ new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
@@ -191,50 +332,58 @@ private void processBeanDefinitions(Set beanDefinitions) {
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
- logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
+ LOGGER.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
- definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
+ definition.getPropertyValues().add("sqlSessionTemplate",
+ new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
- logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
+ LOGGER.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
- if (logger.isDebugEnabled()) {
- logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
- }
+ LOGGER.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
+
+ if (scopedProxy) {
+ continue;
+ }
+
+ if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
+ definition.setScope(defaultScope);
+ }
+
+ if (!definition.isSingleton()) {
+ var proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
+ if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
+ registry.removeBeanDefinition(proxyHolder.getBeanName());
+ }
+ registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
+ }
+
}
}
- /**
- * {@inheritDoc}
- */
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
- /**
- * {@inheritDoc}
- */
@Override
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
if (super.checkCandidate(beanName, beanDefinition)) {
return true;
- } else {
- logger.warn("Skipping MapperFactoryBean with name '" + beanName
- + "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface"
- + ". Bean already defined with the same name!");
- return false;
}
+ LOGGER.warn("Skipping MapperFactoryBean with name '" + beanName + "' and '"
+ + beanDefinition.getBeanClassName() + "' mapperInterface" + ". Bean already defined with the same name!");
+ return false;
}
public MapperHelper getMapperHelper() {
@@ -245,18 +394,6 @@ public void setMapperHelper(MapperHelper mapperHelper) {
this.mapperHelper = mapperHelper;
}
- public void setAddToConfig(boolean addToConfig) {
- this.addToConfig = addToConfig;
- }
-
- public void setAnnotationClass(Class extends Annotation> annotationClass) {
- this.annotationClass = annotationClass;
- }
-
- public void setLazyInitialization(boolean lazyInitialization) {
- this.lazyInitialization = lazyInitialization;
- }
-
/**
* 配置通用 Mapper
*
@@ -269,16 +406,12 @@ public void setConfig(Config config) {
mapperHelper.setConfig(config);
}
- public void setMapperFactoryBean(MapperFactoryBean> mapperFactoryBean) {
- this.mapperFactoryBean = mapperFactoryBean != null ? mapperFactoryBean : new MapperFactoryBean();
- }
-
public void setMapperHelperBeanName(String mapperHelperBeanName) {
this.mapperHelperBeanName = mapperHelperBeanName;
}
/**
- * 从环境变量中获取 mapper 配置信息
+ * TODO 从环境变量中获取 mapper 配置信息
*
* @param environment
*/
@@ -293,7 +426,7 @@ public void setMapperProperties(Environment environment) {
}
/**
- * 从 properties 数组获取 mapper 配置信息
+ * TODO 从 properties 数组获取 mapper 配置信息
*
* @param properties
*/
@@ -319,23 +452,4 @@ public void setMapperProperties(String[] properties) {
mapperHelper.setProperties(props);
}
- public void setMarkerInterface(Class> markerInterface) {
- this.markerInterface = markerInterface;
- }
-
- public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
- this.sqlSessionFactory = sqlSessionFactory;
- }
-
- public void setSqlSessionFactoryBeanName(String sqlSessionFactoryBeanName) {
- this.sqlSessionFactoryBeanName = sqlSessionFactoryBeanName;
- }
-
- public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
- this.sqlSessionTemplate = sqlSessionTemplate;
- }
-
- public void setSqlSessionTemplateBeanName(String sqlSessionTemplateBeanName) {
- this.sqlSessionTemplateBeanName = sqlSessionTemplateBeanName;
- }
}
diff --git a/spring/src/main/java/tk/mybatis/spring/mapper/MapperScannerConfigurer.java b/spring/src/main/java/tk/mybatis/spring/mapper/MapperScannerConfigurer.java
index 70970e278..3c0b1d715 100644
--- a/spring/src/main/java/tk/mybatis/spring/mapper/MapperScannerConfigurer.java
+++ b/spring/src/main/java/tk/mybatis/spring/mapper/MapperScannerConfigurer.java
@@ -1,12 +1,12 @@
-/**
- * Copyright 2010-2016 the original author or authors.
- *
+/*
+ * 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
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
+ * 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.
@@ -17,11 +17,10 @@
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
-import org.springframework.beans.PropertyValue;
+import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;
import org.springframework.beans.factory.config.TypedStringValue;
@@ -33,69 +32,70 @@
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
+import org.springframework.core.type.filter.*;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
+import tk.mybatis.mapper.MapperException;
import tk.mybatis.mapper.common.Marker;
+import tk.mybatis.mapper.entity.Config;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
import java.lang.annotation.Annotation;
-import java.util.Map;
-import java.util.Properties;
+import java.util.*;
+import java.util.regex.Pattern;
import static org.springframework.util.Assert.notNull;
/**
- * BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for
- * interfaces and registers them as {@code MapperFactoryBean}. Note that only interfaces with at
- * least one method will be registered; concrete classes will be ignored.
+ * BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and
+ * registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered;
+ * concrete classes will be ignored.
*
* This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to
- * {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269
- * for the details.
+ * {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the
+ * details.
*
- * The {@code basePackage} property can contain more than one package name, separated by either
- * commas or semicolons.
+ * The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons.
*
- * This class supports filtering the mappers created by either specifying a marker interface or an
- * annotation. The {@code annotationClass} property specifies an annotation to search for. The
- * {@code markerInterface} property specifies a parent interface to search for. If both properties
- * are specified, mappers are added for interfaces that match either criteria. By default,
- * these two properties are null, so all interfaces in the given {@code basePackage} are added as
- * mappers.
+ * This class supports filtering the mappers created by either specifying a marker interface or an annotation. The
+ * {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property
+ * specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that
+ * match either criteria. By default, these two properties are null, so all interfaces in the given
+ * {@code basePackage} are added as mappers.
*
- * This configurer enables autowire for all the beans that it creates so that they are
- * automatically autowired with the proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}.
- * If there is more than one {@code SqlSessionFactory} in the application, however, autowiring
- * cannot be used. In this case you must explicitly specify either an {@code SqlSessionFactory} or
- * an {@code SqlSessionTemplate} to use via the bean name properties. Bean names are used
- * rather than actual objects because Spring does not initialize property placeholders until after
- * this class is processed.
+ * This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the
+ * proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory}
+ * in the application, however, autowiring cannot be used. In this case you must explicitly specify either an
+ * {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the bean name properties. Bean names
+ * are used rather than actual objects because Spring does not initialize property placeholders until after this class
+ * is processed.
*
- * Passing in an actual object which may require placeholders (i.e. DB user password) will fail.
- * Using bean names defers actual object creation until later in the startup
- * process, after all placeholder substituation is completed. However, note that this configurer
- * does support property placeholders of its own properties. The basePackage
- * and bean name properties all support ${property}
style substitution.
+ * Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers
+ * actual object creation until later in the startup process, after all placeholder substitution is completed. However,
+ * note that this configurer does support property placeholders of its own properties. The
+ * basePackage
and bean name properties all support ${property}
style substitution.
*
* Configuration sample:
- *
- *
+ *
*
* {@code
- *
- *
- *
- *
- *
+ *
+ *
+ *
+ *
+ *
* }
*
*
* @author Hunter Presnall
* @author Eduardo Macarron
- * @author liuzh
- * @see tk.mybatis.spring.mapper.MapperFactoryBean
+ *
+ * @see MapperFactoryBean
* @see ClassPathMapperScanner
*/
-public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
+public class MapperScannerConfigurer
+ implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
@@ -115,6 +115,12 @@ public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProces
private Class> markerInterface;
+ private List excludeFilters;
+
+ private List> rawExcludeFilters;
+
+ private Class extends MapperFactoryBean> mapperFactoryBeanClass;
+
private ApplicationContext applicationContext;
private String beanName;
@@ -123,8 +129,12 @@ public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProces
private BeanNameGenerator nameGenerator;
+ private String defaultScope;
+
private MapperHelper mapperHelper = new MapperHelper();
+ private String mapperHelperBeanName;
+
public MapperHelper getMapperHelper() {
return mapperHelper;
}
@@ -135,132 +145,25 @@ public void setMapperHelper(MapperHelper mapperHelper) {
/**
- * {@inheritDoc}
- */
- @Override
- public void afterPropertiesSet() throws Exception {
- notNull(this.basePackage, "Property 'basePackage' is required");
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
- // left intentionally blank
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 1.0.2
- */
- @Override
- public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
- if (this.processPropertyPlaceHolders) {
- processPropertyPlaceHolders();
- }
- ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
- scanner.setAddToConfig(this.addToConfig);
- scanner.setAnnotationClass(this.annotationClass);
- scanner.setMarkerInterface(this.markerInterface);
- scanner.setSqlSessionFactory(this.sqlSessionFactory);
- scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
- scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
- scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
- scanner.setResourceLoader(this.applicationContext);
- scanner.setBeanNameGenerator(this.nameGenerator);
- if (StringUtils.hasText(lazyInitialization)) {
- scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
- }
- scanner.registerFilters();
- //设置通用 Mapper
- scanner.setMapperHelper(this.mapperHelper);
- scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
- }
-
- /*
- * BeanDefinitionRegistries are called early in application startup, before
- * BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been
- * loaded and any property substitution of this class' properties will fail. To avoid this, find
- * any PropertyResourceConfigurers defined in the context and run them on this class' bean
- * definition. Then update the values.
- */
- private void processPropertyPlaceHolders() {
- Map prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
-
- if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
- BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext)
- .getBeanFactory().getBeanDefinition(beanName);
-
- // PropertyResourceConfigurer does not expose any methods to explicitly perform
- // property placeholder substitution. Instead, create a BeanFactory that just
- // contains this mapper scanner and post process the factory.
- DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
- factory.registerBeanDefinition(beanName, mapperScannerBean);
-
- for (PropertyResourceConfigurer prc : prcs.values()) {
- prc.postProcessBeanFactory(factory);
- }
-
- PropertyValues values = mapperScannerBean.getPropertyValues();
-
- this.basePackage = updatePropertyValue("basePackage", values);
- this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
- this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
- this.lazyInitialization = updatePropertyValue("lazyInitialization", values);
- }
- this.lazyInitialization = this.lazyInitialization == null ? null : getEnvironment().resolvePlaceholders(this.lazyInitialization);
- }
-
- private Environment getEnvironment() {
- return this.applicationContext.getEnvironment();
- }
-
- private String updatePropertyValue(String propertyName, PropertyValues values) {
- PropertyValue property = values.getPropertyValue(propertyName);
-
- if (property == null) {
- return null;
- }
-
- Object value = property.getValue();
-
- if (value == null) {
- return null;
- } else if (value instanceof String) {
- return value.toString();
- } else if (value instanceof TypedStringValue) {
- return ((TypedStringValue) value).getValue();
- } else {
- return null;
- }
- }
-
- /**
- * Gets beanNameGenerator to be used while running the scanner.
- *
- * @return the beanNameGenerator BeanNameGenerator that has been configured
- * @since 1.2.0
- */
- public BeanNameGenerator getNameGenerator() {
- return nameGenerator;
- }
-
- /**
- * Sets beanNameGenerator to be used while running the scanner.
+ * This property lets you set the base package for your mapper interface files.
+ *
+ * You can set more than one package by using a semicolon or comma as a separator.
+ *
+ * Mappers will be searched for recursively starting in the specified package(s).
*
- * @param nameGenerator the beanNameGenerator to set
- * @since 1.2.0
+ * @param basePackage
+ * base package name
*/
- public void setNameGenerator(BeanNameGenerator nameGenerator) {
- this.nameGenerator = nameGenerator;
+ public void setBasePackage(String basePackage) {
+ this.basePackage = basePackage;
}
/**
* Same as {@code MapperFactoryBean#setAddToConfig(boolean)}.
*
* @param addToConfig
+ * a flag that whether add mapper to MyBatis or not
+ *
* @see MapperFactoryBean#setAddToConfig(boolean)
*/
public void setAddToConfig(boolean addToConfig) {
@@ -269,8 +172,14 @@ public void setAddToConfig(boolean addToConfig) {
/**
* Set whether enable lazy initialization for mapper bean.
+ *
* Default is {@code false}.
- * @param lazyInitialization Set the @{code true} to enable
+ *
+ *
+ * @param lazyInitialization
+ * Set the @{code true} to enable
+ *
+ * @since 2.0.2
*/
public void setLazyInitialization(String lazyInitialization) {
this.lazyInitialization = lazyInitialization;
@@ -279,79 +188,112 @@ public void setLazyInitialization(String lazyInitialization) {
/**
* This property specifies the annotation that the scanner will search for.
*
- * The scanner will register all interfaces in the base package that also have the
- * specified annotation.
+ * The scanner will register all interfaces in the base package that also have the specified annotation.
*
* Note this can be combined with markerInterface.
*
- * @param annotationClass annotation class
+ * @param annotationClass
+ * annotation class
*/
public void setAnnotationClass(Class extends Annotation> annotationClass) {
this.annotationClass = annotationClass;
}
/**
- * {@inheritDoc}
+ * This property specifies the parent that the scanner will search for.
+ *
+ * The scanner will register all interfaces in the base package that also have the specified interface class as a
+ * parent.
+ *
+ * Note this can be combined with annotationClass.
+ *
+ * @param superClass
+ * parent class
*/
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) {
- this.applicationContext = applicationContext;
+ public void setMarkerInterface(Class> superClass) {
+ this.markerInterface = superClass;
+ if (Marker.class.isAssignableFrom(superClass)) {
+ mapperHelper.registerMapper(superClass);
+ }
}
/**
- * This property lets you set the base package for your mapper interface files.
+ * Specifies which types are not eligible for the mapper scanner.
*
- * You can set more than one package by using a semicolon or comma as a separator.
- *
- * Mappers will be searched for recursively starting in the specified package(s).
+ * The scanner will exclude types that define with excludeFilters.
*
- * @param basePackage base package name
+ * @since 3.0.3
+ *
+ * @param excludeFilters
+ * list of TypeFilter
*/
- public void setBasePackage(String basePackage) {
- this.basePackage = basePackage;
+ public void setExcludeFilters(List excludeFilters) {
+ this.excludeFilters = excludeFilters;
}
/**
- * {@inheritDoc}
+ * In order to support process PropertyPlaceHolders.
+ *
+ * After parsed, it will be added to excludeFilters.
+ *
+ * @since 3.0.3
+ *
+ * @param rawExcludeFilters
+ * list of rawExcludeFilter
*/
- @Override
- public void setBeanName(String name) {
- this.beanName = name;
+ public void setRawExcludeFilters(List> rawExcludeFilters) {
+ this.rawExcludeFilters = rawExcludeFilters;
}
/**
- * This property specifies the parent that the scanner will search for.
+ * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
+ * Usually this is only needed when you have more than one datasource.
*
- * The scanner will register all interfaces in the base package that also have the
- * specified interface class as a parent.
+ *
+ * @deprecated Use {@link #setSqlSessionTemplateBeanName(String)} instead
+ *
+ * @param sqlSessionTemplate
+ * a template of SqlSession
+ */
+ @Deprecated
+ public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
+ this.sqlSessionTemplate = sqlSessionTemplate;
+ }
+
+ /**
+ * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
+ * Usually this is only needed when you have more than one datasource.
*
- * Note this can be combined with annotationClass.
+ * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
+ * it is too early to build mybatis object instances.
*
- * @param superClass parent class
+ * @since 1.1.0
+ *
+ * @param sqlSessionTemplateName
+ * Bean name of the {@code SqlSessionTemplate}
*/
- public void setMarkerInterface(Class> superClass) {
- this.markerInterface = superClass;
- if (Marker.class.isAssignableFrom(superClass)) {
- mapperHelper.registerMapper(superClass);
- }
+ public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) {
+ this.sqlSessionTemplateBeanName = sqlSessionTemplateName;
}
/**
- * @param processPropertyPlaceHolders
- * @since 1.1.1
+ * 属性注入
+ *
+ * @param properties
*/
- public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
- this.processPropertyPlaceHolders = processPropertyPlaceHolders;
+ public void setProperties(Properties properties) {
+ mapperHelper.setProperties(properties);
}
/**
- * Specifies which {@code SqlSessionFactory} to use in the case that there is
- * more than one in the spring context. Usually this is only needed when you
- * have more than one datasource.
+ * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
+ * Usually this is only needed when you have more than one datasource.
*
*
- * @param sqlSessionFactory
* @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead.
+ *
+ * @param sqlSessionFactory
+ * a factory of SqlSession
*/
@Deprecated
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
@@ -359,58 +301,315 @@ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
}
/**
- * Specifies which {@code SqlSessionFactory} to use in the case that there is
- * more than one in the spring context. Usually this is only needed when you
- * have more than one datasource.
+ * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
+ * Usually this is only needed when you have more than one datasource.
*
- * Note bean names are used, not bean references. This is because the scanner
- * loads early during the start process and it is too early to build mybatis
- * object instances.
+ * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
+ * it is too early to build mybatis object instances.
*
- * @param sqlSessionFactoryName Bean name of the {@code SqlSessionFactory}
* @since 1.1.0
+ *
+ * @param sqlSessionFactoryName
+ * Bean name of the {@code SqlSessionFactory}
*/
public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) {
this.sqlSessionFactoryBeanName = sqlSessionFactoryName;
}
/**
- * Specifies which {@code SqlSessionTemplate} to use in the case that there is
- * more than one in the spring context. Usually this is only needed when you
- * have more than one datasource.
+ * Specifies a flag that whether execute a property placeholder processing or not.
*
+ * The default is {@literal false}. This means that a property placeholder processing does not execute.
*
- * @param sqlSessionTemplate
- * @deprecated Use {@link #setSqlSessionTemplateBeanName(String)} instead
+ * @since 1.1.1
+ *
+ * @param processPropertyPlaceHolders
+ * a flag that whether execute a property placeholder processing or not
*/
- @Deprecated
- public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
- this.sqlSessionTemplate = sqlSessionTemplate;
+ public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
+ this.processPropertyPlaceHolders = processPropertyPlaceHolders;
+ }
+
+ /**
+ * The class of the {@link MapperFactoryBean} to return a mybatis proxy as spring bean.
+ *
+ * @param mapperFactoryBeanClass
+ * The class of the MapperFactoryBean
+ *
+ * @since 2.0.1
+ */
+ public void setMapperFactoryBeanClass(Class extends MapperFactoryBean> mapperFactoryBeanClass) {
+ this.mapperFactoryBeanClass = mapperFactoryBeanClass;
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ @Override
+ public void setBeanName(String name) {
+ this.beanName = name;
+ }
+
+ /**
+ * Gets beanNameGenerator to be used while running the scanner.
+ *
+ * @return the beanNameGenerator BeanNameGenerator that has been configured
+ *
+ * @since 1.2.0
+ */
+ public BeanNameGenerator getNameGenerator() {
+ return nameGenerator;
}
/**
- * Specifies which {@code SqlSessionTemplate} to use in the case that there is
- * more than one in the spring context. Usually this is only needed when you
- * have more than one datasource.
+ * Sets beanNameGenerator to be used while running the scanner.
+ *
+ * @param nameGenerator
+ * the beanNameGenerator to set
+ *
+ * @since 1.2.0
+ */
+ public void setNameGenerator(BeanNameGenerator nameGenerator) {
+ this.nameGenerator = nameGenerator;
+ }
+
+ /**
+ * Sets the default scope of scanned mappers.
*
- * Note bean names are used, not bean references. This is because the scanner
- * loads early during the start process and it is too early to build mybatis
- * object instances.
+ * Default is {@code null} (equiv to singleton).
+ *
*
- * @param sqlSessionTemplateName Bean name of the {@code SqlSessionTemplate}
- * @since 1.1.0
+ * @param defaultScope
+ * the default scope
+ *
+ * @since 2.0.6
*/
- public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) {
- this.sqlSessionTemplateBeanName = sqlSessionTemplateName;
+ public void setDefaultScope(String defaultScope) {
+ this.defaultScope = defaultScope;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ notNull(this.basePackage, "Property 'basePackage' is required");
+ }
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
+ // left intentionally blank
+ }
+
+ @Override
+ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
+ if (this.processPropertyPlaceHolders) {
+ processPropertyPlaceHolders();
+ }
+
+ var scanner = new ClassPathMapperScanner(registry, getEnvironment());
+ scanner.setAddToConfig(this.addToConfig);
+ scanner.setAnnotationClass(this.annotationClass);
+ scanner.setMarkerInterface(this.markerInterface);
+ scanner.setExcludeFilters(this.excludeFilters = mergeExcludeFilters());
+ scanner.setSqlSessionFactory(this.sqlSessionFactory);
+ scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
+ scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
+ scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
+ scanner.setResourceLoader(this.applicationContext);
+ scanner.setBeanNameGenerator(this.nameGenerator);
+ scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
+ if (StringUtils.hasText(lazyInitialization)) {
+ scanner.setLazyInitialization(Boolean.parseBoolean(lazyInitialization));
+ }
+ if (StringUtils.hasText(defaultScope)) {
+ scanner.setDefaultScope(defaultScope);
+ }
+ if (StringUtils.hasText(mapperHelperBeanName)) {
+ scanner.setMapperHelperBeanName(mapperHelperBeanName);
+ }
+ scanner.registerFilters();
+ //设置通用 Mapper
+ scanner.setMapperHelper(this.mapperHelper);
+ scanner.scan(
+ StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
+ }
+
+ /*
+ * BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
+ * PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
+ * fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
+ * definition. Then update the values.
+ */
+ private void processPropertyPlaceHolders() {
+ Map prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,
+ false, false);
+
+ if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
+ var mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
+ .getBeanDefinition(beanName);
+
+ // PropertyResourceConfigurer does not expose any methods to explicitly perform
+ // property placeholder substitution. Instead, create a BeanFactory that just
+ // contains this mapper scanner and post process the factory.
+ var factory = new DefaultListableBeanFactory();
+ factory.registerBeanDefinition(beanName, mapperScannerBean);
+
+ for (PropertyResourceConfigurer prc : prcs.values()) {
+ prc.postProcessBeanFactory(factory);
+ }
+
+ PropertyValues values = mapperScannerBean.getPropertyValues();
+
+ this.basePackage = getPropertyValue("basePackage", values);
+ this.sqlSessionFactoryBeanName = getPropertyValue("sqlSessionFactoryBeanName", values);
+ this.sqlSessionTemplateBeanName = getPropertyValue("sqlSessionTemplateBeanName", values);
+ this.lazyInitialization = getPropertyValue("lazyInitialization", values);
+ this.defaultScope = getPropertyValue("defaultScope", values);
+ this.rawExcludeFilters = getPropertyValueForTypeFilter("rawExcludeFilters", values);
+ }
+ this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
+ this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
+ .map(getEnvironment()::resolvePlaceholders).orElse(null);
+ this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
+ .map(getEnvironment()::resolvePlaceholders).orElse(null);
+ this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
+ .orElse(null);
+ this.defaultScope = Optional.ofNullable(this.defaultScope).map(getEnvironment()::resolvePlaceholders).orElse(null);
+ }
+
+ private Environment getEnvironment() {
+ return this.applicationContext.getEnvironment();
+ }
+
+ private String getPropertyValue(String propertyName, PropertyValues values) {
+ var property = values.getPropertyValue(propertyName);
+
+ if (property == null) {
+ return null;
+ }
+
+ var value = property.getValue();
+
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof String) {
+ return value.toString();
+ }
+ if (value instanceof TypedStringValue) {
+ return ((TypedStringValue) value).getValue();
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private List> getPropertyValueForTypeFilter(String propertyName, PropertyValues values) {
+ var property = values.getPropertyValue(propertyName);
+ Object value;
+ if (property == null || (value = property.getValue()) == null || !(value instanceof List>)) {
+ return null;
+ }
+ return (List>) value;
+ }
+
+ private List mergeExcludeFilters() {
+ List typeFilters = new ArrayList<>();
+ if (this.rawExcludeFilters == null || this.rawExcludeFilters.isEmpty()) {
+ return this.excludeFilters;
+ }
+ if (this.excludeFilters != null && !this.excludeFilters.isEmpty()) {
+ typeFilters.addAll(this.excludeFilters);
+ }
+ try {
+ for (Map typeFilter : this.rawExcludeFilters) {
+ typeFilters.add(
+ createTypeFilter(typeFilter.get("type"), typeFilter.get("expression"), this.getClass().getClassLoader()));
+ }
+ } catch (ClassNotFoundException exception) {
+ throw new RuntimeException("ClassNotFoundException occur when to load the Specified excludeFilter classes.",
+ exception);
+ }
+ return typeFilters;
+ }
+
+ @SuppressWarnings("unchecked")
+ private TypeFilter createTypeFilter(String filterType, String expression, @Nullable ClassLoader classLoader)
+ throws ClassNotFoundException {
+
+ if (this.processPropertyPlaceHolders) {
+ expression = this.getEnvironment().resolvePlaceholders(expression);
+ }
+
+ switch (filterType) {
+ case "annotation":
+ Class> filterAnno = ClassUtils.forName(expression, classLoader);
+ if (!Annotation.class.isAssignableFrom(filterAnno)) {
+ throw new IllegalArgumentException(
+ "Class is not assignable to [" + Annotation.class.getName() + "]: " + expression);
+ }
+ return new AnnotationTypeFilter((Class) filterAnno);
+ case "custom":
+ Class> filterClass = ClassUtils.forName(expression, classLoader);
+ if (!TypeFilter.class.isAssignableFrom(filterClass)) {
+ throw new IllegalArgumentException(
+ "Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
+ }
+ return (TypeFilter) BeanUtils.instantiateClass(filterClass);
+ case "assignable":
+ return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
+ case "regex":
+ return new RegexPatternTypeFilter(Pattern.compile(expression));
+ case "aspectj":
+ return new AspectJTypeFilter(expression, classLoader);
+ default:
+ throw new IllegalArgumentException("Unsupported filter type: " + filterType);
+ }
+ }
+
+ public void setMapperHelperBeanName(String mapperHelperBeanName) {
+ this.mapperHelperBeanName = mapperHelperBeanName;
}
/**
- * 属性注入
+ * 从环境变量中获取 mapper 配置信息
+ *
+ * @param environment
+ */
+ public void setMapperProperties(Environment environment) {
+ Config config = SpringBootBindUtil.bind(environment, Config.class, Config.PREFIX);
+ if (mapperHelper == null) {
+ mapperHelper = new MapperHelper();
+ }
+ if (config != null) {
+ mapperHelper.setConfig(config);
+ }
+ }
+
+ /**
+ * 从 properties 数组获取 mapper 配置信息
*
* @param properties
*/
- public void setProperties(Properties properties) {
- mapperHelper.setProperties(properties);
+ public void setMapperProperties(String[] properties) {
+ if (mapperHelper == null) {
+ mapperHelper = new MapperHelper();
+ }
+ Properties props = new Properties();
+ for (String property : properties) {
+ property = property.trim();
+ int index = property.indexOf("=");
+ if (index < 0) {
+ throw new MapperException("通过 @MapperScan 注解的 properties 参数配置出错:" + property + " !\n"
+ + "请保证配置项按 properties 文件格式要求进行配置,例如:\n"
+ + "properties = {\n"
+ + "\t\"mappers=tk.mybatis.mapper.common.Mapper\",\n"
+ + "\t\"notEmpty=true\"\n"
+ + "}"
+ );
+ }
+ props.put(property.substring(0, index).trim(), property.substring(index + 1).trim());
+ }
+ mapperHelper.setProperties(props);
}
}
diff --git a/spring/src/main/java/tk/mybatis/spring/mapper/SpringBootBindUtil.java b/spring/src/main/java/tk/mybatis/spring/mapper/SpringBootBindUtil.java
index de49c343b..4a0b4e3c8 100644
--- a/spring/src/main/java/tk/mybatis/spring/mapper/SpringBootBindUtil.java
+++ b/spring/src/main/java/tk/mybatis/spring/mapper/SpringBootBindUtil.java
@@ -24,14 +24,11 @@
package tk.mybatis.spring.mapper;
-import org.springframework.beans.MutablePropertyValues;
-import org.springframework.beans.PropertyValues;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
-import org.springframework.core.env.PropertyResolver;
-import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
-import java.util.Map;
/**
* @author liuzh
@@ -39,99 +36,29 @@
*/
public abstract class SpringBootBindUtil {
- public static final IBind BIND;
-
- static {
- IBind bind;
- try {
- //boot2
- Class.forName("org.springframework.boot.context.properties.bind.Binder");
- bind = new SpringBoot2Bind();
- } catch (Exception e) {
- //boot1
- bind = new SpringBoot1Bind();
- }
- BIND = bind;
- }
+ private static final Logger LOGGER = LoggerFactory.getLogger(SpringBootBindUtil.class);
public static T bind(Environment environment, Class targetClass, String prefix) {
- return BIND.bind(environment, targetClass, prefix);
- }
-
- public interface IBind {
- T bind(Environment environment, Class targetClass, String prefix);
- }
+ try {
+ Class> binderClass = Class.forName("org.springframework.boot.context.properties.bind.Binder");
+ Method getMethod = binderClass.getDeclaredMethod("get", Environment.class);
+ Method bindMethod = binderClass.getDeclaredMethod("bind", String.class, Class.class);
+ Object binder = getMethod.invoke(null, environment);
+ Object bindResult = bindMethod.invoke(binder, prefix, targetClass);
- /**
- * 使用 Spring Boot 1.x 方式绑定
- */
- public static class SpringBoot1Bind implements IBind {
- @Override
- public T bind(Environment environment, Class targetClass, String prefix) {
- /**
- 为了方便以后直接依赖 Spring Boot 2.x 时不需要改动代码,这里也使用反射
- try {
- RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment);
- Map properties = resolver.getSubProperties("");
- T target = targetClass.newInstance();
- RelaxedDataBinder binder = new RelaxedDataBinder(target, prefix);
- binder.bind(new MutablePropertyValues(properties));
- return target;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- 下面是这段代码的反射实现
- */
- try {
- //反射提取配置信息
- Class> resolverClass = Class.forName("org.springframework.boot.bind.RelaxedPropertyResolver");
- Constructor> resolverConstructor = resolverClass.getDeclaredConstructor(PropertyResolver.class);
- Method getSubPropertiesMethod = resolverClass.getDeclaredMethod("getSubProperties", String.class);
- Object resolver = resolverConstructor.newInstance(environment);
- Map properties = (Map) getSubPropertiesMethod.invoke(resolver, "");
- //创建结果类
- T target = targetClass.newInstance();
- //反射使用 org.springframework.boot.bind.RelaxedDataBinder
- Class> binderClass = Class.forName("org.springframework.boot.bind.RelaxedDataBinder");
- Constructor> binderConstructor = binderClass.getDeclaredConstructor(Object.class, String.class);
- Method bindMethod = binderClass.getMethod("bind", PropertyValues.class);
- //创建 binder 并绑定数据
- Object binder = binderConstructor.newInstance(target, prefix);
- bindMethod.invoke(binder, new MutablePropertyValues(properties));
- return target;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- }
+ // Check if the value is bound
+ Method isBoundMethod = bindResult.getClass().getDeclaredMethod("isBound");
+ boolean isBound = (boolean) isBoundMethod.invoke(bindResult);
- /**
- * 使用 Spring Boot 2.x 方式绑定
- */
- public static class SpringBoot2Bind implements IBind {
- @Override
- public T bind(Environment environment, Class targetClass, String prefix) {
- /**
- 由于不能同时依赖不同的两个版本,所以使用反射实现下面的代码
- Binder binder = Binder.get(environment);
- return binder.bind(prefix, targetClass).get();
- 下面是这两行代码的完全反射版本
- */
- try {
- Class> bindClass = Class.forName("org.springframework.boot.context.properties.bind.Binder");
- Method getMethod = bindClass.getDeclaredMethod("get", Environment.class);
- Method bindMethod = bindClass.getDeclaredMethod("bind", String.class, Class.class);
- Object bind = getMethod.invoke(null, environment);
- Object bindResult = bindMethod.invoke(bind, prefix, targetClass);
- Method resultGetMethod = bindResult.getClass().getDeclaredMethod("get");
- Method isBoundMethod = bindResult.getClass().getDeclaredMethod("isBound");
- if ((Boolean) isBoundMethod.invoke(bindResult)) {
- return (T) resultGetMethod.invoke(bindResult);
- }
+ if (isBound) {
+ Method getMethodResult = bindResult.getClass().getDeclaredMethod("get");
+ return (T) getMethodResult.invoke(bindResult);
+ } else {
return null;
- } catch (Exception e) {
- throw new RuntimeException(e);
}
+ } catch (Exception e) {
+ LOGGER.warn("Bind " + targetClass + " error", e);
+ return null;
}
}
diff --git a/spring/src/test/java/tk/mybatis/mapper/annotation/Country.java b/spring/src/test/java/tk/mybatis/mapper/annotation/Country.java
index 069b254d0..f8432ba52 100644
--- a/spring/src/test/java/tk/mybatis/mapper/annotation/Country.java
+++ b/spring/src/test/java/tk/mybatis/mapper/annotation/Country.java
@@ -24,7 +24,7 @@
package tk.mybatis.mapper.annotation;
-import javax.persistence.Id;
+import jakarta.persistence.Id;
import java.io.Serializable;
public class Country implements Serializable {
diff --git a/spring/src/test/java/tk/mybatis/mapper/annotation/SpringAnnotationTest.java b/spring/src/test/java/tk/mybatis/mapper/annotation/SpringAnnotationTest.java
index fe273f3e0..3169b5041 100644
--- a/spring/src/test/java/tk/mybatis/mapper/annotation/SpringAnnotationTest.java
+++ b/spring/src/test/java/tk/mybatis/mapper/annotation/SpringAnnotationTest.java
@@ -10,7 +10,6 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
-import tk.mybatis.mapper.MapperException;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.entity.Config;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
@@ -70,7 +69,7 @@ public void testMyBatisConfiguration() {
Assert.assertEquals(183, countries.size());
}
- @Test(expected = MapperException.class)
+ @Test(expected = Exception.class)
public void testMyBatisConfigPropertiesError() {
applicationContext.register(MyBatisConfigPropertiesError.class);
startContext();
diff --git a/spring/src/test/java/tk/mybatis/mapper/configuration/Country.java b/spring/src/test/java/tk/mybatis/mapper/configuration/Country.java
index 28f2e1221..2d6b6891d 100644
--- a/spring/src/test/java/tk/mybatis/mapper/configuration/Country.java
+++ b/spring/src/test/java/tk/mybatis/mapper/configuration/Country.java
@@ -24,7 +24,7 @@
package tk.mybatis.mapper.configuration;
-import javax.persistence.Id;
+import jakarta.persistence.Id;
import java.io.Serializable;
public class Country implements Serializable {
diff --git a/spring/src/test/java/tk/mybatis/mapper/xml/Country.java b/spring/src/test/java/tk/mybatis/mapper/xml/Country.java
index 0baa38233..260b8180d 100644
--- a/spring/src/test/java/tk/mybatis/mapper/xml/Country.java
+++ b/spring/src/test/java/tk/mybatis/mapper/xml/Country.java
@@ -24,7 +24,7 @@
package tk.mybatis.mapper.xml;
-import javax.persistence.Id;
+import jakarta.persistence.Id;
import java.io.Serializable;
public class Country implements Serializable {
diff --git a/weekend/src/test/java/tk/mybatis/mapper/weekend/entity/Country.java b/weekend/src/test/java/tk/mybatis/mapper/weekend/entity/Country.java
index 62e7700c8..48dc363c2 100644
--- a/weekend/src/test/java/tk/mybatis/mapper/weekend/entity/Country.java
+++ b/weekend/src/test/java/tk/mybatis/mapper/weekend/entity/Country.java
@@ -25,8 +25,8 @@
package tk.mybatis.mapper.weekend.entity;
-import javax.persistence.Id;
-import javax.persistence.Table;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
import java.io.Serializable;
@Table()
diff --git a/weekend/src/test/java/tk/mybatis/mapper/weekend/entity/User.java b/weekend/src/test/java/tk/mybatis/mapper/weekend/entity/User.java
index 8766af9d3..9258a5697 100644
--- a/weekend/src/test/java/tk/mybatis/mapper/weekend/entity/User.java
+++ b/weekend/src/test/java/tk/mybatis/mapper/weekend/entity/User.java
@@ -25,7 +25,7 @@
package tk.mybatis.mapper.weekend.entity;
-import javax.persistence.Table;
+import jakarta.persistence.Table;
/**
* @author Frank