在项目实际应用中,由于下载文件内容都比较大,如果同时有很多用户同时在下载,JVM的内存就会升的很高,甚至崩溃。为了避免很多用户同时下载,特引入Semaphore控制一次最多有配置个线程能进入实际下载的代码,即而控制JVM内存不会升的很高而导致崩溃。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component("DownloadView.csv")
public class DownloadView extends AbstractCsvView implements InitializingBean {
//允许的最大线程数
private String threadNum=PropertyUtil.getProperty("threadNum");
// 线程池
private ExecutorService exec = null;
// 只能threadNum个线程同时访问
private Semaphore semp = new Semaphore(Integer.parseInt(threadNum), true);
@Override
protected void buildExcelDocument(Map<String, Object> model, List<String> csvList, HttpServletRequest request,
HttpServletResponse response) throws Exception {
String fileName ="";
String url = request.getParameter("outputInfo");
if(StringUtil.isNotEmpty(url)){
fileName= url.substring(url.lastIndexOf("/") + 1, url.length());
}
super.setUrl(url);
try {
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(fileName, response.getCharacterEncoding()));
} catch (UnsupportedEncodingException e) {
throw new Exception("不支持此编码格式");
}
}
@SuppressWarnings("unchecked")
@Override
protected void renderMergedOutputModel(final Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception, IOException, InterruptedException, ExecutionException {
exec = Executors.newCachedThreadPool();
response.setContentType(getContentType());
final String url = request.getParameter("outputInfo");
final ServletOutputStream out=response.getOutputStream();
final HttpServletRequest _request = request;
final HttpServletResponse _response = response;
InputStream in=null;
try{
in= new FileInputStream(url);
}catch(Exception e){
throw new Exception("找不到对应的文件:"+url);
}
final InputStream fis=in;
final String encode=super.getEncoding();
Callable<Boolean> call = new Callable<Boolean>() {
@Override
public Boolean call() {
try {
// 获取许可
semp.acquire();
List<String> csvList = null;
//IOUtils.readLines()是一次性读取整个文件
//readline() 和 .readlines()之间的差异是后者一次读取整个文件,像read()一样。
//readlines()自动将文件内容分析成一个行的列表,
//readline()每次只读取一行,通常比 readlines()慢得多。
//仅当没有足够内存可以一次读取整个文件时,才应该使用readline().
csvList = IOUtils.readLines(fis);
buildExcelDocument(model, csvList, _request, _response);
if (encode == null) {
IOUtils.writeLines(csvList,encode, out);
} else {
IOUtils.writeLines(csvList, encode, out, encode);
}
//Thread.sleep((long) (2000));
return true;
} catch (Exception e) {
System.out.println(e);
return false;
} finally {
semp.release();
}
}
};
Future<Boolean> future=null;
if(!exec.isShutdown()){
future= exec.submit(call);
}
exec.shutdown();
if((Boolean) future.get()){
System.out.println("success");
}else{
System.out.print("fail");
}
}
@Override
public void afterPropertiesSet() throws Exception {
}
}
AbstractCsvView.java
Java代码 import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.LocalizedResourceHelper;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.AbstractView;
public abstract class AbstractCsvView extends AbstractView{
/** The content type for an csv response */
private static final String CONTENT_TYPE = "text/csv";
/** The extension to look for existing templates */
private static final String EXTENSION = ".csv";
private String lineEnding;
private String encoding;
/** The url at which the template to use is located */
private String url;
/**
* Default Constructor.
* Sets the content type of the view to "text/csv".
*/
public AbstractCsvView() {
setContentType(CONTENT_TYPE);
}
/**
* Set the URL of the Excel workbook source, without localization part nor extension.
*/
public void setUrl(String url) {
this.url = url;
}
public void setLineEnding(String lineEnding) {
this.lineEnding = lineEnding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public String getLineEnding() {
return lineEnding;
}
public String getEncoding() {
return encoding;
}
public String getUrl() {
return url;
}
@Override
protected boolean generatesDownloadContent() {
return true;
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
// Set the content type and get the output stream.
response.setContentType(getContentType());
List<String> csvList = null;
if (this.url != null) {
InputStream in = getTemplateSource(this.url, request);
csvList = IOUtils.readLines(in);
}else{
csvList = new ArrayList<String>();
}
buildExcelDocument(model, csvList, request, response);
if(this.encoding == null){
IOUtils.writeLines(csvList, this.lineEnding, response.getOutputStream());
}else{
IOUtils.writeLines(csvList, this.lineEnding, response.getOutputStream(), this.encoding);
}
}
protected InputStream getTemplateSource(String url, HttpServletRequest request) throws IOException {
LocalizedResourceHelper helper = new LocalizedResourceHelper(getApplicationContext());
Locale userLocale = RequestContextUtils.getLocale(request);
Resource inputFile = helper.findLocalizedResource(url, EXTENSION, userLocale);
// Create the Excel document from the source.
if (logger.isDebugEnabled()) {
logger.debug("Loading Excel workbook from " + inputFile);
}
return inputFile.getInputStream();
}
/**
* Subclasses must implement this method to create an csv List
* document, given the model.
* @param model the model Map
* @param csvList
* @param request in case we need locale etc. Shouldn't look at attributes.
* @param response in case we need to set cookies. Shouldn't write to it.
* @throws Exception in case of failure
*/
protected abstract void buildExcelDocument(Map<String, Object> model, List<String> csvList,
HttpServletRequest request, HttpServletResponse response) throws Exception;
}