SpringMVC 第二天 第1章 响应数据和结果视图
1.1返回值分类
1.1.1 字符串
controller 方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
//指定逻辑视图名,经过视图解析器解析为 jsp 物理路径:/WEB-INF/pages/success.jsp
@RequestMapping("/testReturnString") public String testReturnString() {
System.out.println("AccountController 的 testReturnString 方法执行了。。。。");
return "success"; }
运行结果:
1.1.2 void
在昨天的学习中,我们知道 Servlet 原始 API 可以作为控制器中方法的参数:
@RequestMapping("/testReturnVoid")
throws Exception {
}
在 controller 方法形参上可以定义 request 和 response,使用 request 或 response 指定响应结果: 1、使用 request 转向页面,如下:
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
2、也可以通过 response 页面重定向:
response.sendRedirect("testRetrunString")
3、也可以通过 response 指定响应结果,例如响应 json 数据:
response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=utf-8"); response.getWriter().write("json 串");
1.1.3 ModelAndView
ModelAndView 是 SpringMVC 为我们提供的一个对象,该对象也可以用作控制器方法的返回值。 该对象中有两个方法:
示例代码:
/**
* 返回 ModeAndView * @return
*/
@RequestMapping("/testReturnModelAndView") public ModelAndView testReturnModelAndView() {
ModelAndView mv = new ModelAndView();
mv.addObject("username", "张三");
mv.setViewName("success");
return mv; }
响应的 jsp 代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>执行成功</title> </head>
<body>
执行成功!
${requestScope.username}
</body> </html>
输出结果:
注意:
我们在页面上上获取使用的是 requestScope.username 取的,所以返回 ModelAndView 类型时,浏 览器跳转只能是请求转发。
1.2转发和重定向
1.2.1 forward转发
controller 方法在提供了 String 类型的返回值之后,默认就是请求转发。我们也可以写成:
/**
* 转发
* @return */
@RequestMapping("/testForward") public String testForward() {
System.out.println("AccountController 的 testForward 方法执行了。。。。");
return "forward:/WEB-INF/pages/success.jsp"; }
需要注意的是,如果用了 formward:则路径必须写成实际视图 url,不能写逻辑视图。
它相当于“request.getRequestDispatcher("url").forward(request,response)”。使用请求 转发,既可以转发到 jsp,也可以转发到其他的控制器方法。
1.2.2 Redirect重定向
contrller 方法提供了一个 String 类型返回值之后,它需要在返回值里使用:redirect:
/**
* 重定向
* @return */
@RequestMapping("/testRedirect") public String testRedirect() {
System.out.println("AccountController 的 testRedirect 方法执行了。。。。");
return "redirect:testReturnModelAndView"; }
它相当于“response.sendRedirect(url)”。需要注意的是,如果是重定向到 jsp 页面,则 jsp 页面不 能写在 WEB-INF 目录中,否则无法找到。
1.3.1 使用说明
作用:
该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的 数据如:json,xml 等,通过 Response 响应给客户端
1.3.2 使用示例
需求:
使用@ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端。 前置知识点:
Springmvc 默认用 MappingJacksonHttpMessageConverter 对 json 数据进行转换,需要加入 jackson 的包。
1.3ResponseBody 响应 json 数据
注意:2.7.0 以下的版本用不了
jsp 中的代码:
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>
<script type="text/javascript"> $(function(){
$("#testJson").click(function(){ $.ajax({
type:"post",
url:"${pageContext.request.contextPath}/testResponseJson", contentType:"application/json;charset=utf-8",
data:'{"id":1,"name":"test","money":999.0}',
dataType:"json",
success:function(data){
alert(data);
} });
}); })
</script>
<!-- 测试异步请求 -->
<input type="button" value="测试ajax请求json和响应json" id="testJson"/>
控制器中的代码:
/**
* 响应 json 数据的控制器
* @author 黑马程序员
* @Company http://www.ithiema.com * @Version 1.0
*/
@Controller("jsonController") public class JsonController {
/**
* 测试响应 json 数据
*/
@RequestMapping("/testResponseJson")
public @ResponseBody Account testResponseJson(@RequestBody Account account) {
System.out.println("异步请求:"+account);
return account; }
}
运行结果:
第2章 SpringMVC 实现文件上传
2.1文件上传的回顾
2.1.1 文件上传的必要前提
A
form表单的enctype取值必须是:multipart/form-data (默认值是:application/x-www-form-urlencoded)
enctype:是表单请求正文的类型
B
method属性取值必须是Post
C
提供一个文件选择域<input type=”file” />
2.1.2 文件上传的原理分析
当 form 表单的 enctype 取值不是默认值后,request.getParameter()将失效。
enctype=”application/x-www-form-urlencoded”时,form 表单的正文内容是:
key=value&key=value&key=value
当 form 表单的 enctype 取值为 Mutilpart/form-data 时,请求正文内容就变成:
每一部分都是 MIME 类型描述的正文
-----------------------------7de1a433602ac 分界符
Content-Disposition: form-data; name="userName" 协议头
aaa
-----------------------------7de1a433602ac Content-Disposition: form-data;
filename="C:\Users\zhy\Desktop\fileupload_demofile\b.txt" Content-Type: text/plain
协议 的正文
协议的类型(MIME类型)
name="file";
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -----------------------------7de1a433602ac--
2.1.3 借助第三方组件实现文件上传
使用 Commons-fileupload 组件实现文件上传,需要导入该组件相应的支撑 jar 包:Commons-fileupload 和 commons-io。commons-io 不属于文件上传组件的开发jar文件,但Commons-fileupload 组件从1.1 版本开始,它工作时需要 commons-io 包的支持。
2.2springmvc 传统方式的文件上传
2.2.1 说明
传统方式的文件上传,指的是我们上传的文件和访问的应用存在于同一台服务器上。 并且上传完成之后,浏览器可能跳转。
2.2.2 实现步骤
2.2.2.1 第一步:拷贝文件上传的 jar 包到工程的 lib 目录
2.2.2.2 第二步:编写 jsp 页面
<form action="/fileUpload" method="post" enctype="multipart/form-data">
名称:<input type="text" name="picname"/><br/>
图片:<input type="file" name="uploadFile"/><br/>
<input type="submit" value="上传"/> </form>
2.2.2.3 第三步:编写控制器
/**
* 文件上传的的控制器
* @author 黑马程序员
* @Company http://www.ithiema.com * @Version 1.0
*/
@Controller("fileUploadController")
public class FileUploadController {
/**
* 文件上传
*/
@RequestMapping("/fileUpload")
public String testResponseJson(String picname,MultipartFile
uploadFile,HttpServletRequest request) throws Exception{
//定义文件名
String fileName = "";
//1.获取原始文件名
String uploadFileName = uploadFile.getOriginalFilename();
//2.截取文件扩展名
String extendName = uploadFileName.substring(uploadFileName.lastIndexOf(".")+1, uploadFileName.length());
//3.把文件加上随机数,防止文件重复
String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
//4.判断是否输入了文件名
if(!StringUtils.isEmpty(picname)) {
fileName = uuid+"_"+picname+"."+extendName;
}else {
fileName = uuid+"_"+uploadFileName;
} System.out.println(fileName); //2.获取文件路径
ServletContext context = request.getServletContext();
String basePath = context.getRealPath("/uploads");
//3.解决同一文件夹中文件过多问题
String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
//4.判断路径是否存在
File file = new File(basePath+"/"+datePath); if(!file.exists()) {
file.mkdirs(); }
//5.使用 MulitpartFile 接口中方法,把上传的文件写到指定位置
uploadFile.transferTo(new File(file, ));
return "success"; }
}
2.2.2.4 第四步:配置文件解析器
<!-- 配置文件上传解析器 -->
<bean id="multipartResolver" <!-- id 的值是固定的--> class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为 5MB -->
<property name="maxUploadSize">
<value>5242880</value>
</property>
</bean>
注意:
文件上传的解析器 id 是固定的,不能起别的名称,否则无法实现请求参数的绑定。(不光是文件,其他字段也将无法绑定)
2.3 springmvc 跨服务器方式的文件上传
2.3.1 分服务器的目的
在实际开发中,我们会有很多处理不同功能的服务器。例如: 应用服务器:负责部署我们的应用 数据库服务器:运行我们的数据库
缓存和消息服务器:负责处理大并发访问的缓存和消息
文件服务器:负责存储用户上传文件的服务器。
(注意:此处说的不是 服务器集群)
2.3.2 准备两个 tomcat 服务器,并创建一个用于存放图片的 web 工程
在文件服务器的 tomcat 配置中加入,允许读写操作。文件位置:
加入内容:
加入此行的含义是:接收文件的目标服务器可以支持写入操作。
2.3.3 拷贝jar包
在我们负责处理文件上传的项目中拷贝文件上传的必备 jar 包
2.3.4 编写控制器实现上传图片
/**
* 响应 json 数据的控制器
* @author 黑马程序员
* @Company http://www.ithiema.com
* @Version 1.0
*/ @Controller("fileUploadController2") public class FileUploadController2 {
public static final String FILESERVERURL =
"http://localhost:9090/day06_spring_image/uploads/";
/**
* 文件上传,保存文件到不同服务器
*/
@RequestMapping("/fileUpload2")
public String testResponseJson(String picname,MultipartFile uploadFile) throwsException{
//定义文件名
String fileName = "";
//1.获取原始文件名
String uploadFileName = uploadFile.getOriginalFilename();
//2.截取文件扩展名
String extendName = uploadFileName.substring(uploadFileName.lastIndexOf(".")+1, uploadFileName.length());
//3.把文件加上随机数,防止文件重复
String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
//4.判断是否输入了文件名
if(!StringUtils.isEmpty(picname)) {
fileName = uuid+"_"+picname+"."+extendName;
}else {
fileName = uuid+"_"+uploadFileName;
}
System.out.println(fileName);
//5.创建 sun 公司提供的 jersey 包中的 Client 对象
Client client = Client.create();
//6.指定上传文件的地址,该地址是 web 路径
WebResource resource = client.resource(FILESERVERURL+fileName);
//7.实现上传
} }
String result = resource.put(String.class,uploadFile.getBytes()); System.out.println(result);
return "success";
2.3.5 编写 jsp 页面
<form action="fileUpload2" method="post" enctype="multipart/form-data">
名称:<input type="text" name="picname"/><br/>
图片:<input type="file" name="uploadFile"/><br/>
<input type="submit" value="上传"/> </form>
2.3.6 配置解析器
<!-- 配置文件上传解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 设置上传文件的最大尺寸为 5MB -->
<property name="maxUploadSize"> <value>5242880</value>
</property>
</bean>
第3章 SpringMVC 中的异常处理
3.1异常处理的思路
系统中异常包括两类:预期异常和运行时异常 RuntimeException,前者通过捕获异常从而获取异常信息, 后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由 springmvc 前端
控制器交由异常处理器进行异常处理,如下图:
3.2实现步骤
3.2.1 编写异常类和错误页面
/**
* 自定义异常
* @author 黑马程序员
* @Company http://www.ithiema.com * @Version 1.0
*/
public class CustomException extends Exception {
private String message;
public CustomException(String message) { this.message = message;
}
public String getMessage() { return message;
} }
jsp 页面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>执行失败</title>
</head>
<body>
执行失败!
${message }
</body>
</html>
3.2.2 自定义异常处理器
/**
* 自定义异常处理器
* @author 黑马程序员
* @Company http://www.ithiema.com * @Version 1.0
*/
public class CustomExceptionResolver implements HandlerExceptionResolver { @Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ex.printStackTrace();
CustomException customException = null;
//如果抛出的是系统自定义异常则直接转换 if(ex instanceof CustomException){
customException = (CustomException)ex; }else{
//如果抛出的不是系统自定义异常则重新构造一个系统错误异常。
customException = new CustomException("系统错误,请与系统管理 员联系!"); }
ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("message", customException. ());
getMessage
modelAndView.setViewName("error");
return modelAndView; }
}
3.2.3 配置异常处理器
<!-- 配置自定义异常处理器 -->
<bean id="handlerExceptionResolver"
class="com.itheima.exception.CustomExceptionResolver"/>
3.2.4 运行结果:
第4章 SpringMVC 中的拦截器
4.1拦截器的作用
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。 用户可以自己定义一些拦截器来实现特定的功能。 谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺 序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。 说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但 是也有区别,接下来我们就来说说他们的区别: 我们要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。 |
4.2 自定义拦截器的步骤
4.2.1 第一步:编写一个普通类实现 HandlerInterceptor 接口
/**
* 自定义拦截器
* @author 黑马程序员
* @Company http://www.ithiema.com * @Version 1.0
*/
public class HandlerInterceptorDemo1 implements HandlerInterceptor { @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { System.out.println("preHandle 拦截器拦截了"); return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception { System.out.println("postHandle 方法执行了");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception { System.out.println("afterCompletion 方法执行了");
}
}
4.2.2 第二步:配置拦截器
<!-- 配置拦截器 --> <mvc:interceptors>
<mvc:interceptor> <mvc:mapping path="/**"/>
<bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1"></bean>
</mvc:interceptor>
</mvc:interceptors>
4.2.3 测试运行结果:
4.3拦截器的细节
4.3.1 拦截器的放行
放行的含义是指,如果有下一个拦截器就执行下一个,如果该拦截器处于拦截器链的最后一个,则执行控制器 中的方法。
4.3.2 拦截器中方法的说明
public interface HandlerInterceptor {
/**
* 如何调用:
* 按拦截器定义顺序调用
* 何时调用:
* 只要配置了都会调用
* 有什么用:
* 如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去
* 进行处理,则返回 true。
* 如果程序员决定不需要再调用其他的组件去处理请求,则返回 false。
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler)
throws Exception {
return true;
}
/**
* 如何调用:
* 按拦截器定义逆序调用
* 何时调用:
* 在拦截器链内所有拦截器返成功调用
* 有什么用:
* * */
在业务处理器处理完请求后,但是 DispatcherServlet 向客户端返回响应前被调用, 在该方法中对用户请求 request 进行处理。
default void postHandle(HttpServletRequest request, HttpServletResponse
response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/**
* 如何调用:
* 按拦截器定义逆序调用
* 何时调用:
* 只有 preHandle 返回 true 才调用
* 有什么用:
* 在 DispatcherServlet 完全处理完请求后被调用,
* 可以在该方法中进行一些资源清理的操作。 */
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
} }
思考:
如果有多个拦截器,这时拦截器 1 的 preHandle 方法返回 true,但是拦截器 2 的 preHandle 方法返 回 false,而此时拦截器 1 的 afterCompletion 方法是否执行?
4.3.3 拦截器的作用路径
作用路径可以通过在配置文件中配置。
<!-- 配置拦截器的作用范围 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /><!-- 用于指定对拦截的 url -->
<mvc:exclude-mapping path=""/><!-- 用于指定排除的 url--> <bean id="handlerInterceptorDemo1"
class="com.itheima.web.interceptor.HandlerInterceptorDemo1">
</bean> </mvc:interceptor>
</mvc:interceptors>
4.3.4 多个拦截器的执行顺序
多个拦截器是按照配置的顺序决定的。
4.4正常流程测试
4.4.1 配置文件:
<!-- 配置拦截器的作用范围 --> <mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /><!-- 用于指定对拦截的 url -->
<bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean id="handlerInterceptorDemo2" class="com.itheima.web.interceptor.HandlerInterceptorDemo2"></bean>
</mvc:interceptor>
</mvc:interceptors>
4.4.2 拦截器 1 的代码:
/**
* 自定义拦截器
* @author 黑马程序员
* @Company http://www.ithiema.com * @Version 1.0
*/
public class HandlerInterceptorDemo1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler)
throws Exception {
System.out.println("拦截器 1:preHandle 拦截器拦截了"); return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("拦截器 1:postHandle 方法执行了"); }
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("拦截器 1:afterCompletion 方法执行了");
} }
/**
* 自定义拦截器
* @author 黑马程序员
* @Company http://www.ithiema.com
* @Version 1.0
*/
public class HandlerInterceptorDemo2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("拦截器 2:preHandle 拦截器拦截了"); return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("拦截器 2:postHandle 方法执行了"); }
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse
response, Object handler, Exception ex) throws Exception {
System.out.println("拦截器 2:afterCompletion 方法执行了"); }
}
4.4.4 运行结果:
4.5中断流程测试
4.5.1 配置文件:
<!-- 配置拦截器的作用范围 --> <mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /><!-- 用于指定对拦截的 url -->
<bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean id="handlerInterceptorDemo2"
class="com.itheima.web.interceptor.HandlerInterceptorDemo2"></bean>
</mvc:interceptor>
</mvc:interceptors>
4.5.2 拦截器 1 的代码:
/**
* 自定义拦截器
* @author 黑马程序员
* @Company http://www.ithiema.com * @Version 1.0
*/
public class HandlerInterceptorDemo1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler)
throws Exception {
System.out.println("拦截器 1:preHandle 拦截器拦截了"); return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception { System.out.println("拦截器 1:postHandle 方法执行了");
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("拦截器 1:afterCompletion 方法执行了"); }
}
4.5.3 拦截器 2 的代码:
/**
* 自定义拦截器
* @author 黑马程序员
* @Company http://www.ithiema.com
* @Version 1.0 */
public class HandlerInterceptorDemo2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("拦截器 2:preHandle 拦截器拦截了"); return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception { System.out.println("拦截器 2:postHandle 方法执行了");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("拦截器 2:afterCompletion 方法执行了");
}
}
4.5.4 运行结果:
4.6 拦截器的简单案例(验证用户是否登录)
4.6.1 实现思路
1、有一个登录页面,需要写一个 controller 访问页面
2、登录页面有一提交表单的动作。需要在 controller 中处理。
2.1、判断用户名密码是否正确
2.2、如果正确 向 session 中写入用户信息
2.3、返回登录成功。
3、拦截用户请求,判断用户是否登录
3.1、如果用户已经登录。放行
3.2、如果用户未登录,跳转到登录页面
4.6.2 控制器代码
//登陆页面
@RequestMapping("/login")
public String login(Model model)throws Exception{
return "login";
} //登陆提交
//userid:用户账号,pwd:密码 @RequestMapping("/loginsubmit")
public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{
//向 session 记录用户身份信息
session.setAttribute("activeUser", userid);
return "redirect:/main.jsp"; }
//退出
@RequestMapping("/logout")
public String logout(HttpSession session)throws Exception{
//session 过期 session.invalidate();
return "redirect:index.jsp"; }
4.6.3 拦截器代码
public class LoginInterceptor implements HandlerInterceptor{ @Override
Public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//如果是登录页面则放行 if(request.getRequestURI().indexOf("login.action")>=0){
return true; }
HttpSession session = request.getSession();
//如果用户已登录也放行
if(session.getAttribute("user")!=null){
return true;
}
//用户没有登录跳转到登录页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
}
return false;
}