当前位置: 首页 > news >正文

5-5 Mybatis的Resources和Spring中的Resource有什么关联吗

背景

小白:我们在单独使用Mybatis时,加载配置文件时使用的是Resources类,但Spring集成Mybatis时并没有使用Mybatis的Resources类来加载配置,而是利用Spring封装的Resource解析器来读取Mybatis的配置,这是为什么呢?

扫地僧:从功能上来讲,都是相同的;从加载对象上来看,Mybatis加载配置文件,Spring可以加载所有资源文件;总体上说,Spring因面临的资源对象种类更丰富,它需要更详细的资源分类体系,而Mybatis的资源比较固定,实现起来更简单。下面让我们探探它们的内部原理。

Mybatis中的Resources

构建SqlSessionFactory通常有两种方式:

1.使用Reader读取文件

Readerreader=Resources.getResourceAsReader("com/davidwang456/SqlMapConfig.xml");SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(reader);

2.使用InputStream读取文件

InputStreaminputStream=Resources.getResourceAsStream("com/davidwang456/SqlMapConfig.xml");SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(inputStream);

我们发现Mybatis提供了一个公共的Resources类读取配置文件。

Resources

Resources类位于org.apache.ibatis.io包下,用于处理相关的IO操作,例如将文件,网络资源进行读取并转换为File,InputStream,Reader,URL等Java类。Resources类提供了多个getResourceAsxxx()方法作为对外暴露的方法,Resources并不提供方法实现,而是内部维护着一个静态ClassLoaderWrapper实例,Resources中的方法都是通过调用ClassLoaderWrapper相关方法实现。下面列出了Resources最常用的方法。

方法名参数
File getResourceAsFile()resource,classloader
Properties getResourceAsProperties()resource,classloader
Reader getResourceAsReader()resource,classloader
InputStream getResourceAsStream()resource,classloader
URL getResourceURL()resource,classloader
Properties getUrlAsProperties()resource,classloader
setCharset()Charset
void setDefaultClassLoader(ClassLoader)ClassLoader
示例1
Propertiesp=Resources.getResourceAsProperties(null,"com/davidwang456/SqlMapConfig.xml");

此时属性p的值为:

{</configuration>=,<transactionManager=type="JDBC"/>,</environments>=,</environment>=,<environments=default="development">,<mappers>=,</dataSource>=,<dataSource=type="POOLED">,</mappers>=,<environment=id="development">,<mapper=resource="com/davidwang456/Student.xml"/>,<configuration>=,<?xml=version="1.0"encoding="UTF-8"?>,<!DOCTYPE=configurationPUBLIC"-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">,<property=name="password"value="root"/>}
示例2
Filef=Resources.getResourceAsFile(null,"com/davidwang456/SqlMapConfig.xml");System.out.println(f.getAbsolutePath());

此时运行结果为:

C:\workspace\mybatis-3\target\classes\com\davidwang456\SqlMapConfig.xml
示例3
URLurl=Resources.getResourceURL(null,"com/davidwang456/SqlMapConfig.xml");System.out.println(url);

此时运行结果为:

file:/C:/workspace/mybatis-3/target/classes/com/davidwang456/SqlMapConfig.xml

通过追踪源码,我们发现上述的实现由ClassLoaderWrapper实现,如Reader和InputStream的内部实现均为getResourceAsStream

/** * Returns a resource on the classpath as a Stream object * * @param loader The classloader used to fetch the resource * @param resource The resource to find * @return The resource * @throws java.io.IOException If the resource cannot be found or read */publicstaticInputStreamgetResourceAsStream(ClassLoaderloader,Stringresource)throwsIOException{InputStreamin=classLoaderWrapper.getResourceAsStream(resource,loader);if(in==null){thrownewIOException("Could not find resource "+resource);}returnin;}

ClassLoaderWrapper

ClassLoaderWrapper没有构造器,无法直接进行实例化,只能通过Resources类进行获取。该类提供了获取资源的两种方式,以InputStream形式读取本地资源,以URL形式读取本地资源。除此之外,ClassLoaderWrappe还提供了classForName()方法用于获取类路径对应的Class实例。下面是ClassLoaderWrapper提供的所有方法。可以看到,每种类型方法都提供了三种入参形式,其中一种方法需要提供一个ClassLoader数组,需要注意的是这种方法并不能直接调用,前两种方法都是交给带有classloader[]参数的方法进行实现的。如果不提供ClassLoader则设置为null。

  • Class<?> classForName(String name)
  • Class<?> classForName(String name, ClassLoader classLoader)
  • Class<?> classForName(String name, ClassLoader[] classLoader)
  • InputStream getResourceAsStream(String resource)
  • InputStream getResourceAsStream(String resource, ClassLoader classLoader)
  • InputStream getResourceAsStream(String resource, ClassLoader[] classLoader)
  • URL getResourceAsURL(String resource)
  • URL getResourceAsURL(String resource, ClassLoader classLoader)
  • URL getResourceAsURL(String resource, ClassLoader[] classLoader)
  • ClassLoader[] getClassLoaders(ClassLoader classLoader)

**getClassLoaders()**用于为getResouceAsxxx()方法提供多个加载器。前面说过,不传入加载器数组的方法都会调用 getResourceAsxxx(String resource, ClassLoader[] classLoader)相关的方法,而获取加载器数组的方式就是直接调用getClassLoaders()方法。

ClassLoader[]getClassLoaders(ClassLoaderclassLoader){returnnewClassLoader[]{classLoader,//自定义加载器defaultClassLoader,Thread.currentThread().getContextClassLoader(),//当前线程加载器getClass().getClassLoader(),//类加载器systemClassLoader//系统默认加载器};}

**classForName()**根据类路径生成Class实例,该方法的本质是调用classloader下的forname方法进行实例化的。

/** * Attempt to load a class from a group of classloaders * * @param name - the class to load * @param classLoader - the group of classloaders to examine * @return the class * @throws ClassNotFoundException - Remember the wisdom of Judge Smails: Well, the world needs ditch diggers, too. */Class<?>classForName(Stringname,ClassLoader[]classLoader)throwsClassNotFoundException{for(ClassLoadercl:classLoader){if(null!=cl){try{Class<?>c=Class.forName(name,true,cl);if(null!=c){returnc;}}catch(ClassNotFoundExceptione){// we'll ignore this until all classloaders fail to locate the class}}}thrownewClassNotFoundException("Cannot find class: "+name);}

**getResourceAsStream()和getResourceAsURL()😗*这两个方法都是获取相关的资源,与File类不同的是,这两个方法传递的参数必须是相对路径,否则抛出异常。同classForName()一样,必须遍历classLoader数组,然后调用getResourceAsStream()和getResource()。

ClassLoaderWrapper内部封装了JDK内部java.lang.ClassLoader的实现。

ClassLoader

ClassLoaderWrapper的getResourceAsStream调用了ClassLoader的方法getResourceAsStream:

/** * Returns an input stream for reading the specified resource. * * <p> The search order is described in the documentation for {@link * #getResource(String)}. </p> * * @param name * The resource name * * @return An input stream for reading the resource, or <tt>null</tt> * if the resource could not be found * * @since 1.1 */publicInputStreamgetResourceAsStream(Stringname){URLurl=getResource(name);try{returnurl!=null?url.openStream():null;}catch(IOExceptione){returnnull;}}

ClassLoaderWrapper的getResourceAsURL调用了ClassLoader的方法getResourceAsURL:

/** * Finds the resource with the given name. A resource is some data * (images, audio, text, etc) that can be accessed by class code in a way * that is independent of the location of the code. * * <p> The name of a resource is a '<tt>/</tt>'-separated path name that * identifies the resource. * * <p> This method will first search the parent class loader for the * resource; if the parent is <tt>null</tt> the path of the class loader * built-in to the virtual machine is searched. That failing, this method * will invoke {@link #findResource(String)} to find the resource. </p> * * @apiNote When overriding this method it is recommended that an * implementation ensures that any delegation is consistent with the {@link * #getResources(java.lang.String) getResources(String)} method. * * @param name * The resource name * * @return A <tt>URL</tt> object for reading the resource, or * <tt>null</tt> if the resource could not be found or the invoker * doesn't have adequate privileges to get the resource. * * @since 1.1 */publicURLgetResource(Stringname){URLurl;if(parent!=null){url=parent.getResource(name);}else{url=getBootstrapResource(name);}if(url==null){url=findResource(name);}returnurl;}

Spring中的Resource

Spring集成Mybatis时并没有使用Mybatis中的Resources类加载配置文件,而是利用了自己的Resource来实现配置文件的加载。

packagecom.davidwang456.mybatis.spring;importjavax.sql.DataSource;importorg.apache.ibatis.session.SqlSessionFactory;importorg.mybatis.spring.SqlSessionFactoryBean;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.io.support.PathMatchingResourcePatternResolver;importorg.springframework.jdbc.datasource.DataSourceTransactionManager;importcom.alibaba.druid.pool.DruidDataSource;@Configuration@MapperScan("com.davidwang456.mybatis.spring.mapper")publicclassSpringConfig{@BeanpublicDataSourcegetDataSource(){DruidDataSourcedataSource=newDruidDataSource();dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/davidwang456?characterEncoding=UTF-8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC");dataSource.setUsername("root");dataSource.setPassword("wangwei456");returndataSource;}@BeanpublicDataSourceTransactionManagertransactionManager(){returnnewDataSourceTransactionManager(getDataSource());}@BeanpublicSqlSessionFactorysqlSessionFactory()throwsException{PathMatchingResourcePatternResolverresolver=newPathMatchingResourcePatternResolver();SqlSessionFactoryBeansessionFactory=newSqlSessionFactoryBean();sessionFactory.setDataSource(getDataSource());sessionFactory.setConfigLocation(resolver.getResource("SqlMapConfig.xml"));sesionFactory.setMapperLocations(resolver.getResource("StudentMapper.xml"));returnsessionFactory.getObject();}}

我们深入源码看一看这个PathMatchingResourcePatternResolver是如何加载配置文件的?

@OverridepublicResourcegetResource(Stringlocation){returngetResourceLoader().getResource(location);}/** * Return the ResourceLoader that this pattern resolver works with. */publicResourceLoadergetResourceLoader(){returnthis.resourceLoader;}

其中,resourceLoader由DefaultResourceLoader实现,DefaultResourceLoader内部封装了java.lang.ClassLoader。

不同于Mybatis针对classpath下的资源文件,Spring对资源做了更多的封装,ResourceUtils定义了Spring支持的资源类型:

/** Pseudo URL prefix for loading from the class path: "classpath:" */publicstaticfinalStringCLASSPATH_URL_PREFIX="classpath:";/** URL prefix for loading from the file system: "file:" */publicstaticfinalStringFILE_URL_PREFIX="file:";/** URL prefix for loading from a jar file: "jar:" */publicstaticfinalStringJAR_URL_PREFIX="jar:";/** URL prefix for loading from a war file on Tomcat: "war:" */publicstaticfinalStringWAR_URL_PREFIX="war:";/** URL protocol for a file in the file system: "file" */publicstaticfinalStringURL_PROTOCOL_FILE="file";/** URL protocol for an entry from a jar file: "jar" */publicstaticfinalStringURL_PROTOCOL_JAR="jar";/** URL protocol for an entry from a war file: "war" */publicstaticfinalStringURL_PROTOCOL_WAR="war";/** URL protocol for an entry from a zip file: "zip" */publicstaticfinalStringURL_PROTOCOL_ZIP="zip";/** URL protocol for an entry from a WebSphere jar file: "wsjar" */publicstaticfinalStringURL_PROTOCOL_WSJAR="wsjar";/** URL protocol for an entry from a JBoss jar file: "vfszip" */publicstaticfinalStringURL_PROTOCOL_VFSZIP="vfszip";/** URL protocol for a JBoss file system resource: "vfsfile" */publicstaticfinalStringURL_PROTOCOL_VFSFILE="vfsfile";/** URL protocol for a general JBoss VFS resource: "vfs" */publicstaticfinalStringURL_PROTOCOL_VFS="vfs";/** File extension for a regular jar file: ".jar" */publicstaticfinalStringJAR_FILE_EXTENSION=".jar";/** Separator between JAR URL and file path within the JAR: "!/" */publicstaticfinalStringJAR_URL_SEPARATOR="!/";/** Special separator between WAR URL and jar part on Tomcat */publicstaticfinalStringWAR_URL_SEPARATOR="*/";

这些类型是和Spring应用息息相关的,但Mybatis用不到这么多。Spring底层实现这些资源的读取逻辑如下:

@OverridepublicResourcegetResource(Stringlocation){Assert.notNull(location,"Location must not be null");for(ProtocolResolverprotocolResolver:getProtocolResolvers()){Resourceresource=protocolResolver.resolve(location,this);if(resource!=null){returnresource;}}if(location.startsWith("/")){returngetResourceByPath(location);}elseif(location.startsWith(CLASSPATH_URL_PREFIX)){returnnewClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()),getClassLoader());}else{try{// Try to parse the location as a URL...URLurl=newURL(location);return(ResourceUtils.isFileURL(url)?newFileUrlResource(url):newUrlResource(url));}catch(MalformedURLExceptionex){// No URL -> resolve as resource path.returngetResourceByPath(location);}}}

Spring将支持的资源做了分类,不同的解析器做不同的事情。

总结

  • Mybatis和Spring对资源的访问内部都是通过ClassLoader实现的。
  • Mybatis和Spring因访问资源的场景不同,实现资源的读取逻辑也不同,Mybatis更简单,Spring更成体系。
  • Mybaits的Resources类封装了资源的访问,和Spring中定义的Resource是不同的,Resources更接近于Spring中的DefaultResourceLoader。

延伸阅读与资源

Java 工程师进阶:从 JVM 生产排障到OpenJDK原理
NumPy 从入门到生产落地:全链路实战指南(科学计算/向量化)
Redis 8 实战精讲:从 CRUD 到源码,构建高可用缓存系统
Redis 实战修炼与原理进阶
Python 3实战精进:从脚本到高并发订单引擎
python入门:Rquests从菜鸟脚本到企业级SDK的网络实战圣经
Milvus向量数据库实战修炼:从 0 到 1精通向量检索与生产落地
MongoDB 实战进阶与内核修炼
后端工程师的 AI 转型第一课:Ollama 与私有化大模型实战
10倍开发者的 Dify 魔法书:从零构建全栈 AI 应用
后端工程师转型AI第一课-Ollama 与私有化大模型实战
大型语言模型(LLM) vLLM 高性能推理落地实战
Agent开发之LlamaIndex 实战修炼与源码进阶
大语言模型Transformers 实战修炼与源码剖析

http://www.cnnetsun.cn/news/4359929.html

相关文章:

  • 安卓手机解压夸克网盘分卷压缩包全攻略:从原理到实践
  • 奇安信2020开发岗笔试考点拆解:从C/C++到安全基础
  • 划子网不用手算了:IT-Tools 3 个网络工具上手记
  • Bonsai-demo快速上手:一条命令启动本地AI助手完全教程
  • 微信小程序打卡签到案例源码解析:从解压到真机预览全流程
  • 微信小程序打卡签到源码拆解:从核心逻辑到业务改造实战
  • 赛博朋克现实诊断:科技如何重塑我们的心理、社会与数字生存
  • RevokeMsgPatcher 防撤回补丁完全指南:3步安装,让 PC 微信 QQ 撤回的消息留得住
  • MediaPipe 手部检测迁移实录:从 Legacy Solutions 到 Tasks API
  • 基于协调过滤推荐算法的校园电子图书听书系统的设计与实现(源码+文档+部署讲解等)
  • 奇安信运维开发工程师笔试经验:从Linux基础到流程设计全拆解
  • 两条命令完成PDF翻译:PDFMathTranslate简单指南,公式与排版原样保留
  • EnvHarness与SPADE实战:开发环境标准化与代码安全扫描
  • RVC部署完整指南:从安装到出声的两条路径
  • AI辅助开发:用HTML5 Canvas构建坦克大战关卡编辑器
  • 乒乓球编排软件实战经验:从赛程生成到临场调度全解析
  • 狸窝全能视频转换器免安装版:轻量便携的格式转换利器
  • OpenClaw + Ollama 开发者分享文档
  • 动力滚筒线和无动力滚筒线有什么区别?怎么选更划算?实测选型指南
  • 萨博隐身超音速无人战斗机概念:系统架构与关键技术全解析
  • AI模型认知能力评估:六个原则帮你避开评测陷阱
  • 主动RIS辅助ISAC系统联合波束成形MATLAB仿真实现详解
  • Pandas数据清洗与整形实战:Airbnb房源数据完整处理指南
  • 奇安信服务端应用开发面试复盘:四方向底层逻辑与核心能力解析
  • AI原生开发推理成本控制:从部署到调优的实战指南
  • 告别百万域名库:用eBPF动态DPI让软路由流量识别更高效
  • GSDML文件全解析:从文件名到PROFINET设备组态实战
  • 无刷电机短路炸机根因解析:从原理到排查预防的完整指南
  • Keil MDK中.s启动文件详解:从复位到main的执行流程
  • Koishi可逆插件(随时更新ing)