Spring常用工具类
本文介绍一些常用的Spring工具类, 其中大部分Spring工具类不但可以在基于Spring的应用中使用, 还可以在其它的应用中使用. 使用JDK的文件操作类在访问类路径相关、Web上下文相关的文件资源时, 往往显得拖泥带水、拐弯抹角, Spring的Resource实现类使这些工作变得轻松了许多.
1. 文件相关工具
文件资源的操作是应用程序中常见的功能, 如当上传一个文件后将其保存在特定目录下, 从指定地址加载一个配置文件等等. 相比于 JDK 的 File, Spring 的 Resource 接口资源概念的描述接口抽象层面更高且涵盖, Spring 提供了许多方便易用的资源操作工具类, 它们大大降低资源操作的复杂度, 同时具有更强的普适性. 这些工具类不依赖于 Spring 容器, 可以在程序中象一般普通类一样使用它们.
Spring 定义了一个 org.springframework.core.io.Resource 接口, Resource 接口是为了统一各种类型不同的资源而定义的, Spring 提供了若干 Resource 接口的实现类, 这些实现类可以轻松地加载不同类型的底层资源, 并提供了获取文件名、URL 地址以及资源内容的操作方法.
1.1 文件读取
- FileSystemResource: 以文件系统绝对路径的方式进行访问;
- ClassPathResource: 以类路径的方式进行访问;
- ServletContextResource: 以相对于 Web 应用根目录的方式进行访问.
- UrlResource : 访问位于远程服务器Web 服务器或 FTP 服务器的文件资源
为了方便访问不同类型的资源, 必须使用相应的 Resource 实现类, 是否可以在不显式使用 Resource 实现类的情况下, 仅根据带特殊前缀的资源地址直接加载文件资源呢? Spring 提供了一个 ResourceUtils 工具类, 它支持classpath:和file:的地址前缀, 它能够从指定的地址加载文件资源.
File clsFile = ResourceUtils.getFile("classpath:conf/file1.txt");
System.out.println(clsFile.isFile());
File httpFile = ResourceUtils.getFile("file:D:/masterSpring/chapter23/src/conf/file1.txt");
System.out.println(httpFile.isFile());
Spring 提供的 LocalizedResourceHelper 允许通过文件资源基名和本地化实体获取匹配的本地化文件资源并以 Resource 对象返回. 假设在类路径的 i18n 目录下, 拥有一组基名为 message 的本地化文件资源, 我们通过以下代码获取对应中文和英文的本地化文件资源
LocalizedResourceHelper localizedResourceHelper = new LocalizedResourceHelper();
Resource msgUs = localizedResourceHelper.findLocalizedResource("i18n/message", ".properties", Locale.US);
Resource msgCn = localizedResourceHelper.findLocalizedResource("i18n/message", ".properties", Locale.CHINA);
虽然 JDK 的 java.util.ResourceBundle 类也可以通过相似的方式获取本地化文件资源, 但是其返回的是 ResourceBundle 类型的对象. 如果您决定统一使用 Spring 的 Resource 接表征文件资源, 那么 LocalizedResourceHelper 就是获取文件资源的非常适合的帮助类了.
1.2 文件操作
在使用各种 Resource 接口的实现类加载文件资源后, 经常需要对文件资源进行读取、拷贝、转存等不同类型的操作. 可以通过 Resource 接口所提供了方法完成这些功能, 不过在大多数情况下, 通过 Spring 为 Resource 所配备的工具类完成文件资源的操作将更加方便.
Resource resource = null;
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getFile());
String string = FileCopyUtils.copyToString(new FileReader(resource.getFile()));
FileCopyUtils.copy(resource.getFile(), new File(""));
往往我们都通过直接操作 InputStream 读取文件的内容, 但是流操作的代码是比较底层的, 代码的面向对象性并不强. 通过 FileCopyUtils 读取和拷贝文件内容易于操作且相当直观. FileCopyUtils 还提供了多个将文件内容拷贝到各种目标对象中的方法.
1.3 属性操作
可以通过 java.util.Properties 的 load(InputStream inStream) 方法从一个输入流中加载属性资源. Spring 提供的 PropertiesLoaderUtils 允许您直接通过基于类路径的文件地址加载属性资源, 一般情况下, 应用程序的属性文件都放置在类路径下, 所以 PropertiesLoaderUtils 比之于 Properties#load(InputStream inStream) 方法显然具有更强的实用性.
Properties props = PropertiesLoaderUtils.loadAllProperties("jdbc.properties");
System.out.println(props.getProperty("jdbc.driverClassName"));
1.4 处理特殊编码
使用 Resource 实现类加载文件资源时, 默认采用操作系统的编码格式. 如果文件资源采用了特殊的编码格式如 UTF-8, 则在读取资源内容时必须事先通过 EncodedResource 指定编码格式, 否则将会产生中文乱码的问题.
Resource resource = new ClassPathResource("");
EncodedResource encResource = new EncodedResource(resource, "UTF-8");
String content = FileCopyUtils.copyToString(encResource.getReader());
2. Web相关工具
使用 Spring 框架开发 Web 的应用时, Spring 为 Web 应用提供了很多有用的工具类, 这些工具类可以给您的程序开发带来很多便利.
使用 Spring 框架开发 Web 的应用时, Spring 为 Web 应用提供了很多有用的工具类, 这些工具类可以给您的程序开发带来很多便利.
2.1 WebApplicationContextUtils
当 Web 应用集成 Spring 容器后, 代表 Spring 容器的 WebApplicationContext 对象将以 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 为键存放在 ServletContext 属性列表中. 可以通过位于 org.springframework.web.context.support 包中的 WebApplicationContextUtils 工具类获取 WebApplicationContext
当 ServletContext 属性列表中不存在 WebApplicationContext 时, getWebApplicationContext() 方法不会抛出异常, 它简单地返回 null. 如果后续代码直接访问返回的结果将引发一个 NullPointerException 异常, 而 WebApplicationContextUtils 中 getRequiredWebApplicationContext(ServletContext sc) 方法要求 ServletContext 属性列表中一定要包含一个有效的 WebApplicationContext 对象, 否则马上抛出一个 IllegalStateException 异常.
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(servletContext);
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)
2.2 WebUtils
位于 org.springframework.web.util 包中的 WebUtils 是一个非常好用的工具类, 它对很多 Servlet API 提供了易用的代理方法, 降低了访问 Servlet API 的复杂度, 可以将其看成是常用 Servlet API 方法的门面类.
| 方法名 | 方法说明 |
|---|---|
| String getRealPath(ServletContext servletContext, String path) | 获取相对路径对应文件系统的真实文件路径 |
| File getTempDir(ServletContext servletContext) | 获取 ServletContex 对应的临时文件地址, 它以 File 对象的形式返回 |
| Cookie getCookie(HttpServletRequest request, String name) | 获取 HttpServletRequest 中特定名字的 Cookie 对象. 如果您需要创建 Cookie, Spring 也提供了一个方便的 CookieGenerator 工具类 |
| Object getSessionAttribute(HttpServletRequest request, String name) | 获取 HttpSession 特定属性名的对象, 否则您必须通过 request.getHttpSession.getAttribute(name) 完成相同的操作 |
| Object getRequiredSessionAttribute(HttpServletRequest request, String name) | 和上一个方法类似, 只不过强制要求 HttpSession 中拥有指定的属性, 否则抛出异常 |
| String getSessionId(HttpServletRequest request) | 获取 Session ID 的值 |
| void exposeRequestAttributes(ServletRequest request, Map attributes) | 将 Map 元素添加到 ServletRequest 的属性列表中, 当请求被导向forward到下一个处理程序时, 这些请求属性就可以被访问到了 |
3. 过滤器和监听器
Spring 为 Web 应用提供了几个过滤器和监听器, 在适合的时间使用它们, 可以解决一些常见的 Web 应用问题.
3.1 延迟加载过滤器
Hibernate 允许对关联对象、属性进行延迟加载, 但是必须保证延迟加载的操作限于同一个 Hibernate Session 范围之内进行. 如果 Service 层返回一个启用了延迟加载功能的领域对象给 Web 层, 当 Web 层访问到那些需要延迟加载的数据时, 由于加载领域对象的 Hibernate Session 已经关闭, 这些导致延迟加载数据的访问异常.
Spring 为此专门提供了一个 OpenSessionInViewFilter 过滤器, 它的主要功能是使每个请求过程绑定一个 Hibernate Session, 即使最初的事务已经完成了, 也可以在 Web 层进行延迟加载的操作.
OpenSessionInViewFilter 过滤器将 Hibernate Session 绑定到请求线程中, 它将自动被 Spring 的事务管理器探测到. 所以 OpenSessionInViewFilter 适用于 Service 层使用 HibernateTransactionManager 或 JtaTransactionManager 进行事务管理的环境, 也可以用于非事务只读的数据操作中.
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate4.support.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
3.2 中文乱码过滤器
通过表单向服务器提交数据时, 一个经典的问题就是中文乱码问题. 虽然我们所有的 JSP 文件和页面编码格式都采用 UTF-8, 但这个问题还是会出现. 解决的办法很简单, 我们只需要在 web.xml 中配置一个 Spring 的编码转换过滤器就可以了
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
3.3 请求跟踪日志过滤器
除了以上两个常用的过滤器外, 还有两个在程序调试时可能会用到的请求日志跟踪过滤器, 它们会将请求的一些重要信息记录到日志中, 方便程序的调试. 这两个日志过滤器只有在日志级别为 DEBUG 时才会起作用
| 类名 | 方法说明 |
|---|---|
| org.springframework.web.filter.ServletContextRequestLoggingFilter | 该过滤器将请求的 URI 记录到 Common 日志中(如通过 Log4J 指定的日志文件) |
| org.springframework.web.filter.ServletContextRequestLoggingFilter | 该过滤器将请求的 URI 记录到 ServletContext 日志中 |
通过这个请求跟踪日志, 程度调试者可以详细地查看到有哪些请求被调用, 请求的参数是什么, 请求是否正确返回等信息. 虽然这两个请求跟踪日志过滤器一般在程序调试时使用, 但是即使程序部署不将其从 web.xml 中移除也不会有大碍, 因为只要将日志级别设置为 DEBUG 以上级别, 它们就不会输出请求跟踪日志信息了.
3.4 WebAppRootListener 监听器与Log4jConfigListener 监听器
Spring 在 org.springframework.web.util 包中提供了几个特殊用途的 Servlet 监听器, 正确地使用它们可以完成一些特定需求的功能. 比如某些第三方工具支持通过 ${key} 的方式引用系统参数即可以通过 System.getProperty() 获取的属性, WebAppRootListener 可以将 Web 应用根目录添加到系统参数中, 对应的属性名可以通过名为“webAppRootKey”的 Servlet 上下文参数指定, 默认为“webapp.root”.
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>rootKey</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.WebAppRootListener</listener-class>
</listener>
这样, 就可以在程序中通过 System.getProperty(rootKey) 获取 Web 应用的根目录了. 不过更常见的使用场景是在第三方工具的配置文件中通过 ${rootKey} 引用 Web 应用的根目录. 比如以下的 log4j.properties 配置文件就通过 ${rootKey} 设置了日志文件的地址:
log4j.rootLogger=INFO,R
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=${rootKey}/WEB-INF/logs/log4j.log
另一个专门用于 Log4J 的监听器是 Log4jConfigListener. 一般情况下, 您必须将 Log4J 日志配置文件以 log4j.properties 为文件名并保存在类路径下. Log4jConfigListener 允许您通过 log4jConfigLocation Servlet 上下文参数显式指定 Log4J 配置文件的地址, 如下所示:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
Log4jConfigListener 监听器包括了 WebAppRootListener 的功能, 也就是说, Log4jConfigListener 会自动完成将 Web 应用根目录以 webAppRootKey 上下文参数指定的属性名添加到系统参数中, 所以当您使用 Log4jConfigListener 后, 就没有必须再使用 WebAppRootListener 了.
3.5 Introspector 缓存清除监听器
Spring 还提供了一个名为 org.springframework.web.util.IntrospectorCleanupListener 的监听器. 它主要负责处理由 JavaBean Introspector 功能而引起的缓存泄露. IntrospectorCleanupListener 监听器在 Web 应用关闭的时会负责清除 JavaBean Introspector 的缓存, 在 web.xml 中注册这个监听器可以保证在 Web 应用关闭的时候释放与其相关的 ClassLoader 的缓存和类引用.
如果使用了 JavaBean Introspector 分析应用中的类, Introspector 缓存会保留这些类的引用, 结果在应用关闭的时候, 这些类以及 Web 应用相关的 ClassLoader 不能被垃圾回收. 不幸的是, 清除 Introspector 的唯一方式是刷新整个缓存, 这是因为没法准确判断哪些是属于本 Web 应用的引用对象, 哪些是属于其它 Web 应用的引用对象. 所以删除被缓存的 Introspection 会导致将整个 JVM 所有应用的 Introspection 都删掉.
需要注意的是, Spring 托管的 Bean 不需要使用这个监听器, 因为 Spring 的 Introspection 所使用的缓存在分析完一个类之后会马上从 javaBean Introspector 缓存中清除掉, 并将缓存保存在应用程序特定的 ClassLoader 中, 所以它们一般不会导致内存资源泄露. 但是一些类库和框架往往会产生这个问题. 例如 Struts 和 Quartz 的 Introspector 的内存泄漏会导致整个的 Web 应用的 ClassLoader 不能进行垃圾回收. 在 Web 应用关闭之后, 还会看到此应用的所有静态类引用, 这个错误当然不是由这个类自身引起的. 解决这个问题的方法很简单, 仅需在 web.xml 中配置 IntrospectorCleanupListener 监听器就可以了
4. 特殊字符转义
由于 Web 应用程序需要联合使用到多种语言, 每种语言都包含一些特殊的字符, 对于动态语言或标签式的语言而言, 如果需要动态构造语言的内容时, 一个我们经常会碰到的问题就是特殊字符转义的问题. 如果不对这些特殊字符进行转义处理, 则不但可能破坏文档结构, 还可以引发潜在的安全问题.
Spring 为 HTML 和 JavaScript 特殊字符提供了转义操作工具类, 它们分别是 HtmlUtils 和 JavaScriptUtils.
即使没有处理 HTML 或 JavaScript 的特殊字符, 也不会带来灾难性的后果, 但是如果不在动态构造 SQL 语句时对变量中特殊字符进行处理, 将可能导致程序漏洞、数据盗取、数据破坏等严重的安全问题. 为了防止他人使用特殊 SQL 字符破坏 SQL 的语句结构或植入恶意操作, 必须在变量拼接到 SQL 语句之前对其中的特殊字符进行转义处理.
Spring 并没有提供相应的工具类, 可以通过 jakarta commons lang 通用类包中spring/lib/jakarta-commons/commons-lang.jar的 StringEscapeUtils 完成这一工作. 事实上, StringEscapeUtils 不但提供了 SQL 特殊字符转义处理的功能, 还提供了 HTML、XML、JavaScript、Java 特殊字符的转义和还原的方法. 如果不介意引入 jakarta commons lang 类包, 更推荐使用 StringEscapeUtils 工具类完成特殊字符转义处理的工作.
5. 方法入参检测工具类
Web 应用在接受表单提交的数据后都需要对其进行合法性检查, 如果表单数据不合法, 请求将被驳回. 类似的, 当我们在编写类的方法时, 也常常需要对方法入参进行合法性检查, 如果入参不符合要求, 方法将通过抛出异常的方式拒绝后续处理. 但是在每个方法中都使用手工编写检测逻辑的方式并不是一个好主意. 阅读 Spring 源码, 会发现 Spring 采用一个 org.springframework.util.Assert 通用类完成这一任务.
Spring 对方法入参的检测借用了这个概念, 其提供的 Assert 类拥有众多按规则对方法入参进行断言的方法, 可以满足大部分方法入参检测的要求. 这些断言方法在入参不满足要求时就会抛出 IllegalArgumentException.
| 断言方法 | 说明 |
|---|---|
| notNull(Object object) | 当 object 不为 null 时抛出异常, notNull(Object object, String message) 方法允许您通过 message 定制异常信息. 和 notNull() 方法断言规则相反的方法是 isNull(Object object)/isNull(Object object, String message), 它要求入参一定是 null |
| isTrue(boolean expression) / isTrue(boolean expression, String message) | 当 expression 不为 true 抛出异常 |
| notEmpty(Collection collection) / notEmpty(Collection collection, String message) | 当集合未包含元素时抛出异常. notEmpty(Map map) / notEmpty(Map map, String message) 和 notEmpty(Object[] array, String message) / notEmpty(Object[] array, String message) 分别对 Map 和 Object[] 类型的入参进行判断 |
| hasLength(String text) / hasLength(String text, String message) | 当 text 为 null 或长度为 0 时抛出异常 |
| hasText(String text) / hasText(String text, String message) | text 不能为 null 且必须至少包含一个非空格的字符, 否则抛出异常 |
| isInstanceOf(Class clazz, Object obj) / isInstanceOf(Class type, Object obj, String message) | 如果 obj 不能被正确造型为 clazz 指定的类将抛出异常 |
| isAssignable(Class superType, Class subType) / isAssignable(Class superType, Class subType, String message) | subType 必须可以按类型匹配于 superType, 否则将抛出异常 |
6. 小结
本文介绍了一些常用的 Spring 工具类, 其中大部分 Spring 工具类不但可以在基于 Spring 的应用中使用, 还可以在其它的应用中使用.
浙公网安备 33010602011771号