文章目录
1 自定义类型转换器
控制器中接收Date类型的参数,SpringMVC要求前端输入的日期格式必须为yyyy/MM/DD
可以使用自定义转换器,改变日期格式
第一步 自定义日期转换器:
package com.yiwu.utils;
import org.springframework.core.convert.converter.Converter;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
* 1:创建一个类实现Converter接口,
* 2.泛型指定从什么类型转换为什么类型
* 3:实现Converter转换方法
* */
public class MyDateConverter implements Converter<String, Date> {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public Date convert(String s) {
Date date = null;
try {
date = sdf.parse(s);
} catch (Exception e) {
System.out.println("格式不对");
}
return date;
}
}
第二步,编辑配置文件 spring-mvc.xml
<!--自定义转换器-->
<bean id="converterFactory" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<!-- 这里可以配置多个-->
<bean class="com.liguoqing.utils.MyDateConverter"/>
</set>
</property>
</bean>
<!--装载转换器-->
<mvc:annotation-driven conversion-service="converterFactory"/>
第三步,编写控制器方法
@RequestMapping("/test08")
public String test08(Date date) {
System.out.println(date);
return null;
}
第四步,测试
浏览器输入该地址进行测试,localhost:8080/demo2_war/user/test08/?date=2011-5-5
2 统一编码
保持,前端页面,服务器编码,springMVC编码一致,解决中文编码问题
-
jSP页面
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
-
html页面
<meta charset="UTF-8">
-
服务器编码
tomcat/conf/server.xml
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/>
-
设置SpringMVC的编码方式
- 在web.xml中配置SpringMVC编码过滤器的编码方式
<filter> <filter-name>EncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</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> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3 异常处理
Dao、Service、Controller各个层中可能出现问题,若空指针,IO异常等,若不进行异常处理页面会出现异常信息,用户不友好。
进行异常处理有三种方式
- 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver。
- 实现 Spring 的异常处理接口 HandlerExceptionResolver,自定义自己的异常处理器。
- 使用 @ExceptionHandler 注解实现异常处理
3.1简单异常处理器
第一步,建立三个不同异常处理页面,error1.jsp,error2.jsp,error3.jsp
第二步,修改配置文件,配置异常与页面的映射关系。
<bean class = "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--默认异常处理页面-->
<property name="defaultErrorView" value="pages/errors/error3"/>
<!--为不同异常配置不同的异常处理页面-->
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException">pages/errors/error1</prop>
<prop key="IOException">pages/errors/error2</prop>
</props>
</property>
<!--自定义异常信息的变量为ex,默认为exception-->
<property name="exceptionAttribute" value="ex"/>
</bean>
第三步,创建控制器方法,人为制造异常,访问对应方法验证。
//空指针异常
@RequestMapping("/test12")
public String test12() {
ArrayList<String> strings = null;
String s = strings.get(1);
System.out.println(s);
return null;
}
//存在IO异常
@RequestMapping("/test13")
public String test13() throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("aaaa.jsp");
return null;
}
//其他异常
@RequestMapping("/test14")
public String test14() {
int i = 5 / 0;
return null;
}
3.2自定义异常处理器
实现 Spring 的异常处理接口 HandlerExceptionResolver,自定义自己的异常处理器。
当一个类实现了 HandlerExceptionResolver 接口后,只要有异常发生,无论什么异常,都会自动执行接口方法 resolveException()。
第一步,创建自定义的异常处理器
package com.yiwu.controllers;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class myExceptionHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView mv = new ModelAndView();
//将异常对象加入数据模型中
//mv.addObject("ex",ex);
//为了用户友好,可以写入网络故障等信息
mv.addObject("ex","网络故障");
//设置默认错误响应页面
mv.setViewName("pages/errors/error3");
//设置NullPointerException响应页面
if(ex instanceof java.lang.NullPointerException){
mv.setViewName("pages/errors/error1");
}
//设置IOException响应页面
if(ex instanceof IOException){
mv.setViewName("pages/errors/error2");
}
return mv;
}
}
第二步,在spring-mvc.xml中注册
<bean class="com.yiwu.controllers.myExceptionHandler"/>
第三步,使用3.1简单异常处理器中的测试方法验证。
3.3 使用注解自定义异常处理方法
-
在类上使用@Controller注解的增强注解@ControllerAdvice注解
-
在对应的方法上使用@ExceptionHandler(xxx.class),
- 该注解可以将一个方法指定为异常处理方法,
- 其中xxx为异常类型,可以是自定义的异常类型。
第一步,编写异常处理类
package com.yiwu.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
@ControllerAdvice
public class myExceptionHandler{
//空指针异常
@ExceptionHandler(java.lang.NullPointerException.class)
public ModelAndView handleNameException(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("ex","网络故障");
mv.setViewName("pages/errors/error1");
return mv;
}
//IO异常处理
@ExceptionHandler(IOException.class)
public ModelAndView handleAgeException(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("ex","网络故障");
mv.setViewName("pages/errors/error2");
return mv;
}
//其他异常处理
@ExceptionHandler
public ModelAndView handleOtherException(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("ex","网络故障");
mv.setViewName("pages/errors/error3");
return mv;
}
}
第二步,测试,继续使用3.2节中的测试方法,结果一致
4 自定义404页面
新建自定义的404显示页面
4.1 方式一
利用web容器提供的error-page标签。
<error-page>
<error-code>404</error-code>
<location>/pages/errors/404.jsp</location>
</error-page>
4.2 方式二
-
利用Spring MVC的最精确匹配,设置统匹配符。
-
若Controller中方法1的访问路径/test/a, 方法2的访问路径为/test/*,
- 请求路径test/a,访问到的是方法1,
- 请求路径test/(任意值),访问到的是方法2
在UserController中添加方法,设置RequestMapping(“*”)
@RequestMapping("*")
public String to404(){
return "pages/errors/404.jsp";
}
则http://localhost:8080/demo2_war/user/xxxx,若不存在RequestMapping(“xxx”)的方法,则会跳转到自定义的404页面。
4.3 方法三
重写noHandlerFound方法,实现自定义的DispatcherServlet
第一步,继承DispatcherServlet,重写noHandlerFound方法。
package com.yiwu.utils;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyDispatcherServlet extends DispatcherServlet {
protected void noHandlerFound(HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.sendRedirect(request.getContextPath() + "/pages/errors/4042.jsp");
}
}
第二步,修改web.xml文件
<servlet>
<servlet-name>testServlet</servlet-name>
<!--修改成自己重写的类-->
<servlet-class>com.yiwu.utils.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
5 拦截器
5.1 简介
拦截器(Interceptor)
可以动态的拦截Controller方法调用的对象,可以在指定的方法调用前后执行预设的代码,与过滤器(Filter)类似,拦截是AOP的一种实现策略。
过滤器
几乎是对所有请求进行过滤,但是缺点是一个过滤器实例,只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码等。
拦截器链(多拦截器)
如果多个拦截器能够对相同的请求进行拦截,则多个拦截器会形成一个拦截器链。拦截器链中多个拦截器的执行顺序,根拦截器的配置顺序有关,先配置的先执行。
区别
- 过滤器(filter):
- filter属于Servlet技术,只要是web工程都可以使用
- ilter主要对所有请求过滤
- filter的执行时机早于Interceptor
- 拦截器(interceptor)
- interceptor属于SpringMVC技术,必须要有SpringMVC环境才可以使用
- interceptor通常对处理器Controller进行拦截
- interceptor只能拦截dispatcherServlet处理的请求!
有两种实现方式
- 实现HandlerInterceptor接口
- 继承HandlerInterceptor接口的实现类HandlerInterceptorAdapter
5.2 实现方式一
实现HandlerInterceptor接口,该接口中有三个方法
-
preHandle()
- 在控制器调用方法之前执行,返回值为biilean类型,若结果为true,则继续后续执行,否则请求结束,不再进行后续操作。
- 常用于安全控制,权限校验。
-
postHandle()
- 在控制器调用方法之后,视图解析之前执行。
- 常用于对模型和视图进行进一步修改。
-
afterCompletion()
- 在整个请求结束之后执行,也就是在视图渲染结束之后。
- 常用于资源的清理,日志工作等。
单个拦截器执行流程
多个拦截器执行流程
- 按照xml的配置顺序,preHandle的执行顺序与配置的顺序,其余相反。
第一步,编写拦截器类实现HandlerInterceptor接口
package com.yiwu.interceptor;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.swing.plaf.SliderUI;
public class MyInterceptor1 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("执行了preHandle方法");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
System.out.println("执行了postHandle方法");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
System.out.println("执行了afterCompletion方法");
}
}
第二步,修改配置文件springmvc-config.xml
- 将bean直接定义在mvc:interceptors中会拦截所有请求
- 可以使用mvc:interceptor定制拦截细节
- 一下案例中回拦截除了http://localhost:8080/demo2_war/user/test02之外的http://localhost:8080/demo2_war/user/xxx所有请求。
<mvc:interceptors>
<!-- 将bean直接定义在mvc:interceptors中会拦截所有请求-->
<!-- <bean class="com.yiwu.interceptor.MyInterceptor1"/>-->
<!-- 可以使用mvc:interceptor定制拦截细节-->
<mvc:interceptor>
<!-- 拦截器作用的路径-->
<mvc:mapping path="/user/*"/>
<!--拦截器排除的路径-->
<mvc:exclude-mapping path="/user/test02"/>
<!--拦截器-->
<bean class="com.yiwu.interceptor.MyInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
5.3实现方式二
继承HandlerInterceptor接口的实现类HandlerInterceptorAdapter
该实现类中多了一个afterConcurrentHandlingStarted(),用于处理异步请求,Controller中有异步方法的时候会触发该方法也就是,返回异步结果时调用。
其余使用方式同方法一
6 文件上传下载
- 上传图书信息,书名,价格,封面图片。
6.1 文件的上传
SpringMVC处理上传文件需要借助MultipartResolver接口的实现类CommonsMultipartResolver,
第一步,在springmvc-config.xml中配置文件解析器
初始化MultipartResolver,会在BeanFactory中查找名为multipartResolver的实现类,所以id应该设置为multipartResolver,不能更改。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--上传文件的最大值,单位为字节-->
<property name="maxUploadSize" value="10240000"/>
<!--缓存中的最大值,单位为字节-->
<property name="maxInMemorySize" value="10240"/>
<!--默认编码格式-->
<property name="defaultEncoding" value="utf-8"/>
</bean>
第二步,引入依赖
-
ommons-io
-
commons-fileupload(根据依赖传递性,仅引入此即可)
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
第三步,编写前端上传及展示页面,fileTest.jsp
-
enctype="multipart/form-data"表单在发送到服务器时候编码方式,有如下的三个值。
-
application/x-www-form-urlencoded。默认的编码方式。但是在用文本的传输和MP3等大型文件的时候,使用这种编码就显得 效率低下。
-
multipart/form-data 。 指定传输数据为二进制类型,比如图片、mp3、文件。
-
text/plain。纯文体的传输。空格转换为 “+” 加号,但不对特殊字符编码。
-
-
”multiple="multiple“
- 若设置<input type=“file” multiple=“multiple” name=“imgFile”>上传文件时候可以选择多个文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="../book/add" method="post" enctype="multipart/form-data">
<span>书名:</span><input type="text" name = "bookName"/><br/><br/>
<span>价格:</span><input type="text" name = "bookPrice"/><br/><br/>
<span>封面: </span> <input type="file" name="imgFile"><br/><br/>
<input type="submit" value="提交"><br/><br/>
</form>
</body>
</html>
第四步、编写实体类Book.java,在pages下建立imgs目录,作为上传文件的保存目录
需要注意,当建立完imgs文件夹后,往文件夹内放置任意一个文件,否则无法打包到服务器。
package com.yiwu.pojo;
public class Book {
String bookName;
String bookPrice;
String bookPic;
//省略setter和getter方法
}
第五步,编写控制器Controller
package com.yiwu.controllers;
import com.yiwu.pojo.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@Controller
@RequestMapping("/book")
public class BookController {
@RequestMapping("/add")
public String addBook(Book book, MultipartFile imgFile, HttpServletRequest request) throws IOException {
System.out.println("~~~~addBook");
System.out.println(book);
System.out.println(imgFile);
//ImgFile表示上传的图片
//1:截取上传文件的后缀名,生成新的文件名
String originalFilename = imgFile.getOriginalFilename();
String ext = originalFilename.substring( originalFilename.lastIndexOf(".") );// .jpg
String filename = System.currentTimeMillis()+ext;
//2:获取imgs目录在服务器的路径
String dir = request.getServletContext().getRealPath("pages/imgs");
String savePath = dir + "/" + filename;//存储路径(C:/asd/ad/imgs/img.jpg)
//3:保存文件
imgFile.transferTo(new File(savePath));
//4:将图片的访问路径设置到对象
book.setBookPic("imgs/"+ filename);
System.out.println(book);
//5:调用service保存book到数据库
return "success";
}
}
目录结构如下
6.2 文件下载
第一种可以直接向response的输出流中写入对应的文件流
第二种可以使用 ResponseEntity<byte[]>来向前端返回文件
第一种实现方式
@RequestMapping("/down1")
public void downBook1(String fileName, HttpServletRequest request, HttpServletResponse response) throws IOException {
String dir = request.getServletContext().getRealPath("pages/imgs");
String savePath = dir + "/" + fileName;//存储路径(C:/asd/ad/imgs/img.jpg)
System.out.println(savePath);
FileInputStream fileInputStream = new FileInputStream(savePath);
response.setCharacterEncoding("UTF-8"); //字符编码
response.setContentType("multipart/form-data"); //二进制传输数据
response.setHeader("Content-Disposition","attachment;fileName="+fileName);
IOUtils.copy(fileInputStream,response.getOutputStream());
}
http://localhost:8080/demo2_war/book/down1?fileName=xxxxx访问该地址,可下载对应文件
第二种实现方式
@RequestMapping("/down2")
public ResponseEntity<byte[]> downBook2(String fileName, HttpServletRequest request) throws IOException {
String dir = request.getServletContext().getRealPath("pages/imgs");
String savePath = dir + "/" + fileName;//存储路径(C:/asd/ad/imgs/img.jpg)
File file = new File(savePath);
HttpHeaders headers=new HttpHeaders();
headers.setContentDispositionFormData("attachment",fileName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
byte[] bytes = FileUtils.readFileToByteArray(new File(savePath));
ResponseEntity<byte[]> entity=new ResponseEntity<byte[]>(bytes,headers, HttpStatus.OK);
return entity;
}