除了标准的org.apache.struts.action.Action类外,在Struts中还提供了另外7个Action类来完成特殊的工作。本文及后面的文章中将介绍这些Action类的用法。
一、DownloadAction类简介
可能有时需要在Web程序中加入下载功能。如果要下载的是静态文件,可以直接交给Web服务器处理,但如果要对下载的文件做额外的功能,如统计文件的下载次数。就需要在下载文件之前先要调用相应的程序进行处理。
虽然我们可以直接在Action子类中来处理下载文件,但是如果这样的程序比较多时,就会写很多重复的代码。为了简化这个工作。Struts提供了一个新的Action类:DownloadAction。所有继承了DownloadAction类的Struts动作都可以非常容易地完成下载文件的工作。
DownloadAction类有一个抽象方法getStreamInfo。这个方法的定义如下:
protectedabstract StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception;
getStreamInfo方法返回一个StreamInfo对象。StreamInfo接口是DownloadAction类的一个内部接口,这个接口的定义如下:
publicstaticinterface StreamInfo { publicabstract String getContentType(); publicabstract InputStream getInputStream() throws IOException; }
从上面的代码可以看出,StreamInfo接口有两个方法。其中getInputStream方法返回了服务端要下载的文件的InputStream对象。getContenttType方法返回了HTTP响应消息头字段Content-Type的信息。在getStreamInfo方法中只要返回了实现这两个方法的StreamInfo对象,就可以自动完成下载工作。
为了方便起见,DownloadAction类中还提供了两个实现StreamInfo的内类:FileStreamInfo和ResourceStreamInfo。这两个类的构造方法的定义如下:
public FileStreamInfo(String contentType, File file);
public ResourceStreamInfo(String contentType, ServletContext context, String path);
我们可以使用FileStreamInfo类来下载静态的文件。如果要下载的文件在Web根目录,可以使用ResourceStreamInfo类。其中path参数表示文件相对于Web根目录的路径,必须以“/”开头,表示从Web根目录开始。
二、实例:统计文件的下载次数
在本节中将使用DownloadAction类实现一个统计文件下载次数的Web程序。这个程序的基本原理是当一个文件下载完成后,加这个文件在数据库中的下载次数加1,如果某个文件是第一次下载,则在数据库中添加一条新记录,下载次数为1。
为了实现这个Web程序,需要如下几步:
【第1步】建立用于保存文件下载次数的数据表
在本例中我们使用名为struts数据库,并且在struts数据库中建立一个t_dcount表,代码如下:
CREATETABLE struts.t_dcount( id INTNOTNULL, countINTNOTNULL, filename VARCHAR( 256 ) NOTNULL, PRIMARYKEY (id) ) ENGINE = InnoDB DEFAULT CHARSET=gbk;
【第2步】编写Struts动作类
这个Struts动作类负责完成文件的下载工作。如果在访问Struts动作类时不加file参数,会将指定目录下的所有文件(不包括隐藏文件)和已经下载的次数发送到客户端浏览器。如果通过file参数指定了下载文件,这个Struts动作就会下载这个文件。
在<samples工程目录>\src\action目录中建立一个FileDownloadAction.java文件,代码如下:
package chapter6.action; import javax.servlet.http.*; import org.apache.struts.action.*; import org.apache.struts.actions.*; import java.io.*; import java.sql.*; publicclass FileDownloadAction extends DownloadAction { private Connection conn; private String path; private String filename; // 获得了Connection对象 privatevoid openConnection() throws Exception { if (conn == null) { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection( "jdbc:mysql://localhost/struts?characterEncoding=GBK", "root", "1234"); } } // 获得某个文件的下载次数,其中id是文件名的hashcode privateint getDownloadCount(int id) throws Exception { openConnection(); PreparedStatement pstmt = conn .prepareStatement("SELECT count FROM t_dcount WHERE id = " + String.valueOf(id)); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { return rs.getInt(1); } return 0; } // 在文件完成下载后,将该文件的下载次数加1 privatevoid incDownloadCount() throws Exception { openConnection(); int id = filename.hashCode(); PreparedStatement pstmt = conn .prepareStatement("UPDATE t_dcount SET count = count + 1 WHERE id = " + String.valueOf(id)); if (pstmt.executeUpdate() == 0) { pstmt.executeUpdate("INSERT INTO t_dcount(id, count, filename) values(" + String.valueOf(id) + ",1,'" + filename + "')"); } } // 下载文件时调用getStreamInfo方法 protected StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { final FileInputStream fis = new FileInputStream(path + filename); final String contentType = "application/file"; // 建议设置content-disposition响应信息头,否则Web浏览器在下载文件时 // 无法在保存文件对话框中显示正确的文件名 response.setHeader("content-disposition", "attachment; filename=" + filename); incDownloadCount(); returnnew DownloadAction.StreamInfo() // 使用隐式的方法实现了StreamInfo接口 { public String getContentType() { return contentType; } public InputStream getInputStream() throws IOException { return fis; } }; } // 如果Struts动作不加file请求参数,则通过execute方法将指定目录中文件列表输出到客户端 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { path = this.getServlet().getInitParameter("downloadPath"); filename = request.getParameter("file"); if (filename == null) { File file = new File(path); File[] files = file.listFiles(); response.setCharacterEncoding("GBK"); PrintWriter out = response.getWriter(); out.println("<ul>"); for (File f : files) // 开始向客户端浏览器输出文件列表 { if (f.isFile() && !f.isHidden()) { out.println("<li><a href='" + request.getContextPath() + mapping.getPath() + ".do?file=" + f.getName() + "'>" + f.getName() + "</a> <font color='blue'>下载次数:" + String.valueOf(getDownloadCount(f.getName().hashCode())) + "</color></li>"); } } out.println("</ul>"); returnnull; } else { // 当file参数存在时,则调用DownloadAction中的execute方法 // 实际上,在DownloadAction类中的execute方法调用了getStreamInfo方法 // 这条语句就相当于调用了getStreamInfo方法 returnsuper.execute(mapping, form, request, response); } } }
【第3步】配置Struts动作类
在struts-config.xml文件中的<action-mappings>标签中加入了如下内容:
<action path="/download" scope="request" type="action.FileDownloadAction"/>
【第4步】配置下载路径
在web.xml中找到一个叫action的Servlet,并在<servlet>标签中添加如下内容:
<init-param> <param-name>downloadPath</param-name> <param-value>D:\download\</param-value> </init-param>
读取可以设置自已的下载目录,但下载目录必须以“"”结尾。
启动Tomcat后,在IE中输入如下的URL来测试程序:
http://localhost:8080/samples/download.do