DAY2
1 响应数据和结果视图
1.1 返回值分类
01 字符串
//从超链接跳转
<a href="user/testString">testString</a>
//对应的控制器处理
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testString")
public String testString(Model model){
System.out.println("执行testString...");
//模拟从数据库中查询user对象
User user = new User();
user.setUsername("美美");
user.setPassword("123");
user.setAge(20);
//model存储数据
model.addAttribute("user",user);
return "success";
/**
* controller 方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
* 指定逻辑视图名,经过视图解析器解析为 jsp 物理路径:/WEB-INF/pages/success.jsp
*/
}
}
//成功跳转页面 success.jsp
<h3>执行成功</h3>
${user.username} //美美
${user.password} //123
${user.age} //20
02 void
/*
返回类型是void,默认会去请求user/testVoid.jsp
请求转发:一次请求,不用编写项目的名称
重定向:两次请求,地址栏会发生变化
直接响应
*/
@RequestMapping("/testVoid")
public void testVoid(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("执行testVoid...");
//1 编写请求转发的程序
//手动调用不会使用视图解析器
// request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
//2 重定向相当于重新请求,不能直接访问WEB-INF下的文件
// response.sendRedirect(request.getContextPath()+"/index.jsp");
// response.sendRedirect("testString");
//解决中文乱码
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
//直接对浏览器作出进行响应
response.getWriter().print("你好"); //使用输出流
return; //后面没有代码
}
03 ModelAndView
ModelAndView 是 SpringMVC 为我们提供的一个对象,可以在方法返回值返回这个对象,也可以通过视图解析器跳转到某个页面。
//返回ModeAndView对象
@RequestMapping("/testModeAndView")
public ModelAndView testModeAndView(){
//1 创建ModeAndView对象
ModelAndView mv = new ModelAndView();
System.out.println("执行testModeAndView...");
//模拟从数据库中查询user对象
User user = new User();
user.setUsername("泡泡");
user.setPassword("456");
user.setAge(20);
//把user对象存储到mv对象中,底层也会把user对象存储到request域中
mv.addObject("user",user);
//跳转到哪个页面 --- 用视图解析器
mv.setViewName("success"); //设置一个视图success.jsp
return mv; //返回字符串的底层源码也是使用ModeAndView
}
1.2 转发和重定向
用的比较少,用不了视图解析器
//使用关键字的方式进行转发或重定向
@RequestMapping("/testForwardOrRedirect")
public String testForwardOrRedirect(){
System.out.println("testForwardOrRedirect执行...");
/*
请求的转发 --- 不能再使用视图解析器,需要自己写路径
关键字:forward
*/
// return "forward:/WEB-INF/pages/success.jsp";
/*
重定向:(无法访问WEB-INF目录下文件)
关键字:redirect
*/
return "redirect:/index.jsp"; //没有加项目的名称,底层有实现
}
1.3 ResponseBody 响应 json 数据
注意:有些文件不应该被前端控制器拦截
springmvc.xml
<!--前端控制器,哪些静态资源不拦截-->
<!-- 设置静态资源不过滤 -->
<mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 -->
<mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 -->
<mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
设置完毕,再引入js文件就不会有问题了。
- 发送ajax请求
需要引入jquery文件
<script>
//页面加载,绑定点击事件
$(function () {
$("#btn").click(function () {
//发生ajax请求
$.ajax({
//编写json格式,设置属性和值
url:"user/testAjax",
contentType:"application/json;charset=utf-8",
data:'{"username":"张三","password":"123","age":30}',
dataType:"json", //服务器返回的数据类型
type:"post", //请求方式
success:function (data) { //成功的回调函数
//data:服务器端响应的json数据,进行解析
}
});
});
});
</script>
- 响应json格式数据
- 发送过来的json数据,如果key值和javaBean里的属性值相同,则spring会帮你把json数据封装到对象中,需要引入一些jar包。
pom.xml
<!-- json字符串和JavaBean对象互相转换的过程中,需要使用jackson的jar包-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
//模拟异步请求和响应
//使用@ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端。
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user){ //应该接受json封装好的user
System.out.println("testAjax执行...");
//客户端发生ajax请求,传的是json字符串,后台把json字符串封装到user对象中
System.out.println(user);
//响应:模拟查询数据库
user.setUsername("哈哈");
user.setAge(40);
//响应数据---前端dataType:json --- > 还是要接受json数据dataType
return user; //会转成json串返回
}
2 SpringMVC实现文件上传
- 文件上传的必要前提:
* A: form 表单的 enctype 取值必须是:multipart/form-data
(默认值是:application/x-www-form-urlencoded)
enctype:是表单请求正文的类型
* B: method 属性取值必须是 Post
* C: 提供一个文件选择域 - 依赖jar包
使用 Commons-fileupload 组件实现文件上传,需要导入该组件相应的支撑 jar 包:Commons-fileupload 和commons-io。
commons-io 不属于文件上传组件的开发 jar 文件,但Commons-fileupload 组件从 1.1 版本开始,它工作时需要 commons-io 包的支持。
<!--文件上传-->
<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>
2.1 传统方式的文件上传
index.jsp
<form action="user/fileUpload1" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/> <br>
<input type="submit" value="上传">
</form>
//文件上传
@RequestMapping("/fileUpload1")
public String fileUpload1(HttpServletRequest request) throws Exception { //解析request,从而拿到文件上传的选项
System.out.println("文件上传...");
//使用fileupload组件完成文件上传
//1 上传的位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");//拿绝对路径
//1.1判断路径是否存在
File file = new File(path);
if(!file.exists()){ //不存在则创建
file.mkdirs();
}
//2 解析request对象,获取上传文件项
DiskFileItemFactory factory = new DiskFileItemFactory(); //磁盘文件项工厂
ServletFileUpload upload = new ServletFileUpload(factory);
//2.1 解析request
List<FileItem> items = upload.parseRequest(request);//文件项的集合
//2.2 遍历
for (FileItem item : items) {
//进行判断,判断当前item对象是否是上传文件项
if(item.isFormField()){
//普通表单项
}else {
//3 上传文件项
//3.1 获取上传文件的名称
String filename = item.getName();
//把文件名称设置为唯一值uuid
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid+"_"+filename;
//3.2 完成文件上传
item.write(new File(path,filename));
//3.3 删除临时文件
item.delete();
}
}
return "success";
}
2.2 springMVC的文件上传
- 原理
request经过前端控制器,调用文件解析器,解析后拿到上传文件的选项,传递给Controller的方法。 - 配置文件解析器
springmvc.xml
<!-- 配置文件解析器对象,要求id名称必须是multipartResolver -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/><!--最大文件大小 10M-->
</bean>
<form action="user/fileUpload2" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/> <br>
<input type="submit" value="上传">
</form>
//springMvc文件上传
@RequestMapping("/fileUpload2")
public String fileUpload2(HttpServletRequest request, MultipartFile upload) throws Exception { //MultipartFile upload 名字与表单name一致
System.out.println("springMvc文件上传...");
//使用fileupload组件完成文件上传
//1 上传的位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");//拿绝对路径
//1.1判断路径是否存在
File file = new File(path);
if(!file.exists()){ //不存在则创建
file.mkdirs();
}
//不需要自己解析了
String filename = upload.getOriginalFilename(); //拿到文件名称
//把文件名称设置为唯一值uuid
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid+"_"+filename;
//完成文件上传
upload.transferTo(new File(path,filename));
return "success";
}
2.3 springmvc 跨服务器方式的文件上传
- 在实际开发中,我们会有很多处理不同功能的服务器,分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。
例如:
应用服务器:负责部署我们的应用
数据库服务器:运行我们的数据库
缓存和消息服务器:负责处理大并发访问的缓存和消息
文件服务器:负责存储用户上传文件的服务器 - 搭建环境(上个项目的基础上)
1. 配置两个tomcat服务器,一个是用于文件上传,一个用来存放上传的文件。
2. 导入开发需要的jar包
pom.xml
<!--跨服务器文件上传-->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.18.1</version>
</dependency>
- 代码
//springMvc跨服务器文件上传
@RequestMapping("/fileupload3")
public String fileupload3(MultipartFile upload) throws Exception { //MultipartFile upload 名字与表单name一致
System.out.println("springMvc跨服务器文件上传...");
//1 定义上传服务器路径
String path = "http://localhost:9090/uploads/";
//2 说明上传文件项
String filename = upload.getOriginalFilename(); //拿到文件名称
//把文件名称设置为唯一值uuid
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid+"_"+filename;
//3 完成文件跨服务器上传
// 创建客户端对象
Client client = Client.create();
// 和图片服务器进行连接
WebResource webResource = client.resource(path + filename);//如果path的最后没有/,那么在path和filename之间需要拼接+"/"+
// 上传文件
webResource.put(upload.getBytes());//通过字节方式上传
return "success";
}
- bug
-
报405 Method Not Allowed错误
部署存储文件的服务器tomcat时,需要修改端口号,和上传文件的tomcat区分开来,并且tomcat服务器默认是不可写操作,只允许读,所以在Tomcat web.xml文件中的servlet标签内加入:
-
returned a response status of 409 Conflict 错误
原因是存放文件的服务器没有指定的上传文件存放目录,我们可以手动创建一个,记得是在:项目 — target — 项目名称 — uploads
要进入target里面的文件夹,不能直接在target中创建,因为粗心导致创建文件夹路径不对,卡了很久。
3 SpringMVC 中的异常处理
-
系统中异常包括两类:预期异常和运行时异常 RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由 springmvc 前端控制器交由异常处理器进行异常处理。
-
配置异常处理器
- 自定义异常类
//自定义异常类
public class SysException extends Exception {
private String message;
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public SysException(String message) {
this.message = message;
}
}
- 编写异常处理器
//异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver {
/**
* 程序出现异常会调用一次处理器,执行这个方法
* @param ex Controller把异常抛出,ex就是当前抛出的异常对象SysException,我们可以使用ex获取提示信息
* @return
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//1 获取到异常对象
SysException e = null;
if(ex instanceof SysException){
e = (SysException)ex; //强转成具体类型
}else { //抛出的不是自定义的异常(其他异常)
e = new SysException("系统正在维护...");
}
//方法的返回值是ModelAndView,可以跳转到页面
//创建ModelAndView对象
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg",e.getMessage()); //在跳转的错误页面可获取errorMsg
mv.setViewName("error"); //找视图解析器,跳转到对应页面
return mv;
}
}
在springmvc.xml中配置,就像普通的bean
<!--配置异常处理器-->
<bean id="sysExceptionResolver" class="cn.itcast.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";
}
}
- 出现异常跳转的error.jsp
使用el表达式获取异常信息显示,这样在前端就不会直接打印异常信息,而是给出提示,我们可以在后端看到具体的异常信息。
${errorMsg}
4 SpringMVC 中的拦截器
- Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
- 区别:
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的。
4.1 自定义拦截器
01 入门代码
先执行拦截器,放行后执行controller中方法,controller再跳转到success.jsp
- 编写拦截器类
//自定义拦截器
public class MyInterceptor implements HandlerInterceptor {
//接口中的方法都实现了,但是也可以重写
/**
* 预处理:controller方法执行前
* @param request
* @param response
* @param handler
* @return true:放行,执行下一个拦截器,如果没有则执行controller中的方法
* false:不放行,可以跳转到其他页面(提示...)
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor拦截器执行...");
return true;
}
}
- 配置拦截器
springmvc.xml
<!--配置拦截器-->
<mvc:interceptors>
<!--配置拦截器-->
<mvc:interceptor>
<!--要拦截的具体方法-->
<mvc:mapping path="/user/*"/>
<!--不拦截的方法,二者选其一
<mvc:exclude-mapping path=""></mvc:exclude-mapping>-->
<!--配置拦截器对象-->
<bean class="cn.itcast.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
- controller
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testInterceptor")
public String testInterceptor() {
System.out.println("testInterceptor执行...");
return "success";
}
}
02 拦截器HandlerInterceptor接口中的方法
- 三个方法的执行时机不同,可以用来实现不同的处理。
//自定义拦截器
//放行的含义是指,如果有下一个拦截器就执行下一个,如果该拦截器处于拦截器链的最后一个,则执行控制器中的方法
public class MyInterceptor implements HandlerInterceptor {
//接口中的方法都实现了,但是也可以重写
/**
* 预处理:controller方法执行前
* @return true:放行,执行下一个拦截器,如果没有则执行controller中的方法
* false:不放行,可以跳转到其他页面(提示...)
*
* 可以做一些逻辑的判断,例如用户登录,如果没登录,直接跳登录页面
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor拦截器执行...前");
//return false时:不放行,可以设置拦截器直接跳到其他页面做出响应
// request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}
/**
* 后处理的方法:controller执行后,success.jsp执行前
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor拦截器执行...后");
//拦截器指定跳转页面,则不会再跳success.jsp,但是success中的代码会执行
// request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
}
/** 全部执行完
* success.jsp执行后,该方法会执行
*
* 可以用于释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor拦截器执行...最后");
//success.jsp响应执行完了,不能再跳其他页面
}
}
- 可以配置多个拦截器,会按照拦截器链来执行
<!--配置第二个拦截器-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="cn.itcast.interceptor.MyInterceptor2"></bean>
</mvc:interceptor>