一、回顾
在上一篇 Spring MVC (二) —— 请求参数绑定 中,我了解了
【1】SpringMVC 的参数绑定机制、使用要求
- POJO 类中包含集合类型参数
- POJO 类型作为参数示例
【2】请求参数乱码问题的解决!
- ① POST 请求方式乱码
- ② 静态资源可以不过滤
- ③ GET 请求方式乱码
【3】自定义类型转换器 Converter 的配置步骤
接下来,我继续学习 SpringMVC 文件上传、SpringMVC 中的拦截器
二、响应数据和结果视图
【1】返回 void
之前了解到:用 @RequestMapping 注解的方法的返回值是一个 String,表示指定逻辑视图名,通过视图解析器解析为物理视图地址(xxx/xxx–>路径)。 其实返回值也可以为 void。
@RequestMapping("/testReturnVoid")
public void testReturnVoid1(HttpServletRequest request,
HttpServletResponse response) throws Exception { }
在 controller 方法形参上可以定义 request 和 response,使用 request 或 response 指定响应结果:
一、使用 request 转发页面:
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
二、通过 response 页面重定向:
response.sendRedirect("testRetrunVoid2")
三、也可通过 response 指定响应结果,例如响应 json 数据
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");//设置浏览器打开页面的编码
response.getWriter().write("json 串");
【2】ModelAndView
ModelAndView 是 SpringMVC 提供的一个用作控制器方法的返回值对象。
方法一:
public ModelAndView addObject(String attributeName, Object attrValue){
this.getModelView.addAttribute(attributeName, attrValue);
return this;
}
可以在页面中,通过 ${requestScope.attributeName}
方式获取到值。
方法二:
用于设置逻辑视图名,视图解析器会根据 viewName 再结合配置文件找指定视图,上面的例子,会找到 success 页面
public void setViewName(@Nullble String viewName){
this.view = viewName;
}
@RequestMapping("/testReturnModelAndView")
public ModelAndView testReturnModelAndView() {
ModelAndView mv = new ModelAndView();
mv.addObject("username", "张三");
mv.setViewName("success");
}
<body> ${requestScope.username} </body>
【3】转发和重定向的不同写法
① Forward
@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,也可以转发到其他的控制器方法。
① Redirect
@RequestMapping("/testRedirect")
public String testRedirect() {
System.out.println("AccountController 的 testRedirect 方法执行了。。。。");
return "redirect:testReturnModelAndView";
}
注: 相当于 response.sendRedirect(url)
。需要注意的是,重定向是两次请求,如果是重定向到 Jsp 页面,则 Jsp 页面不能写在 WEB-INF 目录中,否则无法找到。
三、@ResponseBody 响应 JSON 数据
作用: 该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的数据如:json,xml 等,通过 Response 响应给客户端
需求: 使用 @ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端。
前置知识点: SpringMVC 默认用 MappingJacksonHttpMessageConverter 对 json 数据进行转换,需要加入 jackson 的 jar 包。
jackson-annotation-2.9.0.jar
databind-2.9.0.jar
core--2.9.0.jar
原始 ajax 请求代码
<script>
type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js">
</script>
<script>
$(function(){ // 页面加载,绑定单击事件
$("#btn").click(function(){
// alert("hello btn");
// 发送ajax请求
$.ajax({
// 编写json格式,设置属性和值
url:"user/testAjax",
contentType:"application/json;charset=UTF-8",
data:'{"username":"hehe","age":30}',
dataType:"json",
type:"post",
success:function(data){
// data服务器端响应的json的数据,进行解析
alert(data);
alert(data.username);
alert(data.age);
}
});
});
});
</script>
<input type="button" value=" 测试ajax请求json和响应json" id="testJson"/>
@RequestMapping("/testAjax")//注:此处应与 url:"user/testAjax" 相同
public @ResponseBody User testAjax(@RequestBody User user){
// 客户端发送ajax的请求,传的是json字符串,后端把json字符串封装到user对象中
System.out.println(user);
// 做响应,模拟查询数据库
user.setUsername("李四");
user.setAge(40);
// 做响应
return user;
}
四、SpringMVC 实现文件上传
【1】单服务器实现
① 必要前提
A form 表单的 enctype 取值必须是:multipart/form-data
(默认值是:application/x-www-form-urlencoded)
enctype:是表单请求正文的类型
B method 属性取值必须是 Post
C 提供一个文件选择域<input type=”file” />
② 借助第三方组件实现文件上传
Commons-fileupload 组件(jar)从 1.1 版本开始,工作时需 commons-io 包的支持。 导入jar
③ 编写 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>
注: 图片标签的 name 属性 (uploadFile),必须和 请求参数绑定的 MultipartFile 对象的名字一样
④ 编写控制器
@RequestMapping("/fileupload3")
public String testResponseJson (String imgName, 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);
ServletContext context = request.getServletContext();
String basePath = context.getRealPath("/uploads");
//5. 解决同一文件夹中文件过多问题
String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
//6 .判断路径是否存在
File file = new File(basePath+"/"+datePath);
if (!file.exists()) { file.mkdirs(); }
//7 .使用 MulitpartFile 接口中方法,把上传的文件写到指定位置 ---- 关键
uploadFile.transferTo(new File(file, fileName));
return "success";
}
⑥ 配置文件解析器
<!-- 配置文件上传解析器 --> <!-- id 的值是固定的-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为 5MB -->
<property name="maxUploadSize">
<value>5242880</value>
</property>
</bean>
注: 文件上传的解析器 id 是固定的,不能起别的名称,否则无法实现请求参数的绑定。(不光是文件,其他字段也将无法绑定)
附:原理
【1】当用户请求上传文件时,通过前端控制器,前端控制器发现这是文件上传请求,就把请求分发到我们配置的文件解析器。
【2】文件解析器解析完毕,返回一个 upload 对象给前端控制器。
【3】通过 参数绑定机制 就可以获取该 upload 文件上传项对象。
【2】跨服务器方式
在实际开发中,我们会有很多处理不同功能的服务器。例如:
- 应用服务器:负责部署我们的应用
- 数据库服务器:运行我们的数据库
- 缓存和消息服务器:负责处理大并发访问的缓存和消息
- 文件服务器:负责存储用户上传文件的服务器。
应用服务器作为一个中转站,分发不同的请求
(注意:此处说的不是服务器集群)
① 配置文件服务器
不之所云:在文件服务器的 tomcat 配置中加入,允许读写操作。
-
该服务器的端口应该与应用服务器相异
-
创建专门放置图片的工程,并把工程部署到文件服务器上!
② 拷贝 jar包
在负责处理文件上传的项目中导入额外 jar 包 + 单服务器需要的!
jersey-client-1.18.jar
jersey-core-1.18.jar
③ 编写 jsp 页面 + 文件上传解析器
④ 编写控制器
@RequestMapping("/fileupload3")
public String fileuoload3(MultipartFile upload) throws Exception {
System.out.println("跨服务器文件上传...");
// 定义上传文件服务器路径
String path = "http://localhost:9090/uploads/";
// 说明上传文件项
// 获取上传文件的名称
String filename = upload.getOriginalFilename();
// 把文件的名称设置唯一值,uuid
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid+"_"+filename;
// .创建 sun 公司提供的 jersey 包中的 Client 对象
Client client = Client.create();
// 指定上传文件的地址,该地址是 web 路径
WebResource webResource = client.resource(path + filename);
// 实现上传
webResource.put(upload.getBytes());
return "success";
}
五、SpringMVC 中的异常处理
【1】异常处理的思路
系统中异常包括两类:
异常种类 | 处理逻辑 |
---|---|
预期异常 | 通过捕获异常从而获取异常信息 |
运行时异常 RuntimeException | 主要通过规范代码开发、测试通过手段减少运行时异常的发生。 |
系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由前端控制器交由异常处理器进行异常处理。
【2】实现步骤
① 编写异常类和错误页面
自定义异常
public class CustomException extends Exception {
private String message;
public CustomException(String message) { this.message = message; }
public String getMessage() { return message; }
}
error.jsp 错误提示页面
<%@ page contentType="text/html;charset=UTF-8" language="java"
isELIgnored="false" %>
<body> ${errorMsg} </body>
② 自定义异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver{
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
SysException e = null;
//如果抛出的是系统自定义异常则直接转换
if(ex instanceof SysException){
e = (SysException)ex;
}else{//如果抛出的不是系统自定义异常则重新构造一个系统错误异常。
e = new SysException("系统正在维护....");
}
// 创建ModelAndView对象
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg",e.getMessage());
mv.setViewName("error");
return mv;
}
}
③ 配置异常处理器
在主配置文件中:
<!-- 配置自定义异常处理器 -->
<bean id="handlerExceptionResolver"
class="com.shonan.exception.CustomExceptionResolver"/>
六、SpringMVC中的拦截器
【1】 Interceptor 的作用
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,也同样有拦截器链。用于对处理器进行预处理和后处理,是 AOP 思想的具体应用。
【2】 Interceptor 与 Filter 的区别
Interceptor | Filter |
---|---|
由SpringMVC 提供,只有使用 SpringMVC 框架的工程才能用 | servlet 规范中的一部分,任何 java web 工程都可 |
只拦截访问的控制器方法,如果访问的是 jsp, html, css, image 或者 js 等静态资源不会进行拦截 | 如果在 url-pattern 中配置了 /* 之后,可以对所有要访问的资源拦截。 |
【3】自定义拦截器的步骤
① 实现 HandlerInterceptor 接口
public class MyInterceptor1 implements HandlerInterceptor{
/*预处理,controller方法执行前
* return true: 放行,执行下一个拦截器,
如果没有,执行controller中的方法
* return false:不放行
* @param request
* @param response
* @param handler */
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器预处理");
//在放行之前转发到 某个页面,后序的控制器方法不会执行
request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截器后序处理");
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor1执行了...最后1111");
}
}
② 配置拦截器
<!--通知前端控制器以下静态资源不拦截-->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>
<mvc:interceptors> <!--配置拦截器们-->
<mvc:interceptor> <!--配置单个拦截器-->
<mvc:mapping path="/user/*"/> <!--拦截的具体的方法-->
<!--配置拦截器不拦截的方法:<mvc:exclude-mapping path=""/>-->
<!--配置拦截器对象-->
<bean class="com.shonan.interceptor.MyInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
③ 拦截器3个方法执行的流程说明
情况一:
请求 —> 拦截器1【preH(true)】 —> 拦截器2【preH(true)】 —>
控制器业务方法() —> 拦截器2【postH()】 —> 拦截器1【postH()】 —>
拦截器2【aftercoml()】 —> 拦截器1【aftercoml()】—> 响应浏览器
情况二:
请求 —> 拦截器1【preH(true)】 —> 拦截器2【preH(false)】—>
拦截器1【aftercoml()】
方法 | 执行时间 |
---|---|
preHandle() | 在控制器方法执行前 |
postHandle() | 在拦截器链内所有拦截器返成功调用 |
afterCompletion() | 只有拦截器的 preHandle() 返回 true 才调用 |