随笔 - 63  文章 - 0  trackbacks - 0
<2024年12月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

常用链接

留言簿(2)

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

特殊字符转义

由于 Web 应用程序需要联合使用到多种语言,每种语言都包含一些特殊的字符,对于动态语言或标签式的语言而言,如果需要动态构造语言的内容时,一个我们经常会碰到的问题就是特殊字符转义的问题。下面是 Web 开发者最常面对需要转义的特殊字符类型:

  • HTML 特殊字符;
  • JavaScript 特殊字符;
  • SQL 特殊字符;

如果不对这些特殊字符进行转义处理,则不但可能破坏文档结构,还可以引发潜在的安全问题。Spring 为 HTML 和 JavaScript 特殊字符提供了转义操作工具类,它们分别是 HtmlUtils 和 JavaScriptUtils。

HTML 特殊字符转义

HTML 中 <,>,& 等字符有特殊含义,它们是 HTML 语言的保留字,因此不能直接使用。使用这些个字符时,应使用它们的转义序列:

  • &:&amp;
  • " :&quot;
  • < :&lt;
  • > :&gt;

由于 HTML 网页本身就是一个文本型结构化文档,如果直接将这些包含了 HTML 特殊字符的内容输出到网页中,极有可能破坏整个 HTML 文档的结构。所以,一般情况下需要对动态数据进行转义处理,使用转义序列表示 HTML 特殊字符。下面的 JSP 网页将一些变量动态输出到 HTML 网页中:


清单 1. 未进行 HTML 特殊字符转义处理网页
            <%@ page language="java" contentType="text/html; charset=utf-8"%>
            <%!
            String userName = "</td><tr></table>";
            String address = " \" type=\"button";
            %>
            <table border="1">
            <tr>
            <td>姓名:</td><td><%=userName%></td> ①
            </tr>
            <tr>
            <td>年龄:</td><td>28</td>
            </tr>
            </table>
            <input value="<%=address%>"  type="text" /> ②
            

在 ① 和 ② 处,我们未经任何转义处理就直接将变量输出到 HTML 网页中,由于这些变量可能包含一些特殊的 HTML 的字符,它们将可能破坏整个 HTML 文档的结构。我们可以从以上 JSP 页面的一个具体输出中了解这一问题:

<table border="1">
            <tr>
            <td>姓名:</td><td></td><tr></table></td>
            ① 破坏了 <table> 的结构
            </tr>
            <tr>
            <td>年龄:</td><td>28</td>
            </tr>
            </table>
            <input value=" " type="button"  type="text" />
            ② 将本来是输入框组件偷梁换柱为按钮组件
            

融合动态数据后的 HTML 网页已经面目全非,首先 ① 处的 <table> 结构被包含 HTML 特殊字符的 userName 变量截断了,造成其后的 <table> 代码变成无效的内容;其次,② 处 <input> 被动态数据改换为按钮类型的组件(type="button")。为了避免这一问题,我们需要事先对可能破坏 HTML 文档结构的动态数据进行转义处理。Spring 为我们提供了一个简单适用的 HTML 特殊字符转义工具类,它就是 HtmlUtils。下面,我们通过一个简单的例子了解 HtmlUtils 的具体用法:


清单 2. HtmpEscapeExample
            package com.baobaotao.escape;
            import org.springframework.web.util.HtmlUtils;
            public class HtmpEscapeExample {
            public static void main(String[] args) {
            String specialStr = "<div id=\"testDiv\">test1;test2</div>";
            String str1 = HtmlUtils.htmlEscape(specialStr); ①转换为HTML转义字符表示
            System.out.println(str1);
            String str2 = HtmlUtils.htmlEscapeDecimal(specialStr); ②转换为数据转义表示
            System.out.println(str2);
            String str3 = HtmlUtils.htmlEscapeHex(specialStr); ③转换为十六进制数据转义表示
            System.out.println(str3);
            ④下面对转义后字符串进行反向操作
            System.out.println(HtmlUtils.htmlUnescape(str1));
            System.out.println(HtmlUtils.htmlUnescape(str2));
            System.out.println(HtmlUtils.htmlUnescape(str3));
            }
            }
            

HTML 不但可以使用通用的转义序列表示 HTML 特殊字符,还可以使用以 # 为前缀的数字序列表示 HTML 特殊字符,它们在最终的显示效果上是一样的。HtmlUtils 提供了三个转义方法:

方法 说明
static String htmlEscape(String input) 将 HTML 特殊字符转义为 HTML 通用转义序列;
static String htmlEscapeDecimal(String input) 将 HTML 特殊字符转义为带 # 的十进制数据转义序列;
static String htmlEscapeHex(String input) 将 HTML 特殊字符转义为带 # 的十六进制数据转义序列;

此外,HtmlUtils 还提供了一个能够将经过转义内容还原的方法:htmlUnescape(String input),它可以还原以上三种转义序列的内容。运行以上代码,您将可以看到以下的输出:

str1:&lt;div id=&quot;testDiv&quot;&gt;test1;test2&lt;/div&gt;
            str2:<div id="testDiv">test1;test2</div>
            str3:<div id="testDiv">test1;test2</div>
            <div id="testDiv">test1;test2</div>
            <div id="testDiv">test1;test2</div>
            <div id="testDiv">test1;test2</div>
            

您只要使用 HtmlUtils 对代码 清单 1 的 userName 和 address 进行转义处理,最终输出的 HTML 页面就不会遭受破坏了。

JavaScript 特殊字符转义

JavaScript 中也有一些需要特殊处理的字符,如果直接将它们嵌入 JavaScript 代码中,JavaScript 程序结构将会遭受破坏,甚至被嵌入一些恶意的程序。下面列出了需要转义的特殊 JavaScript 字符:

  • ' :\'
  • " :\"
  • \ :\\
  • 走纸换页: \f
  • 换行:\n
  • 换栏符:\t
  • 回车:\r
  • 回退符:\b

我们通过一个具体例子演示动态变量是如何对 JavaScript 程序进行破坏的。假设我们有一个 JavaScript 数组变量,其元素值通过一个 Java List 对象提供,下面是完成这一操作的 JSP 代码片断:


清单 3. jsTest.jsp:未对 JavaScript 特殊字符进行处理
            <%@ page language="java" contentType="text/html; charset=utf-8"%>
            <jsp:directive.page import="java.util.*"/>
            <%
            List textList = new ArrayList();
            textList.add("\";alert();j=\"");
            %>
            <script>
            var txtList = new Array();
            <% for ( int i = 0 ; i < textList.size() ; i++) { %>
            txtList[<%=i%>] = "<%=textList.get(i)%>";
            ① 未对可能包含特殊 JavaScript 字符的变量进行处理
            <% } %>
            </script>
            

当客户端调用这个 JSP 页面后,将得到以下的 HTML 输出页面:

<script>
            var txtList = new Array();
            txtList[0] = "";alert();j=""; ① 本来是希望接受一个字符串,结果被植入了一段JavaScript代码
            </script>
            

由于包含 JavaScript 特殊字符的 Java 变量直接合并到 JavaScript 代码中,我们本来期望 ① 处所示部分是一个普通的字符串,但结果变成了一段 JavaScript 代码,网页将弹出一个 alert 窗口。想像一下如果粗体部分的字符串是“";while(true)alert();j="”时会产生什么后果呢?

因此,如果网页中的 JavaScript 代码需要通过拼接 Java 变量动态产生时,一般需要对变量的内容进行转义处理,可以通过 Spring 的 JavaScriptUtils 完成这件工作。下面,我们使用 JavaScriptUtils 对以上代码进行改造:

<%@ page language="java" contentType="text/html; charset=utf-8"%>
            <jsp:directive.page import="java.util.*"/>
            <jsp:directive.page import="org.springframework.web.util.JavaScriptUtils"/>
            <%
            List textList = new ArrayList();
            textList.add("\";alert();j=\"");
            %>
            <script>
            var txtList = new Array();
            <% for ( int i = 0 ; i < textList.size() ; i++) { %>
            ① 在输出动态内容前事先进行转义处理
            txtList[<%=i%>] = "<%=JavaScriptUtils.javaScriptEscape(""+textList.get(i))%>";
            <% } %>
            </script>
            

通过转义处理后,这个 JSP 页面输出的结果网页的 JavaScript 代码就不会产生问题了:

<script>
            var txtList = new Array();
            txtList[0] = "\";alert();j=\"";
            ① 粗体部分仅是一个普通的字符串,而非一段 JavaScript 的语句了
            </script>
            

SQL特殊字符转义

应该说,您即使没有处理 HTML 或 JavaScript 的特殊字符,也不会带来灾难性的后果,但是如果不在动态构造 SQL 语句时对变量中特殊字符进行处理,将可能导致程序漏洞、数据盗取、数据破坏等严重的安全问题。网络中有大量讲解 SQL 注入的文章,感兴趣的读者可以搜索相关的资料深入研究。

虽然 SQL 注入的后果很严重,但是只要对动态构造的 SQL 语句的变量进行特殊字符转义处理,就可以避免这一问题的发生了。来看一个存在安全漏洞的经典例子:

SELECT COUNT(userId)
            FROM t_user
            WHERE userName='"+userName+"' AND password ='"+password+"';
            

以上 SQL 语句根据返回的结果数判断用户提供的登录信息是否正确,如果 userName 变量不经过特殊字符转义处理就直接合并到 SQL 语句中,黑客就可以通过将 userName 设置为 “1' or '1'='1”绕过用户名/密码的检查直接进入系统了。

所以除非必要,一般建议通过 PreparedStatement 参数绑定的方式构造动态 SQL 语句,因为这种方式可以避免 SQL 注入的潜在安全问题。但是往往很难在应用中完全避免通过拼接字符串构造动态 SQL 语句的方式。为了防止他人使用特殊 SQL 字符破坏 SQL 的语句结构或植入恶意操作,必须在变量拼接到 SQL 语句之前对其中的特殊字符进行转义处理。Spring 并没有提供相应的工具类,您可以通过 jakarta commons lang 通用类包中(spring/lib/jakarta-commons/commons-lang.jar)的 StringEscapeUtils 完成这一工作:


清单 4. SqlEscapeExample
            package com.baobaotao.escape;
            import org.apache.commons.lang.StringEscapeUtils;
            public class SqlEscapeExample {
            public static void main(String[] args) {
            String userName = "1' or '1'='1";
            String password = "123456";
            userName = StringEscapeUtils.escapeSql(userName);
            password = StringEscapeUtils.escapeSql(password);
            String sql = "SELECT COUNT(userId) FROM t_user WHERE userName='"
            + userName + "' AND password ='" + password + "'";
            System.out.println(sql);
            }
            }
            

事实上,StringEscapeUtils 不但提供了 SQL 特殊字符转义处理的功能,还提供了 HTML、XML、JavaScript、Java 特殊字符的转义和还原的方法。如果您不介意引入 jakarta commons lang 类包,我们更推荐您使用 StringEscapeUtils 工具类完成特殊字符转义处理的工作。





回页首


方法入参检测工具类

Web 应用在接受表单提交的数据后都需要对其进行合法性检查,如果表单数据不合法,请求将被驳回。类似的,当我们在编写类的方法时,也常常需要对方法入参进行合法性检查,如果入参不符合要求,方法将通过抛出异常的方式拒绝后续处理。举一个例子:有一个根据文件名获取输入流的方法:InputStream getData(String file),为了使方法能够成功执行,必须保证 file 入参不能为 null 或空白字符,否则根本无须进行后继的处理。这时方法的编写者通常会在方法体的最前面编写一段对入参进行检测的代码,如下所示:

public InputStream getData(String file) {
            if (file == null || file.length() == 0|| file.replaceAll("\\s", "").length() == 0) {
            throw new IllegalArgumentException("file入参不是有效的文件地址");
            }
            …
            }
            

类似以上检测方法入参的代码是非常常见,但是在每个方法中都使用手工编写检测逻辑的方式并不是一个好主意。阅读 Spring 源码,您会发现 Spring 采用一个 org.springframework.util.Assert 通用类完成这一任务。

Assert 翻译为中文为“断言”,使用过 JUnit 的读者都熟知这个概念,它断定某一个实际的运行值和预期想一样,否则就抛出异常。Spring 对方法入参的检测借用了这个概念,其提供的 Assert 类拥有众多按规则对方法入参进行断言的方法,可以满足大部分方法入参检测的要求。这些断言方法在入参不满足要求时就会抛出 IllegalArgumentException。下面,我们来认识一下 Assert 类中的常用断言方法:

断言方法 说明
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,否则将抛出异常;

使用 Assert 断言类可以简化方法入参检测的代码,如 InputStream getData(String file) 在应用 Assert 断言类后,其代码可以简化为以下的形式:

public InputStream getData(String file){
            Assert.hasText(file,"file入参不是有效的文件地址");
            ① 使用 Spring 断言类进行方法入参检测
            …
            }
            

可见使用 Spring 的 Assert 替代自编码实现的入参检测逻辑后,方法的简洁性得到了不少的提高。Assert 不依赖于 Spring 容器,您可以大胆地在自己的应用中使用这个工具类。





回页首


小结

本文介绍了一些常用的 Spring 工具类,其中大部分 Spring 工具类不但可以在基于 Spring 的应用中使用,还可以在其它的应用中使用。

对于 Web 应用来说,由于有很多关联的脚本代码,如果这些代码通过拼接字符串的方式动态产生,就需要对动态内容中特殊的字符进行转义处理,否则就有可能产生意想不到的后果。Spring 为此提供了 HtmlUtils 和 JavaScriptUtils 工具类,只要将动态内容在拼接之前使用工具类进行转义处理,就可以避免类似问题的发生了。如果您不介意引入一个第三方类包,那么 jakarta commons lang 通用类包中的 StringEscapeUtils 工具类可能更加适合,因为它提供了更加全面的转义功能。

最后我们还介绍了 Spring 的 Assert 工具类,Assert 工具类是通用性很强的工具类,它使用面向对象的方式解决方法入参检测的问题,您可以在自己的应用中使用 Assert 对方法入参进行检查。


posted @ 2009-04-05 08:45 lanxin1020 阅读(177) | 评论 (0)编辑 收藏

Spring 不但提供了一个功能全面的应用开发框架,本身还拥有众多可以在程序编写时直接使用的工具类,您不但可以在 Spring 应用中使用这些工具类,也可以在其它的应用中使用,这些工具类中的大部分是可以在脱离 Spring 框架时使用的。了解 Spring 中有哪些好用的工具类并在程序编写时适当使用,将有助于提高开发效率、增强代码质量。

在这个分为两部分的文章中,我们将从众多的 Spring 工具类中遴选出那些好用的工具类介绍给大家。第 1 部分将介绍与文件资源操作和 Web 相关的工具类。在 第 2 部分 中将介绍特殊字符转义和方法入参检测工具类。

文件资源操作

文件资源的操作是应用程序中常见的功能,如当上传一个文件后将其保存在特定目录下,从指定地址加载一个配置文件等等。我们一般使用 JDK 的 I/O 处理类完成这些操作,但对于一般的应用程序来说,JDK 的这些操作类所提供的方法过于底层,直接使用它们进行文件操作不但程序编写复杂而且容易产生错误。相比于 JDK 的 File,Spring 的 Resource 接口(资源概念的描述接口)抽象层面更高且涵盖面更广,Spring 提供了许多方便易用的资源操作工具类,它们大大降低资源操作的复杂度,同时具有更强的普适性。这些工具类不依赖于 Spring 容器,这意味着您可以在程序中象一般普通类一样使用它们。

加载文件资源

Spring 定义了一个 org.springframework.core.io.Resource 接口,Resource 接口是为了统一各种类型不同的资源而定义的,Spring 提供了若干 Resource 接口的实现类,这些实现类可以轻松地加载不同类型的底层资源,并提供了获取文件名、URL 地址以及资源内容的操作方法。

访问文件资源

假设有一个文件地位于 Web 应用的类路径下,您可以通过以下方式对这个文件资源进行访问:

  • 通过 FileSystemResource 以文件系统绝对路径的方式进行访问;
  • 通过 ClassPathResource 以类路径的方式进行访问;
  • 通过 ServletContextResource 以相对于Web应用根目录的方式进行访问。

相比于通过 JDK 的 File 类访问文件资源的方式,Spring 的 Resource 实现类无疑提供了更加灵活的操作方式,您可以根据情况选择适合的 Resource 实现类访问资源。下面,我们分别通过 FileSystemResource 和 ClassPathResource 访问同一个文件资源:


清单 1. FileSourceExample
package com.baobaotao.io;
            import java.io.IOException;
            import java.io.InputStream;
            import org.springframework.core.io.ClassPathResource;
            import org.springframework.core.io.FileSystemResource;
            import org.springframework.core.io.Resource;
            public class FileSourceExample {
            public static void main(String[] args) {
            try {
            String filePath =
            "D:/masterSpring/chapter23/webapp/WEB-INF/classes/conf/file1.txt";
            // ① 使用系统文件路径方式加载文件
            Resource res1 = new FileSystemResource(filePath);
            // ② 使用类路径方式加载文件
            Resource res2 = new ClassPathResource("conf/file1.txt");
            InputStream ins1 = res1.getInputStream();
            InputStream ins2 = res2.getInputStream();
            System.out.println("res1:"+res1.getFilename());
            System.out.println("res2:"+res2.getFilename());
            } catch (IOException e) {
            e.printStackTrace();
            }
            }
            }
            

在获取资源后,您就可以通过 Resource 接口定义的多个方法访问文件的数据和其它的信息:如您可以通过 getFileName() 获取文件名,通过 getFile() 获取资源对应的 File 对象,通过 getInputStream() 直接获取文件的输入流。此外,您还可以通过 createRelative(String relativePath) 在资源相对地址上创建新的资源。

在 Web 应用中,您还可以通过 ServletContextResource 以相对于 Web 应用根目录的方式访问文件资源,如下所示:

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
            <jsp:directive.page import="
            org.springframework.web.context.support.ServletContextResource"/>
            <jsp:directive.page import="org.springframework.core.io.Resource"/>
            <%
            // ① 注意文件资源地址以相对于 Web 应用根路径的方式表示
            Resource res3 = new ServletContextResource(application,
            "/WEB-INF/classes/conf/file1.txt");
            out.print(res3.getFilename());
            %>
            

对于位于远程服务器(Web 服务器或 FTP 服务器)的文件资源,您则可以方便地通过 UrlResource 进行访问。

为了方便访问不同类型的资源,您必须使用相应的 Resource 实现类,是否可以在不显式使用 Resource 实现类的情况下,仅根据带特殊前缀的资源地址直接加载文件资源呢?Spring 提供了一个 ResourceUtils 工具类,它支持“classpath:”和“file:”的地址前缀,它能够从指定的地址加载文件资源,请看下面的例子:


清单 2. ResourceUtilsExample
package com.baobaotao.io;
            import java.io.File;
            import org.springframework.util.ResourceUtils;
            public class ResourceUtilsExample {
            public static void main(String[] args) throws Throwable{
            File clsFile = ResourceUtils.getFile("classpath:conf/file1.txt");
            System.out.println(clsFile.isFile());
            String httpFilePath = "file:D:/masterSpring/chapter23/src/conf/file1.txt";
            File httpFile = ResourceUtils.getFile(httpFilePath);
            System.out.println(httpFile.isFile());
            }
            }
            

ResourceUtils 的 getFile(String resourceLocation) 方法支持带特殊前缀的资源地址,这样,我们就可以在不和 Resource 实现类打交道的情况下使用 Spring 文件资源加载的功能了。

本地化文件资源

本地化文件资源是一组通过本地化标识名进行特殊命名的文件,Spring 提供的 LocalizedResourceHelper 允许通过文件资源基名和本地化实体获取匹配的本地化文件资源并以 Resource 对象返回。假设在类路径的 i18n 目录下,拥有一组基名为 message 的本地化文件资源,我们通过以下实例演示获取对应中国大陆和美国的本地化文件资源:


清单 3. LocaleResourceTest
package com.baobaotao.io;
            import java.util.Locale;
            import org.springframework.core.io.Resource;
            import org.springframework.core.io.support.LocalizedResourceHelper;
            public class LocaleResourceTest {
            public static void main(String[] args) {
            LocalizedResourceHelper lrHalper = new LocalizedResourceHelper();
            // ① 获取对应美国的本地化文件资源
            Resource msg_us = lrHalper.findLocalizedResource("i18n/message", ".properties",
            Locale.US);
            // ② 获取对应中国大陆的本地化文件资源
            Resource msg_cn = lrHalper.findLocalizedResource("i18n/message", ".properties",
            Locale.CHINA);
            System.out.println("fileName(us):"+msg_us.getFilename());
            System.out.println("fileName(cn):"+msg_cn.getFilename());
            }
            }
            

虽然 JDK 的 java.util.ResourceBundle 类也可以通过相似的方式获取本地化文件资源,但是其返回的是 ResourceBundle 类型的对象。如果您决定统一使用 Spring 的 Resource 接表征文件资源,那么 LocalizedResourceHelper 就是获取文件资源的非常适合的帮助类了。

文件操作

在使用各种 Resource 接口的实现类加载文件资源后,经常需要对文件资源进行读取、拷贝、转存等不同类型的操作。您可以通过 Resource 接口所提供了方法完成这些功能,不过在大多数情况下,通过 Spring 为 Resource 所配备的工具类完成文件资源的操作将更加方便。

文件内容拷贝

第一个我们要认识的是 FileCopyUtils,它提供了许多一步式的静态操作方法,能够将文件内容拷贝到一个目标 byte[]、String 甚至一个输出流或输出文件中。下面的实例展示了 FileCopyUtils 具体使用方法:


清单 4. FileCopyUtilsExample
package com.baobaotao.io;
            import java.io.ByteArrayOutputStream;
            import java.io.File;
            import java.io.FileReader;
            import java.io.OutputStream;
            import org.springframework.core.io.ClassPathResource;
            import org.springframework.core.io.Resource;
            import org.springframework.util.FileCopyUtils;
            public class FileCopyUtilsExample {
            public static void main(String[] args) throws Throwable {
            Resource res = new ClassPathResource("conf/file1.txt");
            // ① 将文件内容拷贝到一个 byte[] 中
            byte[] fileData = FileCopyUtils.copyToByteArray(res.getFile());
            // ② 将文件内容拷贝到一个 String 中
            String fileStr = FileCopyUtils.copyToString(new FileReader(res.getFile()));
            // ③ 将文件内容拷贝到另一个目标文件
            FileCopyUtils.copy(res.getFile(),
            new File(res.getFile().getParent()+ "/file2.txt"));
            // ④ 将文件内容拷贝到一个输出流中
            OutputStream os = new ByteArrayOutputStream();
            FileCopyUtils.copy(res.getInputStream(), os);
            }
            }
            

往往我们都通过直接操作 InputStream 读取文件的内容,但是流操作的代码是比较底层的,代码的面向对象性并不强。通过 FileCopyUtils 读取和拷贝文件内容易于操作且相当直观。如在 ① 处,我们通过 FileCopyUtils 的 copyToByteArray(File in) 方法就可以直接将文件内容读到一个 byte[] 中;另一个可用的方法是 copyToByteArray(InputStream in),它将输入流读取到一个 byte[] 中。

如果是文本文件,您可能希望将文件内容读取到 String 中,此时您可以使用 copyToString(Reader in) 方法,如 ② 所示。使用 FileReader 对 File 进行封装,或使用 InputStreamReader 对 InputStream 进行封装就可以了。

FileCopyUtils 还提供了多个将文件内容拷贝到各种目标对象中的方法,这些方法包括:

方法 说明
static void copy(byte[] in, File out) 将 byte[] 拷贝到一个文件中
static void copy(byte[] in, OutputStream out) 将 byte[] 拷贝到一个输出流中
static int copy(File in, File out) 将文件拷贝到另一个文件中
static int copy(InputStream in, OutputStream out) 将输入流拷贝到输出流中
static int copy(Reader in, Writer out) 将 Reader 读取的内容拷贝到 Writer 指向目标输出中
static void copy(String in, Writer out) 将字符串拷贝到一个 Writer 指向的目标中

在实例中,我们虽然使用 Resource 加载文件资源,但 FileCopyUtils 本身和 Resource 没有任何关系,您完全可以在基于 JDK I/O API 的程序中使用这个工具类。

属性文件操作

我们知道可以通过 java.util.Properties的load(InputStream inStream) 方法从一个输入流中加载属性资源。Spring 提供的 PropertiesLoaderUtils 允许您直接通过基于类路径的文件地址加载属性资源,请看下面的例子:

package com.baobaotao.io;
            import java.util.Properties;
            import org.springframework.core.io.support.PropertiesLoaderUtils;
            public class PropertiesLoaderUtilsExample {
            public static void main(String[] args) throws Throwable {
            // ① jdbc.properties 是位于类路径下的文件
            Properties props = PropertiesLoaderUtils.loadAllProperties("jdbc.properties");
            System.out.println(props.getProperty("jdbc.driverClassName"));
            }
            }
            

一般情况下,应用程序的属性文件都放置在类路径下,所以 PropertiesLoaderUtils 比之于 Properties#load(InputStream inStream) 方法显然具有更强的实用性。此外,PropertiesLoaderUtils 还可以直接从 Resource 对象中加载属性资源:

方法 说明
static Properties loadProperties(Resource resource) 从 Resource 中加载属性
static void fillProperties(Properties props, Resource resource) 将 Resource 中的属性数据添加到一个已经存在的 Properties 对象中

特殊编码的资源

当您使用 Resource 实现类加载文件资源时,它默认采用操作系统的编码格式。如果文件资源采用了特殊的编码格式(如 UTF-8),则在读取资源内容时必须事先通过 EncodedResource 指定编码格式,否则将会产生中文乱码的问题。


清单 5. EncodedResourceExample
package com.baobaotao.io;
            import org.springframework.core.io.ClassPathResource;
            import org.springframework.core.io.Resource;
            import org.springframework.core.io.support.EncodedResource;
            import org.springframework.util.FileCopyUtils;
            public class EncodedResourceExample {
            public static void main(String[] args) throws Throwable  {
            Resource res = new ClassPathResource("conf/file1.txt");
            // ① 指定文件资源对应的编码格式(UTF-8)
            EncodedResource encRes = new EncodedResource(res,"UTF-8");
            // ② 这样才能正确读取文件的内容,而不会出现乱码
            String content  = FileCopyUtils.copyToString(encRes.getReader());
            System.out.println(content);
            }
            }
            

EncodedResource 拥有一个 getResource() 方法获取 Resource,但该方法返回的是通过构造函数传入的原 Resource 对象,所以必须通过 EncodedResource#getReader() 获取应用编码后的 Reader 对象,然后再通过该 Reader 读取文件的内容。





回页首


Web 相关工具类

您几乎总是使用 Spring 框架开发 Web 的应用,Spring 为 Web 应用提供了很多有用的工具类,这些工具类可以给您的程序开发带来很多便利。在这节里,我们将逐一介绍这些工具类的使用方法。

操作 Servlet API 的工具类

当您在控制器、JSP 页面中想直接访问 Spring 容器时,您必须事先获取 WebApplicationContext 对象。Spring 容器在启动时将 WebApplicationContext 保存在 ServletContext的属性列表中,通过 WebApplicationContextUtils 工具类可以方便地获取 WebApplicationContext 对象。

WebApplicationContextUtils

当 Web 应用集成 Spring 容器后,代表 Spring 容器的 WebApplicationContext 对象将以 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 为键存放在 ServletContext 属性列表中。您当然可以直接通过以下语句获取 WebApplicationContext:

WebApplicationContext wac = (WebApplicationContext)servletContext.
            getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
            

但通过位于 org.springframework.web.context.support 包中的 WebApplicationContextUtils 工具类获取 WebApplicationContext 更方便:

WebApplicationContext wac =WebApplicationContextUtils.
            getWebApplicationContext(servletContext);
            

当 ServletContext 属性列表中不存在 WebApplicationContext 时,getWebApplicationContext() 方法不会抛出异常,它简单地返回 null。如果后续代码直接访问返回的结果将引发一个 NullPointerException 异常,而 WebApplicationContextUtils 另一个 getRequiredWebApplicationContext(ServletContext sc) 方法要求 ServletContext 属性列表中一定要包含一个有效的 WebApplicationContext 对象,否则马上抛出一个 IllegalStateException 异常。我们推荐使用后者,因为它能提前发现错误的时间,强制开发者搭建好必备的基础设施。

WebUtils

位于 org.springframework.web.util 包中的 WebUtils 是一个非常好用的工具类,它对很多 Servlet API 提供了易用的代理方法,降低了访问 Servlet API 的复杂度,可以将其看成是常用 Servlet API 方法的门面类。

下面这些方法为访问 HttpServletRequest 和 HttpSession 中的对象和属性带来了方便:

方法 说明
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)到下一个处理程序时,这些请求属性就可以被访问到了;

此外,WebUtils还提供了一些和ServletContext相关的方便方法:

方法 说明
String getRealPath(ServletContext servletContext, String path) 获取相对路径对应文件系统的真实文件路径;
File getTempDir(ServletContext servletContext) 获取 ServletContex 对应的临时文件地址,它以 File 对象的形式返回。

下面的片断演示了使用 WebUtils 从 HttpSession 中获取属性对象的操作:

protected Object formBackingObject(HttpServletRequest request) throws Exception {
            UserSession userSession = (UserSession) WebUtils.getSessionAttribute(request,
            "userSession");
            if (userSession != null) {
            return new AccountForm(this.petStore.getAccount(
            userSession.getAccount().getUsername()));
            } else {
            return new AccountForm();
            }
            }
            

Spring 所提供的过滤器和监听器

Spring 为 Web 应用提供了几个过滤器和监听器,在适合的时间使用它们,可以解决一些常见的 Web 应用问题。

延迟加载过滤器

Hibernate 允许对关联对象、属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 Hibernate Session 范围之内进行。如果 Service 层返回一个启用了延迟加载功能的领域对象给 Web 层,当 Web 层访问到那些需要延迟加载的数据时,由于加载领域对象的 Hibernate Session 已经关闭,这些导致延迟加载数据的访问异常。

Spring 为此专门提供了一个 OpenSessionInViewFilter 过滤器,它的主要功能是使每个请求过程绑定一个 Hibernate Session,即使最初的事务已经完成了,也可以在 Web 层进行延迟加载的操作。

OpenSessionInViewFilter 过滤器将 Hibernate Session 绑定到请求线程中,它将自动被 Spring 的事务管理器探测到。所以 OpenSessionInViewFilter 适用于 Service 层使用HibernateTransactionManager 或 JtaTransactionManager 进行事务管理的环境,也可以用于非事务只读的数据操作中。

要启用这个过滤器,必须在 web.xml 中对此进行配置:

…
            <filter>
            <filter-name>hibernateFilter</filter-name>
            <filter-class>
            org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
            </filter-class>
            </filter>
            <filter-mapping>
            <filter-name>hibernateFilter</filter-name>
            <url-pattern>*.html</url-pattern>
            </filter-mapping>
            …
            

上面的配置,我们假设使用 .html 的后缀作为 Web 框架的 URL 匹配模式,如果您使用 Struts 等 Web 框架,可以将其改为对应的“*.do”模型。

中文乱码过滤器

在您通过表单向服务器提交数据时,一个经典的问题就是中文乱码问题。虽然我们所有的 JSP 文件和页面编码格式都采用 UTF-8,但这个问题还是会出现。解决的办法很简单,我们只需要在 web.xml 中配置一个 Spring 的编码转换过滤器就可以了:

<web-app>
            <!---listener的配置-->
            <filter>
            <filter-name>encodingFilter</filter-name>
            <filter-class>
            org.springframework.web.filter.CharacterEncodingFilter ① Spring 编辑过滤器
            </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> ② 过滤器的匹配 URL
            <filter-name>encodingFilter</filter-name>
            <url-pattern>*.html</url-pattern>
            </filter-mapping>
            <!---servlet的配置-->
            </web-app>
            

这样所有以 .html 为后缀的 URL 请求的数据都会被转码为 UTF-8 编码格式,表单中文乱码的问题就可以解决了。

请求跟踪日志过滤器

除了以上两个常用的过滤器外,还有两个在程序调试时可能会用到的请求日志跟踪过滤器,它们会将请求的一些重要信息记录到日志中,方便程序的调试。这两个日志过滤器只有在日志级别为 DEBUG 时才会起作用:

方法 说明
org.springframework.web.filter.ServletContextRequestLoggingFilter 该过滤器将请求的 URI 记录到 Common 日志中(如通过 Log4J 指定的日志文件);
org.springframework.web.filter.ServletContextRequestLoggingFilter 该过滤器将请求的 URI 记录到 ServletContext 日志中。

以下是日志过滤器记录的请求跟踪日志的片断:

(JspServlet.java:224) -     JspEngine --> /htmlTest.jsp
            (JspServlet.java:225) - 	     ServletPath: /htmlTest.jsp
            (JspServlet.java:226) - 	        PathInfo: null
            (JspServlet.java:227) - 	        RealPath: D:\masterSpring\chapter23\webapp\htmlTest.jsp
            (JspServlet.java:228) - 	      RequestURI: /baobaotao/htmlTest.jsp
            …
            

通过这个请求跟踪日志,程度调试者可以详细地查看到有哪些请求被调用,请求的参数是什么,请求是否正确返回等信息。虽然这两个请求跟踪日志过滤器一般在程序调试时使用,但是即使程序部署不将其从 web.xml 中移除也不会有大碍,因为只要将日志级别设置为 DEBUG 以上级别,它们就不会输出请求跟踪日志信息了。

转存 Web 应用根目录监听器和 Log4J 监听器

Spring 在 org.springframework.web.util 包中提供了几个特殊用途的 Servlet 监听器,正确地使用它们可以完成一些特定需求的功能。比如某些第三方工具支持通过 ${key} 的方式引用系统参数(即可以通过 System.getProperty() 获取的属性),WebAppRootListener 可以将 Web 应用根目录添加到系统参数中,对应的属性名可以通过名为“webAppRootKey”的 Servlet 上下文参数指定,默认为“webapp.root”。下面是该监听器的具体的配置:


清单 6. WebAppRootListener 监听器配置
…
            <context-param>
            <param-name>webAppRootKey</param-name>
            <param-value>baobaotao.root</param-value> ① Web 应用根目录以该属性名添加到系统参数中
            </context-param>
            …
            ② 负责将 Web 应用根目录以 webAppRootKey 上下文参数指定的属性名添加到系统参数中
            <listener>
            <listener-class>
            org.springframework.web.util.WebAppRootListener
            </listener-class>
            </listener>
            …
            

这样,您就可以在程序中通过 System.getProperty("baobaotao.root") 获取 Web 应用的根目录了。不过更常见的使用场景是在第三方工具的配置文件中通过${baobaotao.root} 引用 Web 应用的根目录。比如以下的 log4j.properties 配置文件就通过 ${baobaotao.root} 设置了日志文件的地址:

log4j.rootLogger=INFO,R
            log4j.appender.R=org.apache.log4j.RollingFileAppender
            log4j.appender.R.File=${baobaotao.root}/WEB-INF/logs/log4j.log ① 指定日志文件的地址
            log4j.appender.R.MaxFileSize=100KB
            log4j.appender.R.MaxBackupIndex=1
            log4j.appender.R.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n
            

另一个专门用于 Log4J 的监听器是 Log4jConfigListener。一般情况下,您必须将 Log4J 日志配置文件以 log4j.properties 为文件名并保存在类路径下。Log4jConfigListener 允许您通过 log4jConfigLocation Servlet 上下文参数显式指定 Log4J 配置文件的地址,如下所示:

① 指定 Log4J 配置文件的地址
            <context-param>
            <param-name>log4jConfigLocation</param-name>
            <param-value>/WEB-INF/log4j.properties</param-value>
            </context-param>
            …
            ② 使用该监听器初始化 Log4J 日志引擎
            <listener>
            <listener-class>
            org.springframework.web.util.Log4jConfigListener
            </listener-class>
            </listener>
            …
            

提示

一些Web应用服务器(如 Tomcat)不会为不同的Web应用使用独立的系统参数,也就是说,应用服务器上所有的 Web 应用都共享同一个系统参数对象。这时,您必须通过webAppRootKey 上下文参数为不同Web应用指定不同的属性名:如第一个 Web 应用使用 webapp1.root 而第二个 Web 应用使用 webapp2.root 等,这样才不会发生后者覆盖前者的问题。此外,WebAppRootListener 和 Log4jConfigListener 都只能应用在 Web 应用部署后 WAR 文件会解包的 Web 应用服务器上。一些 Web 应用服务器不会将Web 应用的 WAR 文件解包,整个 Web 应用以一个 WAR 包的方式存在(如 Weblogic),此时因为无法指定对应文件系统的 Web 应用根目录,使用这两个监听器将会发生问题。

Log4jConfigListener 监听器包括了 WebAppRootListener 的功能,也就是说,Log4jConfigListener 会自动完成将 Web 应用根目录以 webAppRootKey 上下文参数指定的属性名添加到系统参数中,所以当您使用 Log4jConfigListener 后,就没有必须再使用 WebAppRootListener了。

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 监听器就可以了:

<listener>
            <listener-class>
            org.springframework.web.util.IntrospectorCleanupListener
            </listener-class>
            </listener>
            





回页首


小结

本文介绍了一些常用的 Spring 工具类,其中大部分 Spring 工具类不但可以在基于 Spring 的应用中使用,还可以在其它的应用中使用。使用 JDK 的文件操作类在访问类路径相关、Web 上下文相关的文件资源时,往往显得拖泥带水、拐弯抹角,Spring 的 Resource 实现类使这些工作变得轻松了许多。

在 Web 应用中,有时你希望直接访问 Spring 容器,获取容器中的 Bean,这时使用 WebApplicationContextUtils 工具类从 ServletContext 中获取 WebApplicationContext 是非常方便的。WebUtils 为访问 Servlet API 提供了一套便捷的代理方法,您可以通过 WebUtils 更好的访问 HttpSession 或 ServletContext 的信息。

Spring 提供了几个 Servlet 过滤器和监听器,其中 ServletContextRequestLoggingFilter 和 ServletContextRequestLoggingFilter 可以记录请求访问的跟踪日志,你可以在程序调试时使用它们获取请求调用的详细信息。WebAppRootListener 可以将 Web 应用的根目录以特定属性名添加到系统参数中,以便第三方工具类通过 ${key} 的方式进行访问。Log4jConfigListener 允许你指定 Log4J 日志配置文件的地址,且可以在配置文件中通过 ${key} 的方式引用 Web 应用根目录,如果你需要在 Web 应用相关的目录创建日志文件,使用 Log4jConfigListener 可以很容易地达到这一目标。

Web 应用的内存泄漏是最让开发者头疼的问题,虽然不正确的程序编写可能是这一问题的根源,也有可能是一些第三方框架的 JavaBean Introspector 缓存得不到清除而导致的,Spring 专门为解决这一问题配备了 IntrospectorCleanupListener 监听器,它只要简单在 web.xml 中声明该监听器就可以了。

posted @ 2009-04-05 08:44 lanxin1020 阅读(146) | 评论 (0)编辑 收藏

一个事件监听机制(Event Listener)的例程

事件监听机制在java编程中有很重要的应用,一般我们在处理GUI编程时,只是重写一下监听接口的perform函数即可。但事件监听在底层是如何运行的?通过下面的例子我们可以有个清楚地了解。
1.      首先写一个事件类
public class NewEvent {
     private int eventType=-1;
     public      NewEvent(int type){
           eventType=type;
     }
     public int getEventType(){
           return eventType;
     }

}
2.      写一个事件监听器

public interface INewEventListener {
      public void doProcessEvent(NewEvent event);

}
3.      写一个测试程序
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


public class Source {
     private List listeners = new ArrayList();
     
     public void addNewEventListener(INewEventListener listener){//增加一个监听器
           if(!listeners.contains(listener)){
                 listeners.add(listener);
           }
     }
     
     public void fireEvent(NewEvent event){//触发事件
           for (Iterator i=listeners.iterator(); i.hasNext();) {
                 INewEventListener listener = (INewEventListener)i.next();
                 listener.doProcessEvent(event);
           }
     }
     
     public void GenerateEvent(int eventType){//生成一个指定类型的事件
           fireEvent(new NewEvent(eventType));
     }
     
     //for test
     public static void main (String [] args){
           Source source = new Source ();
           source.addNewEventListener(new INewEventListener(){//增加一个监听器,并实现这个监听器接口的方法
                  public void doProcessEvent(NewEvent event){
                        int eventType = event.getEventType();
                        System.out.println("EventType "+eventType+ " was triggered");
                  }
           });
           source.GenerateEvent(3);//生成一个eventType为3的事件
           
     }
     
}

posted @ 2009-04-05 08:39 lanxin1020 阅读(162) | 评论 (0)编辑 收藏
Servlet运行在Servlet容器中,其生命周期由容器来管理。Servlet的生命周期通过javax.servlet.Servlet接口中的init()、service()和destroy()方法来表示。

Servlet的生命周期包含了下面4个阶段:

(1)加载和实例化

Servlet容器负责加载和实例化Servlet。当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。当Servlet容器启动后,它必须要知道所需的Servlet类在什么位置,Servlet容器可以从本地文件系统、远程文件系统或者其他的网络服务中通过类加载器加载Servlet类,成功加载后,容器创建Servlet的实例。因为容器是通过Java的反射API来创建Servlet实例,调用的是Servlet的默认构造方法(即不带参数的构造方法),所以我们在编写Servlet类的时候,不应该提供带参数的构造方法。

(2)初始化

在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象。初始化的目的是为了让Servlet对象在处理客户端请求前完成一些初始化的工作,如建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只被调用一次。在初始化期间,Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。在初始化期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常来通知容器。ServletException异常用于指明一般的初始化失败,例如没有找到初始化参数;而UnavailableException异常用于通知容器该Servlet实例不可用。例如,数据库服务器没有启动,数据库连接无法建立,Servlet就可以抛出UnavailableException异常向容器指出它暂时或永久不可用。

(3)请求处理

Servlet容器调用Servlet的service()方法对请求进行处理。要注意的是,在service()方法调用之前,init()方法必须成功执行。在service()方法中,Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。在service()方法执行期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常。如果UnavailableException异常指示了该实例永久不可用,Servlet容器将调用实例的destroy()方法,释放该实例。此后对该实例的任何请求,都将收到容器发送的HTTP 404(请求的资源不可用)响应。如果UnavailableException异常指示了该实例暂时不可用,那么在暂时不可用的时间段内,对该实例的任何请求,都将收到容器发送的HTTP 503(服务器暂时忙,不能处理请求)响应。

(4)服务终止

当容器检测到一个Servlet实例应该从服务中被移除的时候,容器就会调用实例的destroy()方法,以便让该实例可以释放它所使用的资源,保存数据到持久存储设备中。当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。如果再次需要这个Servlet处理请求,Servlet容器会创建一个新的Servlet实例。

在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。

如果需要让Servlet容器在启动时即加载Servlet,可以在web.xml文件中配置<load-on-startup>元素。
posted @ 2009-04-04 18:20 lanxin1020 阅读(167) | 评论 (0)编辑 收藏

tomcat5下jsp出现getOutputStream() has already been called for this response异常的原因和解决方法

在tomcat5下jsp中出现此错误一般都是在jsp中使用了输出流(如输出图片验证码,文件下载等),
没有妥善处理好的原因。
具体的原因就是
在tomcat中jsp编译成servlet之后在函数_jspService(HttpServletRequest request, HttpServletResponse response)的最后
有一段这样的代码
finally {
      if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
    }
这里是在释放在jsp中使用的对象,会调用response.getWriter(),因为这个方法是和
response.getOutputStream()相冲突的!所以会出现以上这个异常。

然后当然是要提出解决的办法,其实挺简单的(并不是和某些朋友说的那样--
将jsp内的所有空格和回车符号所有都删除掉),

在使用完输出流以后调用以下两行代码即可:
out.clear();
out = pageContext.pushBody();

最后这里是一个输出彩色验证码例子(这样的例子几乎随处可见)
imag.jsp

<%@ page  import="java.awt.*,java.awt.image.*,java.util.*,javax.imageio.*" %>
<%@ page import="java.io.OutputStream" %>
<%!
Color getRandColor(int fc,int bc){
Random random = new Random();
if(fc>255) fc=255;
if(bc>255) bc=255;
int r=fc+random.nextInt(bc-fc);
int g=fc+random.nextInt(bc-fc);
int b=fc+random.nextInt(bc-fc);
return new Color(r,g,b);
}
%>
<%
try{
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
int width=60, height=20;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
OutputStream os=response.getOutputStream();
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200,250));
g.fillRect(0, 0, width, height);

g.setFont(new Font("Times New Roman",Font.PLAIN,18));
g.setColor(getRandColor(160,200));
for (int i=0;i<155;i++)
{
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x,y,x+xl,y+yl);
}
String sRand="";
for (int i=0;i<4;i++){
String rand=String.valueOf(random.nextInt(10));
sRand+=rand;
g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));
g.drawString(rand,13*i+6,16);
}
session.setAttribute("rand",sRand);
g.dispose();

ImageIO.write(image, "JPEG",os);
os.flush();
os.close();
os=null;
response.flushBuffer();
out.clear();
out = pageContext.pushBody();
}
catch(IllegalStateException e)
{
System.out.println(e.getMessage());
e.printStackTrace();
}%>

posted @ 2009-03-26 09:15 lanxin1020 阅读(148) | 评论 (0)编辑 收藏

配置Spring数据源

配置一个数据源 
    Spring在第三方依赖包中包含了两个数据源的实现类包,其一是Apache的DBCP,其二是 C3P0。可以在Spring配置文件中利用这两者中任何一个配置数据源。
DBCP数据源 
    DBCP类包位于 /lib/jakarta-commons/commons-dbcp.jar,DBCP 是一个依赖 Jakarta commons-pool对象池机制的数据库连接池,所以在类路径下还必须包括/lib/jakarta- commons /commons-pool.jar。下面是使用DBCP配置MySql数据源的配置片断:

xml 代码
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"        
        destroy-method="close">        
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />       
    <property name="url" value="jdbc:mysql://localhost:3309/sampledb" />       
    <property name="username" value="root" />       
    <property name="password" value="1234" />       
bean>   
BasicDataSource提供了close()方法关闭数据源,所以必须设定destroy-method=”close”属性, 以便Spring容器关闭时,数据源能够正常关闭。除以上必须的数据源属性外,还有一些常用的属性: 
    defaultAutoCommit:设置从数据源中返回的连接是否采用自动提交机制,默认值为 true; 
    defaultReadOnly:设置数据源是否仅能执行只读操作, 默认值为 false; 
    maxActive:最大连接数据库连接数,设置为0时,表示没有限制; 
    maxIdle:最大等待连接中的数量,设置为0时,表示没有限制; 
    maxWait:最大等待秒数,单位为毫秒, 超过时间会报出错误信息; 
    validationQuery:用于验证连接是否成功的查询SQL语句,SQL语句必须至少要返回一行数据, 如你可以简单地设置为:“select count(*) from user”; 
    removeAbandoned:是否自我中断,默认是 false ; 
    removeAbandonedTimeout:几秒后数据连接会自动断开,在removeAbandoned为true,提供该值; 
    logAbandoned:是否记录中断事件, 默认为 false; 

C3P0数据源 
    C3P0 是一个开放源代码的JDBC数据源实现项目,它在lib目录中与Hibernate一起发布,实现了JDBC3和JDBC2扩展规范说明的 Connection 和Statement 池。C3P0类包位于/lib/c3p0/c3p0-0.9.0.4.jar。下面是使用C3P0配置一个 oracle数据源:

xml 代码
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"        
        destroy-method="close">       
    <property name="driverClass" value=" oracle.jdbc.driver.OracleDriver "/>       
    <property name="jdbcUrl" value=" jdbc:oracle:thin:@localhost:1521:ora9i "/>       
    <property name="user" value="admin"/>       
    <property name="password" value="1234"/>       
bean>   

ComboPooledDataSource和BasicDataSource一样提供了一个用于关闭数据源的close()方法,这样我们就可以保证Spring容器关闭时数据源能够成功释放。 
    C3P0拥有比DBCP更丰富的配置属性,通过这些属性,可以对数据源进行各种有效的控制: 
    acquireIncrement:当连接池中的连接用完时,C3P0一次性创建新连接的数目; 
    acquireRetryAttempts:定义在从数据库获取新连接失败后重复尝试获取的次数,默认为30; 
    acquireRetryDelay:两次连接中间隔时间,单位毫秒,默认为1000; 
    autoCommitOnClose:连接关闭时默认将所有未提交的操作回滚。默认为false; 
    automaticTestTable: C3P0 将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数,那么属性preferredTestQuery将被忽略。你 不能在这张Test表上进行任何操作,它将中为C3P0测试所用,默认为null; 
    breakAfterAcquireFailure:获取连接失败将会引起所有等待获取连接的线程抛出异常。但是数据源仍有效保留,并在下次调   用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。默认为 false; 
    checkoutTimeout:当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,如设为0则无限期等待。单位毫秒,默认为0; 
    connectionTesterClassName: 通过实现ConnectionTester或QueryConnectionTester的类来测试连接,类名需设置为全限定名。默认为 com.mchange.v2.C3P0.impl.DefaultConnectionTester; 
    idleConnectionTestPeriod:隔多少秒检查所有连接池中的空闲连接,默认为0表示不检查; 
    initialPoolSize:初始化时创建的连接数,应在minPoolSize与maxPoolSize之间取值。默认为3; 
    maxIdleTime:最大空闲时间,超过空闲时间的连接将被丢弃。为0或负数则永不丢弃。默认为0; 
    maxPoolSize:连接池中保留的最大连接数。默认为15; 
    maxStatements:JDBC 的标准参数,用以控制数据源内加载的PreparedStatement数量。但由于预缓存的Statement属 于单个Connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素,如果maxStatements与 maxStatementsPerConnection均为0,则缓存被关闭。默认为0; 
    maxStatementsPerConnection:连接池内单个连接所拥有的最大缓存Statement数。默认为0; 
    numHelperThreads:C3P0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能,通过多线程实现多个操作同时被执行。默认为3; 
    preferredTestQuery:定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个参数能显著提高测试速度。测试的表必须在初始数据源的时候就存在。默认为null; 
    propertyCycle: 用户修改系统配置参数执行前最多等待的秒数。默认为300; 
    testConnectionOnCheckout:因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的时候都 将校验其有效性。建议使用 idleConnectionTestPeriod或automaticTestTable 
等方法来提升连接测试的性能。默认为false; 
    testConnectionOnCheckin:如果设为true那么在取得连接的同时将校验连接的有效性。默认为false。 

读配置文件的方式引用属性: 

<bean id="propertyConfigurer"      
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">       
    <property name="location" value="/WEB-INF/jdbc.properties"/>       
bean>       
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"        
        destroy-method="close">       
    <property name="driverClassName" value="${jdbc.driverClassName}" />       
    <property name="url" value="${jdbc.url}" />       
    <property name="username" value="${jdbc.username}" />       
    <property name="password" value="${jdbc.password}" />       
bean>    

    在jdbc.properties属性文件中定义属性值: 
    jdbc.driverClassName= com.mysql.jdbc.Driver 
    jdbc.url= jdbc:mysql://localhost:3309/sampledb 
    jdbc.username=root 
    jdbc.password=1234 
    提示 经常有开发者在${xxx}的前后不小心键入一些空格,这些空格字符将和变量合并后作为属性的值。如: 的属性配置项,在前后都有空格,被解析后,username的值为“ 1234 ”,这将造成最终的错误,因此需要特别小心。

 获取JNDI数据源 
    如果应用配置在高性能的应用服务器(如WebLogic或Websphere等)上,我们可能更希望使用应用服务器本身提供的数据源。应用服务器的数据源 使用JNDI开放调用者使用,Spring为此专门提供引用JNDI资源的JndiObjectFactoryBean类。下面是一个简单的配置:

xml 代码
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">       
    <property name="jndiName" value="java:comp/env/jdbc/bbt"/>       
bean>   

通过jndiName指定引用的JNDI数据源名称。 
    Spring 2.0为获取J2EE资源提供了一个jee命名空间,通过jee命名空间,可以有效地简化J2EE资源的引用。下面是使用jee命名空间引用JNDI数据源的配置: 

xml 代码
<beans xmlns=http://www.springframework.org/schema/beans     
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance     
xmlns:jee=http://www.springframework.org/schema/jee     
xsi:schemaLocation="http://www.springframework.org/schema/beans       
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd       
http://www.springframework.org/schema/jee      
http://www.springframework.org/schema/jee /spring-jee-2.0.xsd">       
<jee:jndi-lookup id="dataSource" jndi-name=" java:comp/env/jdbc/bbt"/>       
beans>   

Spring的数据源实现类 
    Spring 本身也提供了一个简单的数据源实现类DriverManagerDataSource ,它位于 org.springframework.jdbc.datasource包中。这个类实现了javax.sql.DataSource接口,但 它并没有提供池化连接的机制,每次调用getConnection()获取新连接时,只是简单地创建一个新的连接。因此,这个数据源类比较适合在单元测试 或简单的独立应用中使用,因为它不需要额外的依赖类。 
     下面,我们来看一下DriverManagerDataSource的简单使用:当然,我们也可以通过配置的方式直接使用DriverManagerDataSource。

java 代码
DriverManagerDataSource ds = new DriverManagerDataSource ();       
ds.setDriverClassName("com.mysql.jdbc.Driver");       
ds.setUrl("jdbc:mysql://localhost:3309/sampledb");       
ds.setUsername("root");       
ds.setPassword("1234");       
Connection actualCon = ds.getConnection();   

 

小结 

    不管采用何种持久化技术,都需要定义数据源。Spring附带了两个数据源的实现类包,你可以自行选择进行定义。在实际部署时,我们可能会直接采用应用服 务器本身提供的数据源,这时,则可以通过JndiObjectFactoryBean或jee命名空间引用JNDI中的数据源。 

DBCP与C3PO配置的区别:

C3PO :

xml 代码

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">   
    <property name="driverClass">   
        <value>oracle.jdbc.driver.Oracle</Drivervalue>   
   </ property>   
    <property name="jdbcUrl">              
        <value>jdbc:oracle:thin:@10.10.10.6:1521:DataBaseNamevalue>   
    </ property>   
    <property name="user">   
        <value>testAdmin</value>   
   </ property>   
    <property name="password">   
        <value>123456</value>   
    </property>   
</bean>   

 DBCP:

xml 代码
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">   
    <property name="driverClassName">   
        <value>oracle.jdbc.driver.OracleDriver</value>   
    </property>   
    <property name="url">              
        <value>jdbc:oracle:thin:@10.10.10.6:1521:DataBaseNamevalue>   
    </property>   
    <property name="username">   
        <value>testAdmin</value>   
    </property>   
    <property name="password">   
        <value>123456</value>   
    </property>   
</bean>

posted @ 2009-03-25 11:14 lanxin1020 阅读(166) | 评论 (0)编辑 收藏

 

Tomcat5版本的数据源的配置如下:
<Resource name="jdbc/mldn"  auth="Container" type="javax.sql.DataSource"/>
 <ResourceParams name="jdbc/mldn">
  <parameter>
   <name>factory</name>
   <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
  </parameter>
  <parameter>
   <name>maxActive</name>
   <value>100</value>
  </parameter>
  <parameter>
   <name>maxIdle</name>
   <value>30</value>
  </parameter>
  <parameter>
   <name>maxWait</name>
   <value>5000</value>
  </parameter>
  <parameter>
   <name>username</name>
   <value>root</value>
  </parameter>
  <parameter>
   <name>password</name>
   <value>root</value>
  </parameter>
  <parameter>
   <name>driverClassName</name>
   <value>com.mysql.jdbc.Driver</value>
  </parameter>
  <parameter>
   <name>url</name>
   <value>jdbc:mysql://localhost:3306/mldn</value>
  </parameter>
  </ResourceParams>

Tomcat6版本的数据源的配置如下:

第一步:在%Tomcat_HOME%conf\server.xml的 <Host>标签里当前位置下的<Context>里添加如下代码。

<Resource   name="jdbc/mldn"
    auth="Container"
    type="javax.sql.DataSource"
    factory="org.apache.commons.dbcp.BasicDataSourceFactory"
    username="root"
    password="root"
    url="jdbc:mysql://localhost:3306/mldn"
    driverClassName="com.mysql.jdbc.Driver"
    maxIdle="30"
    maxWait="5000"
    maxActive="100"/>

另外在%Tomcat_HOME%conf\context.xml的 <Context>标签添加如下代码。
<Resource name="jdbc/mldn" auth="Container"
            type="javax.sql.DataSource" username="root" password="xtcwvt"
            driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/mldn"
            maxActive="8" maxIdle="4"/>

第二步:配置应用目录下的web.xml文件,在<web-app></web-app>之间加入以下代码:

<resource-ref>
  <description>DB Connection</description>
  <res-ref-name>jdbc/mldn</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
 </resource-ref>

注意:要求<res-ref-name>jdbc/mldn</res-ref-name>中的“jdbc/mldn”一定要与在server.xml中配置的Resource中的name一致,不然会出错。

第三步:在测试之前,要确保将数据库的驱动jar包放到,你的应用目录\WEB-INF\lib目录下和%Tomcat_HOME%c\lib目录下。

 下面重启Tomcat就OK了!

posted @ 2009-03-25 09:53 lanxin1020 阅读(146) | 评论 (0)编辑 收藏

配置Tomcat5.5.X的Server.xml,在<host>下面加:
          

  • <Context path="/JNDIDemo" docBase="D:\workspace\JNDIDemo\WebRoot" debug="0" reloadable="true" crossContext="true">    
  • <Logger className="org.apache.catalina.logger.FileLogger" prefix="localhost_quality_log." suffix=".txt" timestamp="true"/>    
  •   <Resource    
  •     name="jdbc/test" <!-- JNDI数据池名称 -->    
  •     type="javax.sql.DataSource" <!-- 数据类 -->    
  •     password="karid"     <!-- 密码 -->    
  •     driverClassName="oracle.jdbc.driver.OracleDriver"  <!-- 驱动 -->    
  •     maxIdle="2"               <!-- 最少可用lia -->    
  •     maxWait="5000"        <!-- 最大等待时间 5秒 -->    
  •     username="karid"       <!-- 用户名 -->    
  •     url="jdbc:oracle:thin:@127.0.0.1:1521:karid"    
  •     maxActive="4" <!-- 最大可用连接 --> />          
  •   
  • <ResourceParams name="jdbc/test">    
  •       
  • <parameter>    
  •   <name>removeAbandoned</name>    
  •   <!-- Abandoned DB connections are removed and recycled -->    
  •   <value>true</value>    
  • </parameter>    
  • <parameter>    
  •   <name>removeAbandonedTimeout</name>    
  •   <!-- Use the removeAbandonedTimeout parameter to set the number of seconds a DB connection has been idle before it is considered abandoned.  -->    
  •   <value>60</value>    
  • </parameter>    
  • <parameter>    
  •   <name>logAbandoned</name>    
  •   <!-- Log a stack trace of the code which abandoned -->    
  •   <value>false</value>    
  • </parameter>    
  •   
  • <parameter>    
  •   <name>factory</name>    
  •   <!--DBCP Basic Datasource Factory -->    
  •   <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>    
  • </parameter>    
  •   
  • </ResourceParams> 
  •  


    2、配置web.xml 
         

    1. <description>MySQL Test App</description>    
    2. <resource-ref>    
    3.      <description>DB Connection</description>    
    4.      <res-ref-name>jdbc/test</res-ref-name>    
    5.      <res-type>javax.sql.DataSource</res-type>    
    6.      <res-auth>Container</res-auth>    
    7. </resource-ref>   

    3、JNDI使用 

         

    1. public class DataSourceFactory    
    2. {    
    3.     private static DataSource ds;    
    4.     public static DataSource createDataSourde()    
    5.     {    
    6.       if (ds == null)    
    7.       {    
    8.          try    
    9.          {    
    10.              Context initContext = new InitialContext();    
    11.              if (initContext == null)    
    12.                  System.out.println("无配置环境");    
    13.              Context envContext = (Context) initContext.lookup("java:/compenv");    
    14.              ds = (DataSource) envContext.lookup("jdbc/test"); //根据名称取得数据源    
    15.           }    
    16.           catch (NamingException e)    
    17.           {    
    18.               e.printStackTrace();    
    19.           }    
    20.        }    
    21.        return ds;    
    22.     }    
    23. }  
    posted @ 2009-03-24 11:43 lanxin1020 阅读(138) | 评论 (0)编辑 收藏

    1  如果用到j2ee包则现将servlet-api包拷贝到javaHome/jre\lib\ext下
    2  在命令行方式下定位到要编译的文件所在的目录
    3  javac -d . 文件名

    posted @ 2009-01-31 10:59 lanxin1020 阅读(160) | 评论 (0)编辑 收藏

     

    所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。

    例:  
      1.class   A,class   B  
      2.class   A实现接口operate  
      3.class   B拥有一个参数为operate接口类型的函数test(operate   o)  
      4.class   A运行时调用class   B中test函数,以自身传入参数  
      5.class   B已取得A,就可以随时回调A所实现的operate接口中的方法

    举个例子来说:

    public interface InterestingEvent
    {

        // This is just a regular method so it can return something or

        // take arguments if you like.

        public void interestingEvent ();

    }






    public class EventNotifier
    {

        private InterestingEvent ie;

        private boolean somethingHappened;

        public EventNotifier (InterestingEvent event)
        {

            // Save the event object for later use.

            ie = event;

            // Nothing to report yet.

            somethingHappened = false;

        }

        // 

        public void doWork ()
        {

            // Check the predicate, which is set elsewhere.

            if (somethingHappened)
            {

                // Signal the even by invoking the interface's method.

                ie.interestingEvent ();

            }

            //
        }

        //

    }



    public class CallMe implements InterestingEvent
    {

        private EventNotifier en;

        public CallMe ()
        {

            // Create the event notifier and pass ourself to it.

            en = new EventNotifier (this);

        }

        // Define the actual handler for the event.

        public void interestingEvent ()
        {

            // Wow!  Something really interesting must have occurred!

            // Do something

        }

        //

    }






















    posted @ 2009-01-20 08:51 lanxin1020 阅读(210) | 评论 (0)编辑 收藏
    仅列出标题
    共7页: 上一页 1 2 3 4 5 6 7 下一页