All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mybatis.spring.mapper.MapperScannerConfigurer Maven / Gradle / Ivy

The newest version!
/*
 * 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.mapper;

import static org.springframework.util.Assert.notNull;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
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.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * 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. *

* 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 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 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 * * @see MapperFactoryBean * @see ClassPathMapperScanner */ public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { private String basePackage; private boolean addToConfig = true; private String lazyInitialization; private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; private String sqlSessionFactoryBeanName; private String sqlSessionTemplateBeanName; private Class annotationClass; private Class markerInterface; private List excludeFilters; private List> rawExcludeFilters; private Class mapperFactoryBeanClass; private ApplicationContext applicationContext; private String beanName; private boolean processPropertyPlaceHolders; private BeanNameGenerator nameGenerator; private String defaultScope; /** * 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 basePackage * base package name */ 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) { this.addToConfig = addToConfig; } /** * 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(String lazyInitialization) { this.lazyInitialization = 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. *

* Note this can be combined with markerInterface. * * @param annotationClass * annotation class */ public void setAnnotationClass(Class annotationClass) { this.annotationClass = annotationClass; } /** * 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 */ public void setMarkerInterface(Class superClass) { this.markerInterface = superClass; } /** * Specifies which types are not eligible for the mapper scanner. *

* The scanner will exclude types that define with excludeFilters. * * @since 3.0.3 * * @param excludeFilters * list of TypeFilter */ public void setExcludeFilters(List excludeFilters) { this.excludeFilters = excludeFilters; } /** * In order to support process PropertyPlaceHolders. *

* After parsed, it will be added to excludeFilters. * * @since 3.0.3 * * @param rawExcludeFilters * list of rawExcludeFilter */ public void setRawExcludeFilters(List> rawExcludeFilters) { this.rawExcludeFilters = rawExcludeFilters; } /** * 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. *

* * @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 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. * * @since 1.1.0 * * @param sqlSessionTemplateName * Bean name of the {@code SqlSessionTemplate} */ public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) { this.sqlSessionTemplateBeanName = sqlSessionTemplateName; } /** * 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. *

* * @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead. * * @param sqlSessionFactory * a factory of SqlSession */ @Deprecated public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { this.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. *

* 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. * * @since 1.1.0 * * @param sqlSessionFactoryName * Bean name of the {@code SqlSessionFactory} */ public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) { this.sqlSessionFactoryBeanName = sqlSessionFactoryName; } /** * 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. * * @since 1.1.1 * * @param processPropertyPlaceHolders * a flag that whether execute a property placeholder processing or not */ 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 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; } /** * 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. *

* Default is {@code null} (equiv to singleton). *

* * @param defaultScope * the default scope * * @since 2.0.6 */ 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); } scanner.registerFilters(); 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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy