SpringBoot集成Mybatis
在集成Mybaits的时候遇到一个配置上的小问题,因为我的实体类都是按不同功能模块划分包的
比如:com.zhxin.logic.system.model、com.zhxin.logic.blog.model、com.zhxin.logic.monitor.model...
所以在配置的时候想用通配符来匹配实体类的包,写了如下代码:
public class MybatisConfig {
//...省略前面部分主要写出错的部分
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage("com.zhxin.logic.**.model"); // 扫描Model
}
//...省略后面部分
}
写了这段之后,启动会报错,提示实体类会找不到
error
跟踪mybatis的SqlSessionFactoryBean 的setTypeAliasesPackage()方法,查看SqlSessionFactory是如何build
/*SqlSessionFactoryBean.class*/
//...前面省略
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//...
/*
重点看这一段,其它源码先不看,这里获取到typeAliasesPackage字符串之后,调用tokenizeToStringArray进行字符串分隔返回一个数组,`String CONFIG_LOCATION_DELIMITERS = ",; \t\n";`
*/
String[] typeHandlersPackageArray;
String[] var4;
int var5;
int var6;
String packageToScan;
if (StringUtils.hasLength(this.typeAliasesPackage)) {
typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
var4 = typeHandlersPackageArray;
var5 = typeHandlersPackageArray.length;
for(var6 = 0; var6 < var5; ++var6) {
packageToScan = var4[var6];
configuration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
//...
}
//...后边省略
这里可以看到注册所有别名的方法 ,registerAliases是如何处理的?
configuration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
要扫描注册所有的别名之前先要扫描包下面的所有类:
/*TypeAliasRegistry.class*/
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new IsA(superType), packageName);
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去查找的呢,可以再继续跟一下
/*ResolverUtil.class*/
public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) {
String path = this.getPackagePath(packageName);
try {
List<String> children = VFS.getInstance().list(path);
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;
}
获取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;
}
原因分析
查看了一下源码后,稍微总结一下Mybatis对别名的注册是先将从sqlSessionFactoryBean类set的别名报名进行tokenizeToStringArray拆分成数组,然后将包名数组丢给ResolverUtil类和VFS等类进行一系列类加载遍历,之后将 resolverUtil.getClasses()获取的类都赋值给Set<Class>> typeSet 一个集合。其中也是依赖与类加载器。
支持Ant通配符方式setTypeAliasesPackage解决方案
从这个源码比较简单的分析过程,我们并没有找到支持所谓通配符的方法,通过类加载的话也是要传个相对路径去遍历,不过我上面描述的业务场景是要兼容通配符的情况的,一般不会去改包名,假如这个项目有一定规模的话。
解决方案如下
增加一个setTypeAliasesPackage()的方法,扫描自定义typeAliasesPackage目录下的所有包:
@Configuration
@MapperScan("com.zhxin.logic.**.mapper")
public class MybatisConfig {
@Autowired
private DataSource dataSource;
@Autowired
private Environment env;
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
public static String setTypeAliasesPackage(String typeAliasesPackage)
{
ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
List<String> allResult = new ArrayList<String>();
try
{
for (String aliasesPackage : typeAliasesPackage.split(","))
{
List<String> result = new ArrayList<String>();
aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
Resource[] resources = resolver.getResources(aliasesPackage);
if (resources != null && resources.length > 0)
{
MetadataReader metadataReader = null;
for (Resource resource : resources)
{
if (resource.isReadable())
{
metadataReader = metadataReaderFactory.getMetadataReader(resource);
try
{
result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
}
if (result.size() > 0)
{
HashSet<String> hashResult = new HashSet<String>(result);
allResult.addAll(hashResult);
}
}
if (allResult.size() > 0)
{
typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
}
else
{
throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
}
}
catch (IOException e)
{
e.printStackTrace();
}
return typeAliasesPackage;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
String mapperLocations = env.getProperty("mybatis.mapperLocations");
String configLocation = env.getProperty("mybatis.configLocation");
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
VFS.addImplClass(SpringBootVFS.class);
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
return sessionFactory.getObject();
}
}
网友评论