The business scenario

Business scenario: After the project is divided into distributed components, it is divided into API layer, Service layer and Web layer according to modules. Orders of business entity class in com. MUSES. Taoshop. Item. The entity, and the user related entity class in com. MUSES. Taoshop. User. The entity. So, go to setTypeAliasesPackage, com.muses.taoshop.*. Entity via wildcard

The 3 styles of Ant wildcards: (1)? : Matches a character in the file name eg: com/test/entity? Match com/test/ entityAA (2) * : matches any character in the file name eg: com/*/entity Match com/test/entity (3) ** : matches multiple paths in the file name eg: Com / * * / entity match com/test/test1 / entity

Mybatis configuration class is written in the common project, some database operations can be shared, do not need to be repeated for each Web project configuration. Mybatis config class

package com.muses.taoshop.common.core.database.config; Public static final String DATA_SOURCE_NAME = "shop"; static final String DATA_SOURCE_NAME = "shop"; Public static final String DATA_SOURCE_PROPERTIES = "spring.datasource. Shop "; / / public static final String DATA_SOURCE_PROPERTIES = "spring.datasource. /** * repository repository */ public static final String REPOSITORY_PACKAGES = "com.muses.taoshop.**. Repository "; Public static final String MAPPER_PACKAGES = "com.muses.taoshop.**. Mapper "; Public static final String ENTITY_PACKAGES = "com.muses.taoshop.*. Entity "; . }Copy the code

The configuration class code, mainly focus on: factoryBean. SetTypeAliasesPackage (ENTITY_PACKAGES); The way I wrote it before was like this. Public static final String ENTITY_PACKAGES = “com.muses.taoshop.*. Entity “; Ps: Notice the wildcard character here

package com.muses.taoshop.common.core.database.config; @mapperscan (basePackages = MAPPER_PACKAGES, annotationClass = MybatisRepository. Class, sqlSessionFactoryRef = SQL_SESSION_FACTORY ) @EnableTransactionManagement @Configuration public class MybatisConfig { // omit other code, @primary @bean (name = SQL_SESSION_FACTORY) public sqlSessionFactory SqlSessionFactory (@qualifier (DATA_SOURCE_NAME)DataSource DataSource)throws Exception{//SpringBoot uses DefaultVFS for scanning by default. The vfS.addimplClass (SpringBootFS.class) entity class in the JAR is not detected. SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); //factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml")); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try{ factoryBean.setMapperLocations(resolver.getResources("classpath*:/mybatis/*Mapper.xml")); //String typeAliasesPackage = packageScanner.getTypeAliasesPackages(); / / set the TypeAliasesPackage factoryBean. SetTypeAliasesPackage (ENTITY_PACKAGES); SqlSessionFactory sqlSessionFactory = factoryBean.getObject(); return sqlSessionFactory; }catch (Exception e){ e.printStackTrace(); throw new RuntimeException(); }}... }Copy the code

Set TypeAliasesPackage in sqlSessionFactory (Mybatis can scan entity classes in XML file); factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES);

ResultType = ItemCategory;}} {ResultType = ItemCategory;}}

<! ResultType ="ItemCategory"> select <include refid="BaseColumnList" /> FROM item_category t </select>Copy the code

Simple source code analysis

For the above business scenario, first of all, we know that the execution of Mybatis will be called by SQLSessionFactory, SqlSessionFactoryBean () factoryBean () : SqlSessionFactoryBean

/** * Packages to search for type aliases. ** @since 1.0.1 ** @param typeAliasesPackage package to scan for domain objects * */ public void setTypeAliasesPackage(String typeAliasesPackage) { thisCopy the code

Let’s look at the initial Build method of the SqlSessionFactoryBean:

/** * Build a {@code SqlSessionFactory} instance. * * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a * {code SqlSessionFactory} instance based on an Reader. * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file). * * @return SqlSessionFactory * @throws IOException if loading the config file failed */ protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; // Create an XMLConfigBuilder to read some information from the configuration file. if (this.configuration ! = null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties ! = null) { configuration.getVariables().putAll(this.configurationProperties); }} Else if (this.configLocation! = null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); if (this.configurationProperties ! = null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory ! = null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory ! = null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs ! = null) { configuration.setVfsImpl(this.vfs); Return typeAliasesPackage; return tokenizeToStringArray; return tokenizeToStringArray; `String CONFIG_LOCATION_DELIMITERS = ",; \t\n"; ` */ if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : TypeAliasPackageArray) {// traversal, Registered to the configuration object on the configuration. The getTypeAliasRegistry () registerAliases (packageToScan, typeAliasesSuperType = = null? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); }}}... // omit other code}Copy the code

Then you can see how to register all aliases. How does registerAliases do this?

configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
Copy the code

To register all aliases, scan all classes under the package first:

public void registerAliases(String packageName, Class<? > superType) { ResolverUtil<Class<? >> resolverUtil = new ResolverUtil(); resolverUtil.find(new IsA(superType), packageName); // All classes obtained by ResolverUtil are assigned to a Set<Class<? extends Class<? >>> typeSet = resolverUtil.getClasses(); Iterator var5 = typeset.iterator (); while(var5.hasNext()) { Class<? > type = (Class)var5.next(); if (! type.isAnonymousClass() && ! type.isInterface() && ! type.isMemberClass()) { this.registerAlias(type); }}}Copy the code

ResolverUtil: ResolverUtil: ResolverUtil: ResolverUtil: ResolverUtil: ResolverUtil: ResolverUtil: ResolverUtil

public ResolverUtil<T> find(ResolverUtil.Test test, String path = this.getPackagePath(packageName); List<String> children = vs.getInstance ().list(path); List<String> children = vs.getinstance (). Iterator var5 = children.iterator(); While (var5.hasNext()) {String child = (String)var5.next(); if (child.endsWith(".class")) { this.addIfMatching(test, child); } } } catch (IOException var7) { log.error("Could not read package: " + packageName, var7); } return this; }Copy the code

Getting packPath is just getting the relative path

protected String getPackagePath(String packageName) {
        return packageName == null ? null : packageName.replace('.', '/');
    }
Copy the code

Check method addIfMatching:

protected void addIfMatching(ResolverUtil.Test test, String fqn) { try { String externalName = fqn.substring(0, fqn.indexOf(46)).replace('/', '.'); ClassLoader loader = this.getClassLoader(); If (log.isdebugenabled ()) {log.debug("Checking to see if class "+ externalName +" matches Criteria [" + test + "] "); } Class<? > type = loader.loadClass(externalName); If (test.matches(type)) {this.matches. Add (type); } } catch (Throwable var6) { log.warn("Could not examine class '" + fqn + "' due to a " + var6.getClass().getName() + " with message: " + var6.getMessage()); }}Copy the code

How exactly does the VFS class setInstance? Continue to follow:

GetResources (), thread.currentThread ().getContextClassLoader().getResources(), Protected static List<URL> getResources(String path) throws IOException { Return collections.list (thread.currentThread ().getContextClassLoader().getResources(path)); }... public List<String> list(String path) throws IOException { List<String> names = new ArrayList(); Iterator var3 = getResources(path).iterator(); While (var3.hasnext ()) {URL URL = (URL)var3.next(); names.addAll(this.list(url, path)); } return names; }Copy the code

Ok, this blog is just a little bit with the source code, and did not compare Mybatis source system higher level analysis. TokenizeToStringArray is a tokenizeToStringArray from sqlSessionFactoryBean class set. We then throw the array of package names to classes like ResolverUtil and VFS for a series of class-loading traversals, and then assign all classes obtained by ResolverUtil.getclasses () to Set

>> typeSet is a collection. Among them are dependencies and class loaders.
>

Support Ant wildcard settypealias package solution

From this source code relatively simple analysis process, we did not find a way to support the so-called wildcard, through the class load is also to pass a relative path to traverse, but I described the above business scenario is compatible with the wildcard situation, generally not to change the package name, if the project has a certain size.

Here is my solution:

package com.muses.taoshop.common.core.database.annotation;

import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ClassUtils;

import static com.muses.taoshop.common.core.database.config.BaseConfig.ENTITY_PACKAGES;

public class AnnotationConstants {

    public static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    public final static String PACKAGE_PATTERN =
            ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
            + ClassUtils.convertClassNameToResourcePath(ENTITY_PACKAGES)
            + DEFAULT_RESOURCE_PATTERN;

}

Copy the code

Write a scan class, refer to Hibernate AnnotationSessionFactoryBean source code

package com.muses.taoshop.common.core.database.annotation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import java.io.IOException; import java.util.*; import org.springframework.core.io.Resource; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.thymeleaf.util.StringUtils; import static com.muses.taoshop.common.core.database.annotation.AnnotationConstants.PACKAGE_PATTERN; /** ** <pre> * TypeAlicsesPackage scan class * </pre> ** @author nicky * @version 1.00.00 * <pre> * 2018.12.01 18:23 Modification: * </pre> */ @Component public class TypeAliasesPackageScanner { protected final static Logger LOGGER = LoggerFactory.getLogger(TypeAliasesPackageScanner.class); private static ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); /* private TypeFilter[] entityTypeFilters = new TypeFilter[]{new AnnotationTypeFilter(Entity.class, false), new AnnotationTypeFilter(Embeddable.class, false), new AnnotationTypeFilter(MappedSuperclass.class, false), new AnnotationTypeFilter(org.hibernate.annotations.Entity.class, false)}; */ public static String getTypeAliasesPackages() { Set<String> packageNames = new TreeSet<String>(); //TreeSet packageNames = new TreeSet(); String typeAliasesPackage =""; Try {/ / loading all the resources the Resource [] resources. = resourcePatternResolver getResources (PACKAGE_PATTERN); MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver); For (Resource Resource: resources) { if (resource.isReadable()) { MetadataReader reader = readerFactory.getMetadataReader(resource); String className = reader.getClassMetadata().getClassName(); //eg:com.muses.taoshop.item.entity.ItemBrand LOGGER.info("className : {} "+className); try{ //eg:com.muses.taoshop.item.entity LOGGER.info("packageName : {} "+Class.forName(className).getPackage().getName()); packageNames.add(Class.forName(className).getPackage().getName()); }catch (ClassNotFoundException e){ LOGGER.error("classNotFoundException : {} "+e); } } } } catch (IOException e) { LOGGER.error("ioException =>: {} " + e); } // If the set is not empty, assemble the data. CollectionUtils.isEmpty(packageNames)) { typeAliasesPackage = StringUtils.join(packageNames.toArray() , ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); }else{ LOGGER.info("set empty,size:{} "+packageNames.size()); } return typeAliasesPackage; }}Copy the code

Then just Mybatis configuration class change, mainly to change the String typeAliasesPackage = packageScanner. GetTypeAliasesPackages (); By scanning a class to get the key code in TypeAliasesPackageScanner scan type

package com.muses.taoshop.common.core.database.config; @mapperscan (basePackages = MAPPER_PACKAGES, annotationClass = MybatisRepository. Class, sqlSessionFactoryRef = SQL_SESSION_FACTORY ) @EnableTransactionManagement @Configuration public class MybatisConfig { // omit other code, @primary @bean (name = SQL_SESSION_FACTORY) public sqlSessionFactory SqlSessionFactory (@qualifier (DATA_SOURCE_NAME)DataSource DataSource)throws Exception{//SpringBoot uses DefaultVFS for scanning by default. The vfS.addimplClass (SpringBootFS.class) entity class in the JAR is not detected. SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); //factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml")); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try{ factoryBean.setMapperLocations(resolver.getResources("classpath*:/mybatis/*Mapper.xml")); String typeAliasesPackage = packageScanner.getTypeAliasesPackages(); / / set the TypeAliasesPackage factoryBean. SetTypeAliasesPackage (TypeAliasesPackage); SqlSessionFactory sqlSessionFactory = factoryBean.getObject(); return sqlSessionFactory; }catch (Exception e){ e.printStackTrace(); throw new RuntimeException(); }}Copy the code