业务场景

业务场景:首先项目进行分布式拆分之后,按照模块再分为为api层和service层,web层。

其中订单业务的实体类放在com.muses.taoshop.item.entity,而用户相关的实体类放在com.muses.taoshop.user.entity。所以就这样,通过通配符方式去setTypeAliasesPackage ,com.muses.taoshop.*.entity

Ant通配符的3中风格:

(1) ?:匹配文件名中的一个字符 eg: com/test/entity? 匹配 com/test/entityaa

(2) * : 匹配文件名中的任意字符 eg: com/*/entity 匹配 com/test/entity

(3) ** : 匹配文件名中的多重路径 eg: com/**/entity 匹配 com/test/test1/entity

mybatis配置类写在common工程,数据库操作有些是可以共用的,不需要每个web工程都进行重复配置。

所以写了个Mybatis配置类:

package com.muses.taoshop.common.core.database.config;
public class BaseConfig { /**
* 设置主数据源名称
*/
public static final String DATA_SOURCE_NAME = "shop"; /**
* 加载配置文件信息
*/
public static final String DATA_SOURCE_PROPERTIES = "spring.datasource.shop"; /**
* repository 所在包
*/
public static final String REPOSITORY_PACKAGES = "com.muses.taoshop.**.repository"; /**
* mapper 所在包
*/
public static final String MAPPER_PACKAGES = "com.muses.taoshop.**.mapper"; /**
* 实体类 所在包
*/
public static final String ENTITY_PACKAGES = "com.muses.taoshop.*.entity";
... }

贴一下配置类代码,主要关注: factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES);之前的写法是这样的。ENTITY_PACKAGES是个常量:public static final String ENTITY_PACKAGES = "com.muses.taoshop.*.entity";,ps:注意了,这里用了通配符

package com.muses.taoshop.common.core.database.config;
//省略代码
@MapperScan(
basePackages = MAPPER_PACKAGES,
annotationClass = MybatisRepository.class,
sqlSessionFactoryRef = SQL_SESSION_FACTORY
)
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
//省略其它代码,主要看sqlSessionFactory配置
@Primary
@Bean(name = SQL_SESSION_FACTORY)
public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME)DataSource dataSource)throws Exception{
//SpringBoot默认使用DefaultVFS进行扫描,但是没有扫描到jar里的实体类
VFS.addImplClass(SpringBootVFS.class);
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();
//设置一下TypeAliasesPackage
factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES);
SqlSessionFactory sqlSessionFactory = factoryBean.getObject();
return sqlSessionFactory;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException();
}
} ... }

ps:原先做法:在sqlSessionFactory方法里进行TypeAliasesPackage设置,(让Mybatis能够扫描到实体类,在xml文件里就不需要写全实体类的全包名了。)factoryBean.setTypeAliasesPackage(ENTITY_PACKAGES);

但是运行之后都会报错,提示实体类不能扫描到,因为我的service工程里都是这样写的,ResultType进行用别名ItemCategory,例子:

 <!-- 获取所有的商品品类信息-->
<select id="listCategory" resultType="ItemCategory">
SELECT
<include refid="BaseColumnList" />
FROM item_category t
</select>

源码简单分析

针对上面的业务场景,首先的分析一下,我们知道Mybatis的执行都会通过SQLSessionFactory去调用,调用前都是先用SqlSessionFactoryBean的setTypeAliasesPackage可以看一下SqlSessionFactoryBean的源码:

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

我们看一下SqlSessionFactoryBean的初步Build方法:

/**
* 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;
//创建一个XMLConfigBuilder读取配置文件的一些信息
XMLConfigBuilder xmlConfigBuilder = null;
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);//添加Properties属性
}
} 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);
}
/*
重点看这里,其它源码先不看,这里获取到typeAliasesPackage字符串之后,调用tokenizeToStringArray进行字符串分隔返回一个数组,`String CONFIG_LOCATION_DELIMITERS = ",; \t\n";`
*/
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {//遍历,注册到configuration对象上
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
...
//省略其它代码
}

然后可以看到注册所有别名的方法 ,registerAliases是怎么做的?

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

要扫描注册所有的别名之前先要扫描包下面的所有类

public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new IsA(superType), packageName);
//通过ResolverUtil获取到的所有类都赋值给一个集合
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);
}
} }

ResolverUtil是怎么通过packageName去查找的呢,可以再继续跟一下

 public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) {
//获取包路径
String path = this.getPackagePath(packageName); try {
//VFS类用单例模式实现,先获取集合
List<String> children = VFS.getInstance().list(path);
Iterator var5 = children.iterator();
//遍历,.class文件的选出来
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;
}

获取packPath只是获取一下相对路径

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

校验方法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());
} }

VFS类具体是怎么setInstance的?继续跟:

//这里的关键点就是getResources,Thread.currentThread().getContextClassLoader().getResources(),其实总结一下Mybatis的类扫描还是要依赖与jdk提供的类加载器
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;
}

ok,本博客只是稍微跟一下源码,并没有对Mybatis的源码进行比较系统更高层次的分析。

跟了一下源码知道,稍微总结一下Mybatis对别名的注册是先将从sqlSessionFactoryBean类set的别名报名进行tokenizeToStringArray拆分成数组,然后将包名数组丢给ResolverUtil类和VFS等类进行一系列类加载遍历,之后将 resolverUtil.getClasses()获取的类都赋值给Set<Class>> typeSet 一个集合。其中也是依赖与类加载器。

支持Ant通配符方式setTypeAliasesPackage解决方案

从这个源码比较简单的分析过程,我们并没有找到支持所谓通配符的方法,通过类加载的话也是要传个相对路径去遍历,不过我上面描述的业务场景是要兼容通配符的情况的,一般不会去改包名,假如这个项目有一定规模的话。

下面给出我的解决方案:

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; }

写一个扫描类,代码参考Hibernate的AnnotationSessionFactoryBean源码

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的扫描类
* </pre>
*
* @author nicky
* @version 1.00.00
* <pre>
* 修改记录
* 修改后版本: 修改人: 修改日期: 2018.12.01 18:23 修改内容:
* </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 {
//加载所有的资源
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 (!CollectionUtils.isEmpty(packageNames)) {
typeAliasesPackage = StringUtils.join(packageNames.toArray() , ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
}else{
LOGGER.info("set empty,size:{} "+packageNames.size());
}
return typeAliasesPackage;
} }

然后刚才的Mybatis配置类改一下,主要改 String typeAliasesPackage = packageScanner.getTypeAliasesPackages();通过扫描类去获取一下,重点代码在TypeAliasesPackageScanner 扫描类

package com.muses.taoshop.common.core.database.config;
//省略代码
@MapperScan(
basePackages = MAPPER_PACKAGES,
annotationClass = MybatisRepository.class,
sqlSessionFactoryRef = SQL_SESSION_FACTORY
)
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
//省略其它代码,主要看sqlSessionFactory配置
@Primary
@Bean(name = SQL_SESSION_FACTORY)
public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME)DataSource dataSource)throws Exception{
//SpringBoot默认使用DefaultVFS进行扫描,但是没有扫描到jar里的实体类
VFS.addImplClass(SpringBootVFS.class);
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();
//设置一下TypeAliasesPackage
factoryBean.setTypeAliasesPackage(typeAliasesPackage);
SqlSessionFactory sqlSessionFactory = factoryBean.getObject();
return sqlSessionFactory;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException();
}
}

Mybatis3.2不支持Ant通配符TypeAliasesPackage扫描的解决方案的更多相关文章

  1. SpringMVC(二):RequestMapping修饰类、指定请求方式、请求参数或请求头、支持Ant路径

    @RequestMapping用来映射请求:RequestMapping可以修饰方法外,还可以修饰类 1)SpringMVC使用@RequestMapping注解为控制指定可以处理哪些URL请求: 2 ...

  2. ant 通配符

    ant 通配符 我们常用的匹配模式有ANT模式,比如acegi可以用PATTERN_TYPE_APACHE_ANT来使用ANT匹配模式,那什么是ANT匹配模式呢.   ANT通配符有三种:     通 ...

  3. ant通配符

    ANT通配符有三种: 通配符 说明 ? 匹配任何单字符 * 匹配0或者任意数量的字符 ** 匹配0或者更多的目录 例子: URL路径 说明 /app/*.x 匹配(Matches)所有在app路径下的 ...

  4. Mybatis 自定义SqlSessionFactoryBean扫描通配符typeAliasesPackage

    typeAliasesPackage 默认只能扫描某一个路径下,或以逗号等分割的 几个路径下的内容,不支持通配符和正则,采用重写的方式解决 package com.xxxx.xxx.util.comm ...

  5. SpringMvc Ant通配符的使用

    @RequestMapping使用通配符来对地址进行映射 Ant 的3风格 – ? 匹配文件名中的一个字符 – *  匹配文件名中的任意字符 – ** ** 匹配多重路径 例如:RequestMapp ...

  6. 支持nmap批量漏洞扫描的script

    NSE脚本,会在NMAP扫描时,通过-sV发现的端口详细信息对应到CVE相应的漏洞: nmap -sS -sV --script=vulscan www.scip.ch -p25 下面是该脚本的说明和 ...

  7. ANT 通配符使用说明

    通配符说明 通配符 说明 ? 匹配任意一个字符 * 匹配零个.一个.多个字符 ** 匹配零个.一个.多个目录 使用示例 URL路径 说明 /app/p?ttern 匹配 /app/pattern 和 ...

  8. unity编辑器的搜索框好特么坑啊,居然不支持*号通配符

    上图 t:Scene或者点搜索框旁边的 分类按钮 用*.unity是什么也搜索不出来的

  9. 转 Fortofy扫描漏洞解决方案

    Log Forging漏洞: 数据从一个不可信赖的数据源进入应用程序. 在这种情况下,数据经由CreditCompanyController.java 的第 53行进入 getParameter(). ...

随机推荐

  1. 安卓学习第一节--环境搭建及Android Studio 安装

    1.安装JDK 2.安装AS 安装参考网址 https://www.cnblogs.com/xiadewang/p/7820377.html 下载网址: http://www.android-stud ...

  2. Windows操作系统发展历程

    1964年贝尔实验室(Bell).麻省理工学院(MIT)及美国通用电气公司(GE)为了开发出一套安装在大型主机上多人多工的操作系统开发了Multics系统.Multics是一个全面的,通用编程系统.后 ...

  3. Sublime Text 3安装emmet(ZenCoding)

    1.安装 Package Ctrol: 使用 ctrl + - 打开控制台,输入以下代码 import urllib.request,os; pf = 'Package Control.sublime ...

  4. 【机器学习】异常检测算法(I)

    在给定的数据集,我们假设数据是正常的 ,现在需要知道新给的数据Xtest中不属于该组数据的几率p(X). 异常检测主要用来识别欺骗,例如通过之前的数据来识别新一次的数据是否存在异常,比如根据一个用户以 ...

  5. jquery选择器基础知识(复制w3c)

    jQuery 元素选择器 jQuery 使用 CSS 选择器来选取 HTML 元素. $("p") 选取 <p> 元素. $("p.intro") ...

  6. [C#.net]获取文本文件的编码,自动区分GB2312和UTF8

    昨天生产突然反馈上传的结果查询出现了乱码,我赶紧打开后台数据库,发现果真有数据变成了乱码.这个上传程序都运行3个多月了,从未发生乱码现象,查看程序的运行日志,发现日志里的中文都变成了乱码,然后对比之前 ...

  7. HttpWebRequest请求Https协议的WebApi

    public static class RequestClient { /// <summary> /// 参数列表转为string /// </summary> /// &l ...

  8. 一、PTA实验作业

    一.PTA实验作业 1.题目1: 6-2 线性表元素的区间删除 2. 设计思路 定义i,j; 判断L,minD,maxD; while(i<l->Last) { 判断所有满足条件的数,de ...

  9. Lambda表达式按字段名字排序

    using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; us ...

  10. contaner

    what Container技术是直接将一个应用程序所需的相关程序代码.函式库.环境配置文件都打包起来建立沙盒执行环境 history 早在1982年,Unix系统内建的chroot机制也是一种Con ...