SpringMVC的文件上传下载,异常处理,拦截器的小总结
文件的上传和下载
我们通常在访问网页时会使用到文件的上传与下载的功能,那么他是如何实现的呢?
1 下载:
ResponseEntity :用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文
下载,就是从服务器下载一个服务器上的资源,(通过浏览器请求向服务器请求资源,如果服务器上有这个资源就响应给客户端也就是响应到客户端的浏览器)
下载文件的代码如下:
@Controller
public class FileUpAndDownController {
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testDown(HttpSession session) {
ServletContext servletContext = session.getServletContext();
String realPath = servletContext.getRealPath("/static/img/girl.jpeg");
InputStream is = null;
ResponseEntity responseEntity = null;
try {
is = new FileInputStream(realPath);
byte[] bytes = new byte[is.available()];
is.read(bytes);
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=girl.jpeg");
HttpStatus status = HttpStatus.OK;
responseEntity = new ResponseEntity(bytes, headers, status);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
is.close();
try {
} catch (IOException e) {
e.printStackTrace();
}
}
}
return responseEntity;
}
}
其实搞懂ResponseEntity的作用,文件下载的问题也就迎刃而解了!
在上面的代码中
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=girl.jpeg");
HttpStatus status = HttpStatus.OK;
responseEntity = new ResponseEntity(bytes, headers, status);
看懂上面部分代码!!!
还有获取文件的真实地址(通过ServletContext)也是需要注意的点!
在ssm项目crm中也有文件下载的实现
将服务器的数据库里的记录下到本地,这里有个值得注意的点:将数据以excle文件的形式下到本地
下面是控制层的代码:
@RequestMapping("/workbench/activity/exportMore.do")
public void exportMore(HttpServletResponse response) throws IOException {
// 先获得数据从数据库
List<Activity> activities = activityService.selectForExport(null);
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("市场活动的表");
HSSFRow row = sheet.createRow(0);
HSSFCell cell = row.createCell(0);
cell.setCellValue("id");
cell = row.createCell(1);
cell.setCellValue("owner");
cell = row.createCell(2);
cell.setCellValue("name");
cell = row.createCell(3);
cell.setCellValue("start_date");
cell = row.createCell(4);
cell.setCellValue("end_date");
cell = row.createCell(5);
cell.setCellValue("cost");
cell = row.createCell(6);
cell.setCellValue("description");
cell = row.createCell(7);
cell.setCellValue("create_time");
cell = row.createCell(8);
cell.setCellValue("edit_time");
cell = row.createCell(9);
cell.setCellValue("edit_by");
cell = row.createCell(10);
cell.setCellValue("create_by");
if(activities.size()>0&&activities!=null){
int count = 1;
for (Activity activity : activities) {
row = sheet.createRow(count++);
cell = row.createCell(0);
cell.setCellValue(activity.getId());
cell = row.createCell(1);
cell.setCellValue(activity.getOwner());
cell = row.createCell(2);
cell.setCellValue(activity.getName());
cell = row.createCell(3);
cell.setCellValue(activity.getStartDate());
cell = row.createCell(4);
cell.setCellValue(activity.getEndDate());
cell = row.createCell(5);
cell.setCellValue(activity.getCost());
cell = row.createCell(6);
cell.setCellValue(activity.getDescription());
cell = row.createCell(7);
cell.setCellValue(activity.getCreateTime());
cell = row.createCell(8);
cell.setCellValue(activity.getEditTime());
cell = row.createCell(9);
cell.setCellValue(activity.getEditBy());
cell = row.createCell(10);
cell.setCellValue(activity.getCreateBy());
}
/*
因为跟磁盘的交互是非常耗时的,效率很低,原来的做法是将内存中的数据也就是从数据库中查询到的数据打算写到磁盘上创建xls文件,然后从磁盘再次
读取文件将数据读到内存再向客户端发送然后在客户端的浏览器接收。这里访问了磁盘两次,而对磁盘的操作往往是很费时的,如果有多个用户在下文件而我们的后台代码
每次都要访问磁盘也就是与磁盘做交互,显然会慢死!!
改进:
将对磁盘访问的部分去掉,直接改成是内存到内存!
// 创建xls文件
OutputStream out = new FileOutputStream("H:\\ssmconformity_code\\crm_project\\crm01\\target\\demo.xls");
wb.write(out);
// 关闭资源
wb.close();
out.close();
*/
// 将文件写出到浏览器
response.setContentType("application/octet-stream;charset=utf-8");/*设置数据类型*/
response.setHeader("Content-Disposition","attachment;filename=财旺拉珍.xls");/*设置响应头*/
// InputStream is = new FileInputStream("H:\\ssmconformity_code\\crm_project\\crm01\\target\\demo.xls");
ServletOutputStream outWeb = response.getOutputStream();
/* byte[] buff = new byte[256];
int len = 0;
while ((len = is.read(buff))!=-1){
outWeb.write(buff,0,len);
}
is.close();
outWeb.flush();//刷新,为什么要刷新,不刷新会有什么错误!?*/
/**
* 如果不刷新,会丢失缓存里未被的保存的数据。
*/
wb.write(outWeb);
wb.close();
outWeb.flush();
}else {
System.out.println("没有数据,不用返回给浏览器,下载");
}
}
以excle的形式将服务器数据库里的记录下到本地,首先是在后台将记录从数据库查出来然后将数据整理成excle文件(通过插件),然后将excle文件写给客户端浏览器,OutputStream outWeb = response.getOutputStream();wb.wirte(outWeb);这是优化之后的写法,也有一种写法是将excle写到磁盘,然后又从磁盘读取到内存写给客户端,这个显然是低效的因为与磁盘的交互是非常费时的。
一些需要注意的:下面分别是设置数据的类型和,设置响应头(以直接下载而不是打开,默认为打开)
response.setContentType("application/octet-stream;charset=utf-8");/*设置数据类型*/
response.setHeader("Content-Disposition","attachment;filename=demo.xls");/*设置响应头*/
文件的上传
配置文件上传的解析,首先需要明确文件的上传是需要通过post请求,
需要引入依赖 (在pom.xml中引入下面的依赖)
<!-- 文件上传依赖-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
在springmvc配置文件中配置文件上传解析器:
<!-- 配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="#{1024*1024*10}"/><!--对上上传文件的大小限制,这里是10M-->
<property name="defaultEncoding" value="utf-8"/><!--编码格式-->
</bean>
配置上传解析器时一定要注意不能将id属性写错,这里的id是程序员需要和springmvc之间的约定,写成别的就会找不到,而出现一系列的问题!
HTML的编写,首先需要明确上传数据是以form 表单,post请求的方式提交,核心的代码如下:
<form th:action="@{/testUp}" method="post" enctype="multipart/form-data">
文件上传:<input type="file" name="photo"/><br>
<input type="submit" value="上传">
</form>
小提示:
enctype:上传数据的编码类型,multipart/form-data 以字节流的方式上传,我们平常所传的数据其实也是有编码的处理,只是不用我们自己去操作而已,浏览器会默认以 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 的方式处理。也就是默认以字符串的方式处理所以但我们上传文件时需要手动指定数据以字节流的方式上传。
控制层代码:
有一次异常是说文件为空,空指针异常,(配置了springmvc的文件上传的驱动,在前台也是正确设置了编码格式(以字节流的方式),在控制台也是用了springmvc的Multipart file 去接收的)
java.lang.NullPointerException: Cannot invoke "org.springframework.web.multipart.MultipartFile.getOriginalFilename()" because "myFile" is null
既然出现了异常是要解决的:
是因为马虎将参数名字不匹配导致的
在表单内name=myFile
<input type='file' name='myfile'>
在控制层却用 photo接收,当然是接不到
public String testUp(MultipartFile photo){}
import java.io.File;
import java.util.UUID;
class up {
@RequestMapping(value = "/testUp", method = RequestMethod.POST)
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
String originalFilename = photo.getOriginalFilename();//获取原先的文件名
String suffixName = filename.substring(filename.lastIndexOf("."));
String fileName = UUID.randomUUID().toString();
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photoFolder");
File file = new File(photoPath);
if(!file.exists()){
file.mkdir();
}
String path = photoPath + File.separator + suffixName;
photo.transferTo(new File(path));
return "nice";
}
}
对上面代码的解读:
photo.getOriginalFilename();//获取原先的文件名
通过对上面的文件名进行字符串的处理,得到文件的后缀,为了防止多次上传时的覆盖问题,名字使用UUID的形式,在服务器的目录下创建一个
目录用于存放数据(没有则新建),然后通过将UUID和文件后缀的添加,在目录下放上传的数据
String path = photoPath + File.separator + suffixName;
photo.transferTo(new File(path));
异常处理
基于配置的异常处理
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
properties的键表示处理器方法执行过程中出现的异常
properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--
exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
-->
<property name="exceptionAttribute" value="exception"></property>
</bean>
基于注解的异常处理
//@ControllerAdvice将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {
//@ExceptionHandler用于设置所标识方法处理的异常
@ExceptionHandler(ArithmeticException.class)
//ex表示当前请求处理中出现的异常对象
public String handleArithmeticException(Exception ex, Model model){
model.addAttribute("ex", ex);
return "error";
}
}
注解配置SpringMVC
使用配置类和注解代替web.xml 和 SpringMVC配置文件的功能
WebInit 类继承 AbstractAnnotationConfigDispatcherServletInitializer 用于代替web.xml 初始化web工程
/**
* 用于web工程的初始化,用于代替web.xml
*/
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定Spring的配置类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/**
* 指定SpringMVC的配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 配置过滤器:
* @return
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceRequestEncoding(true);
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
}
/**
* url-parent
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
SpringConfig 用于代替springmvc的配置
springmvc的配置文件所能提供的一些服务,我们只需在该配置类中实现这些功能即可!
1.扫描组件
2.视图解析器
3.视图控制view-controller
4.default-servlet-handler(静态资源处理)
5.mvc注解驱动
6.文件上传解析器
7.异常处理
8.拦截器
//用于代替springMVC 的配置文件
@Configuration//将当前类标识为配置类
@ComponentScan("com.kobedu.mvc.controller")//扫描组件
@EnableWebMvc//注解驱动
public class WebConfig {
//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver() {
//ioc 容器在springmvc中的实现
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {//自动装配的方式进行赋值
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成视图解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
上面的只是视图解析模块
接下来就是另外的一些功能
首先我们需要认识一些注解@Configuration 用来将当前的类标识为配置类
,@CommponentScan 用于扫描组件(这是springmvc的配置文件中一定不能少的)
@EnableWebMvc 启动mvc的注解驱动
//用于代替springMVC 的配置文件
//1.扫描组件 2.视图解析器 3.视图控制view-controller 4.default-servlet-handler(静态资源处理) 5.mvc注解驱动 6.文件上传解析器 7.异常处理 8.拦截器
@Configuration//将当前类标识为配置类
@ComponentScan("com.kobedu.mvc.controller")//扫描组件
@EnableWebMvc//注解驱动
public class WebConfig implements WebMvcConfigurer {
//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver() {
//ioc 容器在springmvc中的实现
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {//自动装配的方式进行赋值
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成视图解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
//默认静态资源
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();//代表默认的servlet可用!
}
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");//添加拦截器,且设定拦截规则!
}
// mvc 试图控制组件的测试
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("hello");
}
//文件上传解析器
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}
//异常处理
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();//键值对
// prop.put("")
prop.setProperty("java.lang.ArithmeticException","error");
simpleMappingExceptionResolver.setExceptionMappings(prop);//异常映射
simpleMappingExceptionResolver.setExceptionAttribute("exception");//设置在请求域中共享异常信息的键
resolvers.add(simpleMappingExceptionResolver);
}
}