SpringMVC核心技术

请求转发与重定向

SpringMVC 框架把原来Servlet 中的请求转发和重定向操作进行了封装。
现在可以使用简单的方式实现转发和重定向。
forward:表示转发,实现request.getRequestDispatcher(“xx.jsp”).forward()
redirect:表示重定向,实现response.sendRedirect(“xxx.jsp”)

1、请求转发

在框架中,使用forward,redirect,与视图解析器无关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Controller
public class MyController {
/**
* 框架的 forward请求转发
* @param name
* @param age
* @return
*/
@RequestMapping(value = "/doForward")
public ModelAndView doForward(String name,Integer age) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg","Controller请求转发跳转成功~");
modelAndView.addObject("name",name);
modelAndView.addObject("age",age);
// 使用forward显式转发
// 一般用于跳转到视图解析器中没有指定的路径
modelAndView.setViewName("forward:/WEB-INF/view/show.jsp");
return modelAndView;
}
}

请求页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<% String basePath = request.getContextPath()+"/";%>
<html>
<head>
<title>index.jsp</title>
<base href="<%=basePath%>"/>
</head>
<body>
<form action="doForward" method="post">
姓名: <input type="text" name="name"><br/>
年龄: <input type="number" name="age"><br/>
<input type="submit" value="请求转发">
</form>
</body>
</html>

转发页面:

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>show.jsp</title>
</head>
<body>
<p>msg数据:${msg}</p>
<p>name:${name}</p>
<p>age:${age}</p>
</body>
</html>

2、重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 框架的 redirect 重定向
* 1、框架会把 Model中的简单数据类型的数据,转换为String使用,作为hello.jsp的get请求参数
* 目的是在 doRedirect 和 hello.jsp 两次请求之间传递数据
* 2、在目标 hello.jsp中使用参数集合对象${param}获取请求参数值
* @param name
* @param age
* @return
*/
@RequestMapping(value = "/doRedirect")
public ModelAndView doRedirect(String name,Integer age) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg","Controller重定向跳转成功~");
modelAndView.addObject("name",name);
modelAndView.addObject("age",age);
// 使用redirect重定向
modelAndView.setViewName("redirect:/hello.jsp");
return modelAndView;
}

请求页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<% String basePath = request.getContextPath()+"/";%>
<html>
<head>
<title>index.jsp</title>
<base href="<%=basePath%>"/>
</head>
<body>
<form action="doRedirect" method="post">
姓名: <input type="text" name="name"><br/>
年龄: <input type="number" name="age"><br/>
<input type="submit" value="重定向">
</form>
</body>
</html>

重定向页面:

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>hello.jsp</title>
</head>
<body>
<p>msg数据:${param.msg}</p>
<p>name:${param.name}</p>
<p>age:${param.age}</p>
</body>
</html>

异常处理

springmvc采用全局异常处理,把controller中的异常处理集中到一个地方。采用aop的思想,将业务处理与异常处理分开,解耦合。

1、为了方便演示,自定义了几个异常类

自定义用户异常类

1
2
3
4
5
6
7
8
9
public class MyUserException extends Exception {
public MyUserException() {
super();
}

public MyUserException(String message) {
super(message);
}
}

再定义两个子类继承用户异常类

1
2
3
4
5
6
7
8
9
public class AgeException extends MyUserException {
public AgeException() {
super();
}

public AgeException(String message) {
super(message);
}
}
1
2
3
4
5
6
7
8
9
public class NameException extends MyUserException {
public NameException() {
super();
}

public NameException(String message) {
super(message);
}
}

2、编写专门处理异常的controller类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@ControllerAdvice// 控制器增强,增加异常处理功能 需要配置组件扫描器,声明此类所在的包名
public class GlobalExceptionHandler {
// 定义方法处理异常
/**
* 处理 name 异常的方法
*
* @param e controller抛出的异常对象
* @return
*/
@ExceptionHandler(value = NameException.class)// 要处理的异常类型
public ModelAndView doNameException(Exception e) {
// 1、记录异常,记录到数据库,日志文件,记录日志发生的时间,哪个方法发生的异常,异常错误内容
// 2、发送通知,把异常信息发送给相关人员
// 3、用户友好的提示
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", "姓名必须是zyz!");
modelAndView.addObject("ex", e);
modelAndView.setViewName("nameError");
return modelAndView;
}

@ExceptionHandler(value = AgeException.class)// 要处理的异常类型
public ModelAndView doAgeException(Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", "年龄必须小于80!");
modelAndView.addObject("ex", e);
modelAndView.setViewName("ageError");
return modelAndView;
}

// 处理未知异常 在开发过程中一般像这样使用,编写一个方法就能处理所有的异常了
@ExceptionHandler
public ModelAndView doOtherException(Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", "其他异常");
modelAndView.addObject("ex", e);
modelAndView.setViewName("defaultError");
return modelAndView;
}
}

3、添加组件扫描器,声明处理异常的方法所在的包

1
2
3
4
<!-- 注解驱动-->
<mvc:annotation-driven/>
<!-- 处理异常-->
<context:component-scan base-package="com.zyz.handler"/>

4、编写异常跳转的页面

拦截器

拦截器(interceptor),是一种面向切面编程的实现,将多个模块的通用服务进行分离,如权限管理、日志服务,就可以将其各自封装为一个可重用模块。而这些通用服务的具体实现是通过拦截器来完成,比如用户客户端访问一些保密模块都应先通过权限审查的拦截器来进行权限审查,确定用户是否具有该项操作的权限后方能向下执行。在面向切面编程中就是在调用业务方法前调用一个方法,或者在调用业务方法后调用一个方法。

拦截器的执行时间:

1、在请求处理之前,也就是controller类中的方法执行之前先被拦截。
2、在控制器方法执行之后也会执行拦截器。
3、在请求处理完成后也会执行拦截器。

拦截器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 拦截器类,拦截用户请求
public class MyInterceptor implements HandlerInterceptor {
/**
* 预处理方法
* 1、在控制器方法之前执行
* 2、获取请求信息,验证请求是否符合要求
* 可以验证用户是否登录,是否有权限访问某个链接地址 url
* 验证失败,可以截断请求,请求不能被处理。
* 验证成功,可以放行请求,执行控制器方法
* @param request
* @param response
* @param handler 被拦截的控制器对象
* @return true 表示验证成功,可以执行处理器中方法
* false 表示验证失败,请求到达拦截器就截止了,请求没有被处理
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器MyInterceptor的preHandle()");

// 给浏览器一个返回结果
request.getRequestDispatcher("/tips.jsp").forward(request,response);
return false;
}

/**
* 后处理方法
* 1、在处理器方法之后执行
* 2、能够获取到处理器方法中的返回值ModelAndView,并且可以修改
* 3、主要对原来的执行结果做修正
* @param request
* @param response
* @param handler 被拦截的控制器对象
* @param modelAndView 处理器方法的返回值
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截器MyInterceptor的postHandle()");
}

/**
* 最后执行的方法
* 1、请求处理完成后执行,即对视图进行了forward
* 2、一般是做资源回收,释放内存
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("拦截器MyInterceptor的afterCompletion()");
}
}

声明拦截器

1
2
3
4
5
6
7
8
9
<!-- 声明拦截器  可以有多个-->
<mvc:interceptors>
<mvc:interceptor>
<!-- 指定拦截器的url-->
<mvc:mapping path="/**"/>
<!--声明拦截器对象-->
<bean class="com.zyz.handler.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>

preHandle()返回true时:


返回false,跳转到提示页面,请求被终止,不会执行preHandle()以后的方法

当有多个拦截器时

拦截器与过滤器的区别

过滤器,是在java web中将你传入的request、response提前过滤掉一些信息,或者提前设置一些参数。然后再传入Servlet或Struts2的 action进行业务逻辑处理。比如过滤掉非法url,或者在传入Servlet或Struts2的action前统一设置字符集,或者去除掉一些非法字符。

通俗理解:
(1)过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。
(2)拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。

文件上传和下载

文件上传

1、导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- springmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!--servlet-api导入高版本的-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>

​ 2、如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver

1
2
3
4
5
6
7
8
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>

3、编写上传文件的表单

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="upload2" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit" value="upload">
</form>
</body>
</html>

4、编写处理文件上传的Controller方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Controller
public class FileController {
@RequestMapping("/upload")
//@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
//批量上传CommonsMultipartFile则为数组即可
public String fileUpload(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {

//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();

//如果文件名为空,直接回到首页!
if ("".equals(uploadFileName)) {
return "redirect:/index.jsp";
}
System.out.println("上传文件名 : " + uploadFileName);

//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()) {
realPath.mkdir();
}
System.out.println("上传文件保存地址:" + realPath);

InputStream is = file.getInputStream(); //文件输入流
OutputStream os = new FileOutputStream(new File(realPath, uploadFileName)); //文件输出流

//读取写出
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}

/*
* 采用file.TransferTo 来保存上传的文件
*/
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {

//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();

//如果文件名为空,直接回到首页!
if ("".equals(uploadFileName)) {
return "redirect:/index.jsp";
}
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()) {
realPath.mkdir();
}
//上传文件地址
System.out.println("上传文件保存地址:" + realPath);

//通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(new File(realPath + "/" + file.getOriginalFilename()));

return "redirect:/index.jsp";
}
}

文件下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@RequestMapping(value="/download")
public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception{
//要下载的图片地址
String path = request.getServletContext().getRealPath("/upload");
String fileName = "基础语法.png";
//1、设置response 响应头
response.reset(); //设置页面不缓存,清空buffer
response.setCharacterEncoding("UTF-8"); //字符编码
response.setContentType("multipart/form-data"); //二进制传输数据

// 判断浏览器类型
String ua = request.getHeader("User-Agent");
String str="";
// 判断是否是火狐浏览器
if (ua.contains("Firefox")) {
// 使用下面的格式进行BASE64 编码后
str = "=?utf-8?B?" + new BASE64Encoder().encode(fileName.getBytes("utf-8")) + "?=";
} else {
// 把中文名进行UTF-8 编码操作
str = URLEncoder.encode(fileName, "UTF-8");
}
//设置响应头
response.setHeader("Content-Disposition",
"attachment;fileName="+ str);

File file = new File(path,fileName);
//2、 读取文件--输入流
InputStream input=new FileInputStream(file);
//3、 写出文件--输出流
OutputStream out = response.getOutputStream();

byte[] buff =new byte[1024];
int index=0;
//4、执行 写出操作
while((index= input.read(buff))!= -1){
out.write(buff, 0, index);
out.flush();
}
out.close();
input.close();
return "redirect:/index.jsp";
}
1
<a href="download">下载</a>