三、Resources
Java拥有标准【java.net.URL】类和各种URL前缀的标准处理程序,不幸的是,对于所有底层资源的访问来说,还不够充分。 例如,没有标准化的【URL】用来访问需要从类路径或相对于【ServletContext】获取资源的方式,而spring为我们解决了这些问题。
1️⃣Resource接口
Spring的【Resource】接口位于【org.springframework.core.io】 包,他抽象了对资源的访问的能力。 下面提供了【Resource】接口的概述, Spring本身广泛地使用了Resource接口。
public interface Resource extends InputStreamSource { boolean exists(); boolean isReadable(); boolean isOpen(); boolean isFile(); URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; ReadableByteChannel readableChannel() throws IOException; long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; String getFilename(); String getDescription(); }
2️⃣内置的 Resource的实现
Spring包含了几个内置的 Resource 实现,如下所示:
🍀(1)UrlResource
UrlResource包装了java.net.URL,可以用来访问任何需要通过URL访问的对象,例如文件、HTTPS目标、FTP目标等。 所有URL都用一个标准化的“String”表示,这样就可以使用适当的标准化前缀来表示不同类型的URL。 这包括用于访问文件系统路径的’ file: ‘,用于通过https协议访问资源的’ https: ‘,用于通过ftp访问资源的’ ftp: '等。
🍀(2)ClassPathResource
该类表示应该从【类路径】中获取的资源。 它使用线程上下文类装入器、给定的类装入器或给定的类装入资源。
🍀(3)FileSystemResource
这是【java.io】的【Resource】实现。
🍀(4)PathResource
这是一个【java.nio.file】的【资源】实现。
🍀(5)ServletContextResource
这是【ServletContext】资源的【Resource】实现,它解释了相关web应用程序根目录中的相对路径。
🍀(6)InputStreamResource
一个【InputStreamResource】是一个给定的【InputStream】的【Resource】实现。 只有当没有特定的【资源】实现适用时,才应该使用它。 特别是,如果可能的话,最好使用【ByteArrayResource】或任何基于文件的【Resource】实现。
🍀(7)ByteArrayResource
这是一个给定字节数组的【资源】实现。 它为给定的字节数组创建一个ByteArrayInputStream。
它可以从任何给定的字节数组加载内容,而不需要求助于一次性使用的InputStreamResource。
3️⃣ResourceLoader接口
ResourceLoader 接口定义了加载资源的基本能力和方式。 下面的例子显示了 ResourceLoader接口定义:
public interface ResourceLoader { Resource getResource(String location); ClassLoader getClassLoader(); }
所有应用程序上下文(applicationContext)都实现了【ResourceLoader】接口。 因此,可以所有的【应用程序上下文实现(ClassPathXmlA...)】都拥有加载资源的能力。
当您在特定的应用程序上下文中调用’ getResource() ‘时,如果指定的位置路径【没有特定的前缀】,您将返回【适合该特定应用程序上下文中】的’ Resource ‘类型。 例如,假设以下代码片段是在’ ClassPathXmlApplicationContext '实例上运行的:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
针对 ClassPathXmlApplicationContext,该代码返回’ ClassPathResource '。
针对FileSystemXmlApplicationContext实例运行相同的方法,它将返回 ‘FileSystemResource’。
针对WebApplicationContext,它会返回’ ServletContextResource '。它同样会为每个上下文返回适当的对象。
另一方面,你也可以通过指定特殊的【’ classpath: '前缀】来强制使用【ClassPathResource】,无论应用程序的上下文类型是什么,如下面的示例所示:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
类似地,您可以通过指定任何标准的java.net.URL前缀来强制使用【UrlResource】。 下面的例子使用了【file】和【https】前缀:
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
下表总结了将’ String ‘对象转换为’ Resource '对象的策略:
前缀 | 举例 | 说明 |
classpath: | classpath:com/myapp/config.xml |
从类路径加载。 |
file: | file:///data/config.xml |
作为一个“URL”从文件系统加载。 请参见’ FileSystemResource ’ Caveats。 |
https: | https://myserver/logo.png |
作为一个 URL加载。 |
(none) | /data/config.xml |
依赖于底层的 ApplicationContext 。 |
4️⃣应用环境和资源路径
本节介绍如何【使用资源】创建应用程序上下文,包括使用XML的快捷方式、使用通配符以及其他细节。
🍀(1)构建应用程序上下文
应用程序上下文构造函数通常采用【字符串或字符串数组】作为资源的位置路径,例如组成上下文定义的XML文件。
当这样的位置路径没有前缀时,从该路径构建并用于加载beanDifination的特定【Resource】类型取决于我们使用的这个特定的应用程序上下文。 例如,考虑下面的例子,它创建了一个’ ClassPathXmlApplicationContext ':
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
beanDifination是从类路径加载的,因此他使用了【ClassPathResource】。 但是,考虑下面的例子,它创建了一个’ FileSystemXmlApplicationContext ':
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
现在从【文件系统】位置加载beanDifination(在本例中,相对于当前工作目录)。
注意,在位置路径上使用特殊的【classpath前缀】或标准URL前缀会覆盖为加载beanDifination而创建的【默认类型Resource】。 考虑以下例子:
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
使用【FileSystemXmlApplicationContext】从类路径加载beanDifination。 然而,它仍然是一个“FileSystemXmlApplicationContext”。 如果它随后被用作【ResourceLoader】,任何没有前缀的路径仍然被视为文件系统路径。
🍀(2)源路径中的通配符
应用程序上下文构造函数值中的资源路径可以是简单路径,每个路径都有到【目标资源】的一对一映射。当然,也可以包含特殊的【classpath*:】前缀或【内部ant模式】, 后者实际上都是通配符。
注意,这种通配符特定于在应用程序上下文构造函数中使用资源路径(或直接使用“PathMatcher”实用程序类层次结构时),并在构造时解析。 它与“资源”类型本身无关。 你不能使用’ classpath*: '前缀来构造一个【实际的Resource】,因为一个resource一次只指向一个资源。
Ant-style的匹配原则
Ant-style 模式
路径位置可以包含ant样式的模式,如下例所示:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
当路径位置包含【ant样式模式】时,解析器将遵循更复杂的过程来尝试解析通配符。
classpath * :前缀
当构造基于xml的应用上下文时,位置字符串可以使用特殊的’ classpath*: '前缀,如下所示:
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
classpath:和classpath * :的区别
classpath: : 表示从该工程中的类路径中加载资源,classpath:和classpath:/是等价的,都是相对于类的根路径。资源文件库标准的在文件系统中,也可以在JAR或ZIP的类包中。
classpath * : 假设多个JAR包或文件系统类路径都有一个相同的配置文件,classpath:只会在第一个加载的类路径下查找,而【classpath*:】会扫描所有这些JAR包及类路径下出现的同名文件。
四、验证、数据绑定和类型转换
1️⃣BeanWrapper
bean包中一个非常重要的类是【BeanWrappe】接口及其相应的实现(【BeanWrapperImpl】)。 正如在javadoc中引用的,【BeanWrapper】提供了【设置和获取属性值】、【获取属性描述符】等功能。 此外,【BeanWrapper】提供了对嵌套属性的支持,允许对子属性进行无限深度的检索。 说的简单一点,就是这个类能帮助我对使用更简单的api通过反射操作一个bean的属性。
我们以设置和获取基本和嵌套属性为例
设置和获取属性是通过【BeanWrapper】的’ setPropertyValue ‘和’ getPropertyValue '重载方法变体来完成的。 下表显示了这些约定的一些例子:
表达式 | 释义 |
name |
指示属性“name”对应于“getName()”或“isName()”和“setName(…)”方法。 |
account.name |
指示属性’ account ‘的嵌套属性’ name ‘,该属性对应于(例如)’ getAccount(). setname() ‘或’ getAccount(). getname() '方法。 |
account[2] |
指示索引属性’ account ‘的third元素。 索引属性的类型可以是’ array ‘、’ list '或其他自然有序的集合。 |
account[COMPANYNAME] |
指示由“account”、“map”属性的“COMPANYNAME”键索引的映射条目的值 |
下面两个示例类使用’ BeanWrapper '来获取和设置属性:
public class Company { private String name; private Employee managingDirector; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Employee getManagingDirector() { return this.managingDirector; } public void setManagingDirector(Employee managingDirector) { this.managingDirector = managingDirector; } }
public class Employee { private String name; private float salary; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public float getSalary() { return salary; } public void setSalary(float salary) { this.salary = salary; } }
下面的代码片段展示了如何【检索和操作】实例化后的’ Company ’ 和’ Employee ’ 的一些属性:
BeanWrapper company = new BeanWrapperImpl(new Company()); // setting the company name.. company.setPropertyValue("name", "Some Company Inc."); // ... can also be done like this: PropertyValue value = new PropertyValue("name", "Some Company Inc."); company.setPropertyValue(value); // ok, let's create the director and tie it to the company: BeanWrapper jim = new BeanWrapperImpl(new Employee()); jim.setPropertyValue("name", "Jim Stravinsky"); company.setPropertyValue("managingDirector", jim.getWrappedInstance()); // retrieving the salary of the managingDirector through the company Float salary = (Float) company.getPropertyValue("managingDirector.salary");