概述

web项目的文件下载实现;servlet接收请求,spring工具类访问数据库及简化大字段内容获取。

虽然文章的demo中是以sevlet为平台,想必在spring mvc中也有参考意义。

核心代码

响应设置和输出

 1 public void service(ServletRequest request, ServletResponse response) 
 2      throws ServletException, IOException { 
 3   /* 1. 设置响应内容类型 */ 
 4   response.setContentType("Application/Octet-stream;charset=utf-8"); 
 5     
 6   /* 2. 读取文件 */ 
 7   String fileName = ... // 获取文件名 
 8   fileName = new String(file.getName().getBytes(), "ISO-8859-1"); 
 9   InputStream is = ... // 获取文件流 
10     
11   /* 3. 将文件名加入响应头 */ 
12   String cd = "attachment; filename=${fileName}" 
13      .replaceFirst("\\$\\{fileName\\}", fileName); 
14   ((HttpServletResponse) response).addHeader("Content-Disposition", cd); 
15     
16   /* 4. 将内容写到指定输出流,设置响应内容长度 */ 
17   OutputStream os = response.getOutputStream(); 
18   int length = org.springframework.util.FileCopyUtils.copy(is, os); 
19   response.setContentLength(length); // 不设置长度也可 
20     
21   /* 5. 关闭输出流 */ 
22   os.close(); 
23 }
Servlet

我们定义一个servlet用于接收文件下载的请求,按照上述代码实现文件下载服务。但是我们遗留了两个问题:

  1. 如何获取文件名
  2. 如何获取文件的输入流

虽然这两个问题并非难解,我们依然提供一个参考;考虑到文件可能存放在文件服务器或者数据库等多种形式,这里仅提供基于数据库的获取方案。

获取文件名和文件内容

 1 /* 创建JdbcTemplate用以查询数据 */ 
 2 org.springframework.jdbc.core.JdbcTemplate jt = new org.springframework.jdbc.core.JdbcTemplate(ds); 
 3     // 以java.sql.DataSource实例作为参数 
 4    
 5 /* 创建LobHandler用以简化Lob字段读取 */ 
 6 // 在内部对象的方法中使用,需声明为final 
 7 final org.springframework.jdbc.support.lob.LobHandler lobHandler = new org.springframework.jdbc.support.lob.DefaultLobHandler(); 
 8    
 9 /* 创建Map用来存放文件名和文件内容 */ 
10 final Map file = new HashMap(); 
11    
12 /* 确保可以查询到数据记录 - 取第一条 */ 
13 jt.query( 
14      sql, 
15      args, 
16      new org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor() { 
17    
18        protected void streamData(ResultSet rs) 
19             throws SQLException, IOException, 
20             DataAccessException { 
21           // 注意,没有调用过rs.next();rs初始化已指向第一条记录 
22           String fileName = rs.getString("filename"); 
23           InputStream is = lobHandler.getBlobAsBinaryStream(rs, "filecontent"); 
24           file.put("filename", fileName); 
25           file.put("filecontent", is); 
26        } 
27      });
基于spring JdbcTemplate获取文件名和文件流

上面一段代码可以用来获取存在数据库的文件名和文件内容(BLOB字段):

  1. JdbcTemplate用来访问数据库
    依赖于数据源实例。
  2. LobHandler用于简化Lob字段的读取
    代码只是展示了针对BLOB字段的一种用法,关于CLOB的或其他方法,请查阅API。
  3. 匿名内部类
    我们定义了基于AbstractLobStreamingResultSetExtractor的匿名内部类,并创建了对象实例。实例的方法中所访问的实例外的变量,要求必须是final类型的,所以我们把LobHandler定义成final的。鉴于同样的原因,我们无法直接把实例内部查询到的文件名和文件内容,直接赋值给外部的变量(因为外部的必须是final的),所以我们定义了一个final Map对象,用来存放结果。

代码优化

包图

包说明:

  1. cn.com.hnisi.fzyw.xzfy.gz.test.servlet
    集中管理servlet
  2. cn.com.hnisi.fzyw.xzfy.gz.download.service
    集中管理处理请求的service,及创建service实例的工厂
  3. cn.com.hnisi.fzyw.xzfy.gz.download.domain
    集中管理pojo
  4. cn.com.hnisi.baseservices.db
    提供数据库访问功能

类图

数据库访问支持组件

DataSourceFactory

DataSource工厂,负责向JdbcTemplateFactory提供可用的DataSource实例。

 1 package cn.com.hnisi.baseservices.db; 
 2    
 3 import javax.naming.InitialContext; 
 4 import javax.naming.NamingException; 
 5 import javax.sql.DataSource; 
 6    
 7 import cn.com.hnisi.baseservices.config.Config; 
 8 /** 
 9  * SINOBEST 数据源工厂,用于获取DataSource.<br> 
10  * 使用JNDI查找上下文中的DataSource.<br> 
11  * @author lijinlong 
12  * 
13  */ 
14 public class DataSourceFactory { 
15   private static DataSourceFactory instance; 
16   private static InitialContext context; 
17   /** 默认的数据源的jndi名称属性的key */ 
18   private static final String DEFAULT_DATASOURCE_PROP_KEY = "DB.DATASOURCE"; 
19     
20   private DataSourceFactory() { 
21      super(); 
22   } 
23     
24   /** 
25    * 单例模式获取工厂实例.<br> 
26    * @return 
27    */ 
28   public static final synchronized DataSourceFactory newInstance() { 
29      if (instance == null) 
30        instance = new DataSourceFactory(); 
31      return instance; 
32   } 
33     
34   /** 
35    * 获取默认的DataSource.<br> 
36    * 根据config/config.properties中DB.DATASOURCE属性值查找.<br> 
37    * @return 
38    */ 
39   public DataSource getDefaultDataSource() { 
40      String jndiName = Config.getInstance().getValue(DEFAULT_DATASOURCE_PROP_KEY); 
41      DataSource ds = getDataSource(jndiName); 
42      return ds; 
43   } 
44     
45   /** 
46    * 根据JNDI名称,获取上下文中的DataSource.<br> 
47    * @param jndiName 
48    * @return 
49    */ 
50   public synchronized DataSource getDataSource(String jndiName) { 
51      DataSource ds = null; 
52      try { 
53        if (context == null) 
54           context = new InitialContext(); 
55        ds = (DataSource)context.lookup(jndiName); 
56      } catch (NamingException e) { 
57        e.printStackTrace(); 
58      } 
59      return ds; 
60   } 
61 }
DataSourceFactory
JdbcTemplateFactory

JdbcTemplate工厂,负责创建可用的JdbcTemplate实例。

 1 package cn.com.hnisi.baseservices.db; 
 2    
 3 import javax.sql.DataSource; 
 4    
 5 import org.springframework.jdbc.core.JdbcTemplate; 
 6 /** 
 7  * 用于获取{@link JdbcTemplate}实例的工厂.<br> 
 8  * <ol> 
 9  *   <li>使用{@link #getDefaultJT()}可以获取基于默认数据源({@link DataSourceFactory#getDefaultDataSource()})的JT实例. 
10  *  </li> 
11  *  <li>使用{@link #getJT(DataSource)}可以获取基于指定数据源的JT实例. 
12  *  </li> 
13  *  <li>使用{@link #getJT(String)}可以获取基于指定JNDI name的数据源的JT实例. 
14  *  </li> 
15  *  <li> 
16  *   其他的可能以后会补充. 
17  *  </li> 
18  * </ol> 
19  * @author lijinlong 
20  * 
21  */ 
22 public class JdbcTemplateFactory { 
23   private static JdbcTemplateFactory instance; 
24    
25   private JdbcTemplateFactory() { 
26      super(); 
27   } 
28     
29   public static synchronized JdbcTemplateFactory newInstance() { 
30      if (instance == null) 
31        instance = new JdbcTemplateFactory(); 
32        
33      return instance; 
34   } 
35     
36   /** 
37    * 使用默认的数据源,获取{@link JdbcTemplate}对象实例.<br> 
38    * @return 
39    */ 
40   public JdbcTemplate getDefaultJT() { 
41      DataSource ds = DataSourceFactory.newInstance().getDefaultDataSource(); 
42      JdbcTemplate jt = getJT(ds); 
43      return jt; 
44   } 
45     
46   /** 
47    * 使用指定的数据源,获取{@link JdbcTemplate}对象实例.<br> 
48    * @param ds 
49    * @return 
50    */ 
51   public JdbcTemplate getJT(DataSource ds) { 
52      if (ds == null) 
53        return null; 
54       JdbcTemplate jt = new JdbcTemplate(ds); 
55       return jt; 
56   } 
57     
58   /** 
59    * 根据数据源的jndiName,构造{@link JdbcTemplate}对象实例. 
60    * @param dsJndiName 
61    * @return 
62    */ 
63   public JdbcTemplate getJT(String dsJndiName) { 
64      if (dsJndiName == null || dsJndiName.length() == 0) 
65        return null; 
66        
67      DataSource ds = DataSourceFactory.newInstance().getDataSource(dsJndiName); 
68      JdbcTemplate jt = getJT(ds); 
69      return jt; 
70   } 
71 }
JdbcTemplateFactory

File组件

自定义pojo,存放文件的name和content。

 1 package cn.com.hnisi.fzyw.xzfy.gz.download.domain; 
 2    
 3 import java.io.InputStream; 
 4    
 5 public class File { 
 6   private String name; 
 7   private InputStream is; 
 8   public File() { 
 9      super(); 
10   } 
11   public File(String name, InputStream is) { 
12      super(); 
13      this.name = name; 
14      this.is = is; 
15   } 
16   public String getName() { 
17      return name; 
18   } 
19   public void setName(String name) { 
20      this.name = name; 
21   } 
22   public InputStream getIs() { 
23      return is; 
24   } 
25   public void setIs(InputStream is) { 
26      this.is = is; 
27   } 
28 }
File

数据库访问组件

IDownloadService

文件下载服务接口。

 1 package cn.com.hnisi.fzyw.xzfy.gz.download.service; 
 2    
 3 import java.util.List; 
 4    
 5 import cn.com.hnisi.fzyw.xzfy.gz.download.domain.File; 
 6    
 7 public interface IDownloadService { 
 8   /** 
 9    * 读取文件.<br> 
10    *  
11    * @param sql 
12    *            查询sql语句,必须包含文件名字段和文件内容字段. 
13    * @param args 
14    *            参数 - 确保sql查询的结果只有一条. 
15    * @param colNameFileName 
16    *            存放文件名的字段名,大写. 
17    * @param colNameFileContent 
18    *            存放文件内容的字段名,大写. 
19    * @return 
20    */ 
21   public File read(final String sql, final Object[] args, 
22        final String colNameFileName, final String colNameFileContent); 
23 }
IDownloadService
DownloadServiceImpl

文件下载服务实现类。

 1 package cn.com.hnisi.fzyw.xzfy.gz.download.service; 
 2    
 3 import java.io.IOException; 
 4 import java.sql.ResultSet; 
 5 import java.sql.SQLException; 
 6    
 7 import org.springframework.dao.DataAccessException; 
 8 import org.springframework.jdbc.core.JdbcTemplate; 
 9 import org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor; 
10 import org.springframework.jdbc.support.lob.DefaultLobHandler; 
11 import org.springframework.jdbc.support.lob.LobHandler; 
12    
13 import cn.com.hnisi.baseservices.db.JdbcTemplateFactory; 
14 import cn.com.hnisi.fzyw.xzfy.gz.download.domain.File; 
15    
16 public class DownloadServiceImpl implements IDownloadService { 
17   /** 
18    * SINOBEST common 文件下载实现. 
19    */ 
20   public File read(final String sql, final Object[] args, 
21        final String colNameFileName, final String colNameFileContent) { 
22      JdbcTemplate jt = JdbcTemplateFactory.newInstance().getDefaultJT(); 
23      final LobHandler lobHandler = new DefaultLobHandler(); 
24      final File file = new File(); 
25      jt.query(sql, args, new AbstractLobStreamingResultSetExtractor() { 
26    
27        protected void streamData(ResultSet rs) throws SQLException, 
28             IOException, DataAccessException { 
29           file.setName(rs.getString(colNameFileName)); 
30           file.setIs(lobHandler.getBlobAsBinaryStream(rs, 
31                colNameFileContent)); 
32        } 
33      }); 
34      return file; 
35   } 
36    
37 }
DownloadServiceImpl
DownloadServiceFactory

文件下载服务工厂,创建服务组件实例。

 1 package cn.com.hnisi.fzyw.xzfy.gz.download.service; 
 2    
 3 public class DownloadServiceFactory { 
 4   private static DownloadServiceFactory ds; 
 5    
 6   private DownloadServiceFactory() { 
 7      super(); 
 8   } 
 9     
10   public static synchronized DownloadServiceFactory newInstance() { 
11      if (ds == null) 
12        ds = new DownloadServiceFactory(); 
13      return ds; 
14   } 
15     
16   public IDownloadService createDownloadService() { 
17      IDownloadService ds = new DownloadServiceImpl(); 
18      return ds; 
19   } 
20 }
DownloadServiceFactory

Servlet

FileDownloadTestServlet

文件下载请求的servlet

 1 package cn.com.hnisi.fzyw.xzfy.gz.test.servlet; 
 2    
 3 import java.io.IOException; 
 4 import java.io.OutputStream; 
 5    
 6 import javax.servlet.ServletException; 
 7 import javax.servlet.ServletRequest; 
 8 import javax.servlet.ServletResponse; 
 9 import javax.servlet.http.HttpServlet; 
10 import javax.servlet.http.HttpServletResponse; 
11    
12 import org.springframework.util.FileCopyUtils; 
13    
14 import cn.com.hnisi.fzyw.xzfy.gz.download.domain.File; 
15 import cn.com.hnisi.fzyw.xzfy.gz.download.service.DownloadServiceFactory; 
16    
17 public class FileDownloadTestServlet extends HttpServlet { 
18    
19   private static final long serialVersionUID = -2168892287436159079L; 
20   /** 
21    * SINOBEST common 文件下载测试. 
22    */ 
23   public void service(ServletRequest request, ServletResponse response) 
24        throws ServletException, IOException { 
25      /* 1. 设置响应内容类型 */ 
26      response.setContentType("Application/Octet-stream;charset=utf-8"); 
27        
28      /* 2. 读取文件 */ 
29      String sql = "select CLMC, SQCL from V_FZYWGZ_JK_XZFYSQXX_CL where SYSTEMID=?"; 
30      String systemid = request.getParameter("systemid"); 
31      Object[] args = { systemid }; 
32      String colNameFileName = "CLMC"; 
33      String colNameFileContent = "SQCL"; 
34      File file = DownloadServiceFactory.newInstance() 
35           .createDownloadService() 
36           .read(sql, args, colNameFileName, colNameFileContent); 
37        
38      /* 3. 将文件名加入响应头 */ 
39      String fileName = new String(file.getName().getBytes(), "ISO-8859-1"); 
40      ((HttpServletResponse) response).addHeader("Content-Disposition", 
41           "attachment; filename=" + fileName); 
42        
43      /* 4. 将内容写到指定输出流,设置响应内容长度 */ 
44      OutputStream os = response.getOutputStream(); 
45      int length = FileCopyUtils.copy(file.getIs(), os); 
46      response.setContentLength(length); 
47        
48      /* 5. 关闭输出流 */ 
49      os.close(); 
50   } 
51 }
FileDownloadTestServlet

发布评论

分享到:

IT源码网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

定位所用的class讲解
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。