文章目录
1.响应数据和结果视图
- SpringMVC中的处理器方法的返回值用来指定页面跳转到哪个视图,处理器的返回值可以为
String,void,ModelAndView
对象。
1.1返回值为String类型:转发到字符串指定的URL
- 当
控制器
返回值为String
类型的时候,默认情况是返回逻辑视图名
,然后被视图解析器解析为物理视图地址
,最终底层通过请求转发将请求转发到对应页面。 - 实例如下:
- jsp代码:
<a href="user/testString">返回值为String</a><br>
- 控制器代码:
/**
* 返回值类型为String
*/
@RequestMapping("/testString")
public String testString(Model model){
System.out.println("testString方法执行了");
User user = new User();
user.setUsername("美羊羊");
user.setPassword("789");
user.setAge(20);
model.addAttribute("user",user);
//底层也采用的ModelAndView
return "success";
}
1.2返回值为void:转发到当前URL
- 若处理器返回
void
,表示执行完处理器方法体内代码后,不进行请求转发,而直接转发到当前URL.若没有在web.xml
中配置当前对应的url-pattern,则会返回404
错误.
@RequestMapping("/testVoid")
public void testVoid(Model model) {
// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
model.addAttribute("attribute_user", new User("张三", "123"));
// 处理器没有返回值
return;
}
- 上面的处理器方法就没有返回值,则会将请求转发到当前 项目域名
/user/testVoid
路径,若在web.xml中没有配置 项目域名/user/testVoid 对应的url-pattern,则会返回404错误
- 如果既想使用
void
返回值,同时又想对请求做出响应,比如请求转发或者重定向,那么我们就可以利用 Servlet 提供的原生 API 作为控制器方法的参数
/**
* 返回值类型为void
*/
@RequestMapping("/testvoid")
public void testvoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("testvoid方法执行了");
//请求转发
request.getRequestDispatcher("/WEB-INF/page/success.jsp").forward(request,response);
//重定向
//response.sendRedirect(request.getContextPath()+"/index.jsp");
return;
}
1.3返回值为 ModelAndView
ModelAndView
对象是Spring提供的一个对象,可以用来调整具体的JSP视图,此对象的主要方法如下:public ModelMap getModelMap()
: 返回当前页面的ModelMap
对象.public ModelAndView addObject(Object attributeValue)
: 向当前页面的ModelMap
对象中添加属性public void setViewName(@Nullable String viewName)
: 指定返回视图,viewName
会先被视图解析器处理解析成对应视图.
- 具体实例如下:
- jsp代码:
<a href="user/testModelAndView">返回值为ModelAndView</a><br>
- 控制器代码:
/**
* 返回值为ModelAndView
*/
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
System.out.println("testModelAndView方法执行了");
//创建ModelAndView对象
ModelAndView mv = new ModelAndView();
User user = new User();
user.setUsername("美羊羊");
user.setPassword("789");
user.setAge(20);
//把user对象存储到mv对象中,它底层也会把user存到request域中
mv.addObject("user",user);
//跳转到那个页面,会使用视图解释器
mv.setViewName("success");
return mv;
}
1.4SpringMVC框架提供的请求转发和重定向
- 要使用SpringMVC框架提供的请求转发,只需要在处理器方法返回的viewName字符串首加上
forward
:即可,要注意的是,此时forward:
后的地址不能直接被视图解析器解析,因此要写完整的相对路径.示例如下:
@RequestMapping("/testForward")
public String testForward() {
return "forward:/WEB-INF/pages/success.jsp";
}
在forward:要写完整的相对路径
。
- 要使用SpringMVC框架提供的请求重定向,只需要在处理器方法返回的viewName字符串首加上
redirect
:即可,要注意的是,此时redirect:
后的地址要写相对于ContextPath的地址.示例如下:
@RequestMapping("/testRedirct")
public String testRedirct() {
return "redirect:/index.jsp";
}
- 这里的路径写法不用加项目名称,底层会帮我们加上。
1.5SpringMVC响应json数据
- 在开发中,很多时候都是客户端提交 json 数据,服务器接收 json 数据并解析,然后生成 json 响应给客户端。这个时候我们就可以使用
@RequestBody
和@ResponseBody
来完成 json 数据的交互。见代码 - jsp代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Json 数据交互</title>
<script src="js/jquery-1.8.2.min.js"></script>
<script type="text/javascript">
// 页面加载
$(function () {
// 绑定单击事件
$("#btn").click(function () {
// 发送ajax请求
$.ajax({
url: "${pageContext.request.contextPath}/user/testAjax",
// 发送数据的 MIME 类型
contentType: "application/json;charset=utf-8",
// 发送数据为一个 json 串
data: '{"username":"鱼开饭","password":"123456","age":22}',
// 返回的数据类型
dataType: "json",
// 请求方式
type: "post",
// 成功回调函数
success: function (data) {
console.log(data);
console.log(data.username);
console.log(data.password);
console.log(data.age);
}
});
});
});
</script>
</head>
<body>
<button id="btn">测试 ajax 请求 json 和响应 json</button>
</body>
</html>
- 因为我们配置了前端控制器
DispatcherServlet
,因此对于所有的请求都会进行拦截,包括静态资源。为了使引入的jquery.js
不被拦截,我们需要在配置文件springmvc.xml
中配置不拦截静态资源<!-- 设置静态资源不过滤 --> <mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 --> <mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 --> <mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
location
属性 : 指定 webapp 目录下的包mapping
属性 : 表示以 /static 开头的所有请求路径
- 控制器代码:
/**
* responsebody 加在方法上或者加在返回值前面都可以
*
* @param user
*/
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user) {
System.out.println("testAjax方法执行了...");
System.out.println(user);
// 模拟查询出新的用户
user.setPassword("654321");
user.setAge(10);
return user;
}
2.SpringMVC实现文件上传
2.1传统的文件上传
- 要实现文件上传,需要下面这些前提:
<form>
表单的enctype
属性取值必须是multipart/form-data
(默认值是application/x-www-form-urlencoded
),表示表单内容是分块的.这时request
对象的getParameter()
方法将失效.<form>
表单的method
属性取值必须是post
,因为get
请求长度有限制.- 提供一个
<input/>
标签,用来选择上传文件.
<h3>传统文件上传</h3>
<form action="user/fileupload1" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload1"><br>
<input type="submit" value="上传">
</form>
- 引入文件上传的相关jar包:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
- 传统的
JavaEE
文件上传思路是通过解析request对象
,获取表单中的上传文件项并执行保存.
/**
* 文传统件上传
*/
@RequestMapping("fileupload1")
public String fileupload1(HttpServletRequest request) throws Exception {
System.out.println("文件上传");
//使用fileupload组件完成上传
//1.首先获取要上传的文件目录
String path = request.getSession().getServletContext().getRealPath("/uploads");
System.out.println(path);
//2.创建file对象,一会向该路径下上传文件
File file = new File(path);
//判断路径是否存在,如果不存在就创建该路径
if (!file.exists()){
file.mkdirs();
}
//3.创建磁盘文件项工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload fileUpload = new ServletFileUpload(factory);
//4.解析request对象
List<FileItem> fileItems = fileUpload.parseRequest(request);
//5.遍历
for (FileItem fileItem:fileItems){
//6.判断文件项是普通字段,还是要上传的文件
if (fileItem.isFormField()){
//普通字段
}else {
//上传文件项
//7.获取上传文件的名称
String filename = fileItem.getName();
//把文件的名称设为唯一值
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid + "_" + filename;
//8.上传文件
fileItem.write(new File(file,filename));
//9.删除临时文件
fileItem.delete();
}
}
return "success";
}
2.2SpringMVC方式文件上传
- 使用传统方式进行文件上传,我们需要自己解析
request
,分辨每一项究竟是普通表单项还是文件项,特别繁琐。而如果使用SpringMVC
,我们只需要配置一个文件解析器,由文件解析器来替我们解析,然后我们在控制器使用MultipartFile
对象接收即可(该对象表示要上传的文件) - 实例如下:
<h3>springMVC方式上传</h3>
<form action="user/fileupload2" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload2"><br>
<input type="submit" value="上传">
</form>
/**
* SpringMVC文件上传
*/
@RequestMapping("fileupload2")
public String fileupload2(HttpServletRequest request, MultipartFile upload2) throws Exception {
System.out.println("springMVC文件上传");
//1.首先获取要上传的文件目录
String path = request.getSession().getServletContext().getRealPath("/uploads");
System.out.println(path);
//2.创建file对象,一会向该路径下上传文件
File file = new File(path);
//判断路径是否存在,如果不存在就创建该路径
if (!file.exists()){
file.mkdirs();
}
//3.获取上传文件的名称
String filename = upload2.getOriginalFilename();
//把文件的名称设为唯一值
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid + "_" + filename;
//4.上传文件
upload2.transferTo(new File(path,filename));
return "success";
}
- 使用该方式上传文件时,要确保
MultipartFile
对象名称与表单中file
组件的名称保持一致。如果不一致,则需要使用 @RequestParam 进行参数的绑定。譬如表单为:<input type="file" name="file"/>
,则控制器方法应该为test2(HttpServletRequest request, @RequestParam("file") MultipartFile upload)
- 获取上传文件的文件名时,应该使用
getOriginalFilename()
,不能使用getName()
,getName()
是获取表单中file
组件的名称。
如果要想实现多文件上传,那么需要在file
组件加上属性multiple="multiple"
(HTML5 的新属性),然后控制器用List<MultipartFile>
接收即可。
- 在 SpringMVC 配置文件中配置文件解析器
<!-- 配置文件解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 配置上传文件最大大小,字节(Byte)为单位。这里设置 10MB=10*1024KB=10*1024*1024B -->
<property name="maxUploadSize" value="10485760"/>
<!-- 设置编码,防止中文乱码 -->
<property name="defaultEncoding" value="utf-8"/>
</bean>
2.3SpringMVC 跨服务器方式文件上传
- 在实际开发中,我们可能会有好几台服务器,有的服务器用于部署应用,而有的服务器用于存储用户上传的文件。那么,此时我们如果需要跨服务器上传文件,就需要借助
jersey-client
的jar
包。
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.18.1</version>
</dependency>
- 控制器代码:
/**
* 跨服务器文件上传
*/
@RequestMapping("fileupload3")
public String fileupload3(MultipartFile upload3) throws Exception {
System.out.println("跨服务器文件上传文件上传");
//1.定义上传文件服务器的路径
String path = "http://localhost:9090/day02_springMVC_03fileuploadserver_war_exploded/uploads/";
//2..获取上传文件的名称
String filename = upload3.getOriginalFilename();
//把文件的名称设为唯一值
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid + "_" + filename;
//3.完成上传文件,跨服务器上传
//3.1创建客户端对象
Client client = Client.create();
//3.2和文件服务器进行连接
WebResource webResource = client.resource(path + filename);
//3.上传
webResource.put(upload3.getBytes());
return "success";
}
- 想要测试跨服务器文件上传,需要
部署两个 Tomcat 服务器
,一个服务器部署应用,一个服务器用于接收文件。这里将存放文件的路径指定为接收文件项目的根目录下的uploads
文件夹。- 如果程序报 405 错误,那么应该在文件服务器的 Tomcat 的配置文件
conf\web.xml
中加入以下配置,表示允许服务器进行读写操作(默认只读)<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param> </servlet>
- 如果程序报 409 错误,那么应该在文件服务器的
target
目录下创建uploads
文件夹
3.SpringMVC 中的异常处理
- 在开发中,一般我们都是
Controller
调用Service
,Service
调用Dao
,在这个过程中,如果有某个地方出现了异常,那么异常都会被往上一层一层throws Exception
(抛出)。Dao
抛给Service
,Service
抛给DispatcherServlet
,最后由DispatcherServlet
将异常交给HandlerExceptionResolver
(异常处理器)进行处理。 - 编写自定义异常类,继承
Exception
类
/**
* 自定义异常类
*/
public class SysException extends Exception{
//存储提示信息
private String message;
public SysException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
- 编写错误页面
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>出错啦。。</title>
</head>
<body>
${errorMsg}
</body>
</html>
- 编写
自定义异常处理器
,需要实现HandlerExceptionResolver
接口
/**
* 异常处理器
*/
public class SysExceptionResolver implements HandlerExceptionResolver {
/**
* 处理异常的业务逻辑
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @param e 控制所抛出的exception对象
* @return
*/
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//获取到异常对象
SysException ex = null;
if (e instanceof SysException){
ex = (SysException)e;
}else{
ex = new SysException("系统正在维护中。。。");
}
//创建ModelAndView对象
ModelAndView mv = new ModelAndView();
//把异常信息存入到request域中
mv.addObject("errorMsg",ex.getMessage());
//设置跳转页面
mv.setViewName("error");
return mv;
}
}
- 在 SpringMVC 配置文件中配置异常处理器
<!-- 配置异常处理器 -->
<bean id="sysExceptionResolver" class="com.zut.exception.SysExceptionResolver"></bean>
- 控制层测试代码:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testException")
public String testException() throws SysException {
System.out.println("testException方法执行了。。。");
//模拟异常
try {
int i = 1/0;
} catch (Exception e) {
//打印异常信息
e.printStackTrace();
//抛出自定义异常
throw new SysException("查询数据库时出现错误。。。");
}
return "success";
}
}
4.SpringMVC 中的拦截器
4.1拦截器概述
-
SpringMVC 中的
Interceptor
(拦截器) 类似于 Servlet 中的Filter
(过滤器),拦截器是用于对Controller(处理器
)进行预处理和后处理的。和过滤器类似,过滤器可以定义过滤链,拦截器也可以定义拦截器链(Interceptor Chain),也就是将拦截器按一定的顺序联结成一条链,在访问被拦截的方法之前,拦截器链中的拦截器就会按之前定义的顺序进行拦截。 -
过滤器和拦截器虽然比较类似,但是还是有区别的:
- 过滤器是 Servlet 规范中的,也就是说,只要是 JavaWeb 工程就可以使用;而拦截器是 SpringMVC 框架的,只有使用了 SpringMVC 框架的工程才可以使用
- 过滤器可以拦截所有的资源,也就是不仅可以拦截
Controller
,其它的所有资源(像 jsp、js 这些静态资源)它都可以拦截;而拦截器是针对Controller
的,它只能拦截控制器。 - 拦截器是
AOP 思想
的一种实现方式
-
如果想自定义拦截器,那么就需要实现
HandlerInterceptor
接口,该接口中有 3 个default
方法:boolean preHandle(..)
: 该方法在Controller
方法执行前调用。如果返回 true 就代表放行,执行下一个拦截器,如果没有拦截器,那就执行Controller
的方法;如果返回 false 就代表不放行,此时可以使用转发或者重定向跳转到指定的页面。该方法是按拦截器定义顺序
进行调用的。void postHandle(..)
: 该方法在Controller
方法执行后(但是在视图渲染之前)调用。此时我们可以通过ModelAndView
(模型和视图对象)来对模型数据或视图进行处理,也可以通过HttpServletRequest
和HttpServletResponse
进行转发或者重定向。该方法是按拦截器定义逆序进行调用的。void afterCompletion(..)
: 该方法在整个请求处理完毕后调用
(也就是在视图渲染完毕后)。 可以在该方法中进行一些资源清理的操作(不能在该方法中进行页面的跳转了),相当于try...catch...finally 中的 finally
。该方法是按拦截器定义逆序进行调用的,而且只有preHandle()
返回 true 才调用。
4.2自定义拦截器
- 创建自定义拦截器,实现
Interceptor
接口并重写其中的方法
/**
* 自定义拦截器
*/
public class MyInterceptor1 implements HandlerInterceptor {
/**
* 预处理 controller方法前执行
* return true 放行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor1执行了...前");
return true;
}
/**
*后处理 controller方法执行后,success.jsp执行之前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor1执行了...后");
return;
}
/**
*success.jsp页面执行后,该方法会执行
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
- 在 SpringMVC 配置文件中配置拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 要拦截的方法 -->
<mvc:mapping path="/user/**"/>
<!-- 不要拦截的方法 -->
<!-- <mvc:exclude-mapping path=""/>-->
<!-- 配置拦截器对象 -->
<bean id="myInterceptor1" class="com.zut.interceptor.MyInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
- 编写控制器代码
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testInterceptor")
public String testException() {
System.out.println("testInterceptor方法执行了。。。");
return "success";
}
}
- 编写jsp代码
<html>
<head>
<title>拦截器</title>
</head>
<body>
<h3>拦截器</h3>
<a href="user/testInterceptor">拦截器</a>
</body>
</html>
4.3多个拦截器
- 如果有多个拦截器,此时拦截器 1 的
preHandle
方法返回true
,但是拦截器 2 的preHandle
方法返回false
,那么拦截器 1 的afterCompletion
的方法是否执行?- 因为我们上面说过,
afterCompletion
方法只有当preHandle
返回true
的时候执行。所以,拦截器 1 的afterCompletion
方法会执行,而拦截器 2 的afterCompletion
方法不会执行
- 因为我们上面说过,
- 如果某个拦截器在
postHandle
方法中使用request
和response
进行转发,那么最终的结果视图(这里是success.jsp
)还会不会执行?- 我们上面说过,
postHandle
方法是在控制器执行后,视图渲染前被调用。所以,当我们转发到某个页面时,浏览器会把这个页面展示出来,但是postHandle
方法还是会继续执行,也就是说success.jsp
还是会被执行到,只不过不会再被渲染展示了。
- 我们上面说过,
- 如果某个拦截器在
postHandle
方法中不通过request
进行转发,而是使用ModelAndView.setViewName("error")
,那么最终的结果视图(这里是success.jsp
)还会不会执行?- 因为
ModelAndView
中存储的是结果视图,也就是控制器方法的返回值success
,此时我们直接调用setViewName()
方法,就相当于修改了控制器方法的返回值,所以最终只会跳转到error.jsp
而不是success.jsp
- 因为