一、处理 JSON
JSON 知识:
- JSON 有两种格式:① Json 对象:{key:value,key:value….},②Json 数组:[value1, value2…]
- Json 对象解析方式: 对象.key; Json 数组的解析方式:for 循环遍历
- java 对象转 Json: ① Bean 和 map —》Json 对象;② List —》 json 数组
1、 返回 JSON
- 加入 jar 包: 下载地址:https://repo1.maven.org/maven2/com/fasterxml/jackson/core/
注意: 请根据自己的 Spring 版本下载最新的 jar,否则有肯能会报错。 报错异常可以参考我这篇博客:解决方案
- SpringMVC 处理 json 四个条件
① 导入 jackson 的 jar
② 在 springMVC 的配置文件中开启 MVC 驱动,<mvc:annotation-driven />
③ 在处理 ajax 请求的方法上加上注解**@ResponseBody**
④ 将要转换为 json 且响应到客户端的数据,直接作为该方法的返回值返回
- 代码示例
【index.jsp】
代码语言:javascript复制<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
<script type="text/javascript" src="${pageContext.servletContext.contextPath}/js/jquery-1.8.2.min.js"></script>
<script type="text/javascript" >
$(function () {
$('#btn').click(function () {
$.ajax({
url:"testJson",
type:"POST",
dataType:"json",
success:function (msg) {
for(var i in msg){
var emp = msg[i];
alert("id=" emp.id ",lastName=" emp.lastName ",departmentName=" emp.department.departmentName);
}
}
});
});
});
</script>
</head>
<body>
<a href="testJson">测试JSON</a>
</body>
</html>
【JsonControllerTest.json】**@ResponseBody : ** SpringMVC 对 JSON 的支持
代码语言:javascript复制@Controller
public class JsonControllerTest {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping(value = "/testJson")
@ResponseBody
public Collection<Employee> testJson(){
Collection<Employee> emps = employeeDao.getAll();
return emps;
}
}
- 测试
2、 使用 AJAX
【index.jsp】
代码语言:javascript复制<html>
<head>
<title>$Title$</title>
<link
rel="stylesheet"
href="${pageContext.servletContext.contextPath}/css/index_like.css"
/>
<script
type="text/javascript"
src="${pageContext.servletContext.contextPath}/js/jquery-1.8.2.min.js"
></script>
<script type="text/javascript">
$(function () {
$("#btn").click(function () {
$.ajax({
url: "testJson",
type: "POST",
dataType: "json",
success: function (msg) {
/*
[
{"id":1001,"lastName":"E-AA","email":"aa@163.com","gender":1,"department":{"id":101,"departmentName":"D-AA"}},
{"id":1002,"lastName":"E-BB","email":"bb@163.com","gender":1,"department":{"id":102,"departmentName":"D-BB"}},
{"id":1003,"lastName":"E-CC","email":"cc@163.com","gender":0,"department":{"id":103,"departmentName":"D-CC"}},
{"id":1004,"lastName":"E-DD","email":"dd@163.com","gender":0,"department":{"id":104,"departmentName":"D-DD"}},
{"id":1005,"lastName":"E-EE","email":"ee@163.com","gender":1,"department":{"id":105,"departmentName":"D-EE"}}
]
*/
// 第一种实现方式
/*var tb = "<table>";
tb = "<tr><th>id</th><th>lastName</th><th>email</th><th>gender</th><th>departmentName</th></tr>"
for(var i in msg){
var emp = msg[i];
tb = "<tr><td>" emp.id "</td><td>" emp.lastName "</td><td>" emp.email "</td><td>" emp.gender "</td><td>" emp.department.departmentName "</td></tr>"
}
tb = "</table>"
$('body').append(tb);*/
//第二种实现方式
$("body").append("<table></table>");
$("table").append(
"<tr><th>id</th><th>lastName</th><th>email</th><th>gender</th><th>departmentName</th></tr>"
);
for (var i in msg) {
var emp = msg[i];
$("table").append(
"<tr><td>"
emp.id
"</td><td>"
emp.lastName
"</td><td>"
emp.email
"</td><td>"
emp.gender
"</td><td>"
emp.department.departmentName
"</td></tr>"
);
}
},
});
});
});
</script>
</head>
<body>
<a href="hello">测试</a>
<br />
<a href="testJson">测试JSON</a>
<br />
<input id="btn" type="button" value="测试ajax" />
</body>
</html>
测试:
二、HttpMessageConverter 原理
1、HttpMessageConverter<T>
HttpMessageConverter<T> 是 Spring3.0 新添加的一个接口, 负责将请求信息转换为一个对象(类型为 T), 将对象(类型为 T)输出为响应信息
2、HttpMessageConverter<T>接口定义的方法
- Boolean canRead(Class<?> clazz,MediaType mediaType): 指定转换器可以读取的对象类型,即转 换 器 是 否 可 将 请 求 信 息 转 换 为 clazz 类 型 的 对 象 , 同 时 指 定 支 持 MIME 类 型(text/html,applaiction/json 等)
- Boolean canWrite(Class<?> clazz,MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型在 MediaType 中定义。
- List<MediaType> getSupportMediaTypes():该转换器支持的媒体类型。
- T read(Class<? extends T> clazz,HttpInputMessage inputMessage):将请求信息流转换为 T 类型的对象。
- void write(T t,MediaType contnetType,HttpOutputMessgae outputMessage):将 T 类型的对象写到响应流中,同时指定相应的媒体类型为 contentType。
package org.springframework.http;
import java.io.IOException;
import java.io.InputStream;
public interface HttpInputMessage extends HttpMessage {
InputStream getBody() throws IOException;
}
代码语言:javascript复制package org.springframework.http;
import java.io.IOException;
import java.io.OutputStream;
public interface HttpOutputMessage extends HttpMessage {
OutputStream getBody() throws IOException;
}
3、HttpMessageConverter
使用 HttpMessageConverter将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息, Spring 提供了两种途径:
- 使用 @RequestBody / @ResponseBody 对处理方法进行标注
- 使用 HttpEntity<T> / ResponseEntity<T> 作为处理方法的入参或返回值
三、文件上传下载
1、文件下载
- 代码示例
方式一:图片名字由服务进行绑定
【index.jsp】
代码语言:javascript复制<body>
<a href="down">下载图片</a>
</body>
【TestUploadAndDownController.java】
代码语言:javascript复制@RequestMapping(value = "/down")
public ResponseEntity<byte[]> down(HttpSession session) throws Exception {
// 获取下载文件的路径
String realPath = session.getServletContext().getRealPath("img");
String finalPath = realPath File.separator "2.jpg";
InputStream is = new FileInputStream(finalPath);
// available():获取输入流所读取的文件的最大字节数
byte[] bt = new byte[is.available()];
is.read(bt);
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition","attachment;filename=zzz.jpg");
// 设置响应状态
HttpStatus status = HttpStatus.OK;
ResponseEntity<byte[]> entity = new ResponseEntity<>(bt, headers, status);
return entity;
}
测试:
方式二: 图片有请求方式来获取
【index.jsp】
代码语言:javascript复制<body>
<a href="down/1">下载图片</a>
</body>
【TestUploadAndDownController.java】
代码语言:javascript复制@RequestMapping(value = "/down/{username}")
public ResponseEntity<byte[]> down(@PathVariable("username") String username, HttpSession session) throws Exception {
// 获取下载文件的路径
String realPath = session.getServletContext().getRealPath("img");
String finalPath = realPath File.separator username ".jpg";
InputStream is = new FileInputStream(finalPath);
byte[] bt = new byte[is.available()];
is.read(bt);
// 获取请求头
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition","attachment;filename=" username ".jpg");
// 设置响应状态
HttpStatus status = HttpStatus.OK;
ResponseEntity<byte[]> entity = new ResponseEntity<>(bt, headers, status);
return entity;
}
测试:
2、文件上传
- 导入需要的 Jar 包
- 代码示例 【index.jsp】
<form action="up" method="post" enctype="multipart/form-data">
头像:<input type="file" name="uploadFile"/>
描述:<input type="text" name="desc"/>
<input type="submit" value="上传"/>
</form>
【配置 spingmvc.xml】不配置上传则会报错
代码语言:javascript复制<!--
处理文件,将客户端上传的File文件,处理为MultipartFile
注意:文件解析器的bean中id必须设置为multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置文件解析的编码,注意:一定要和页面的pageEncoding保持一致 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 设置最大上传文件大小 -->
<property name="maxUploadSize" value="88888888"></property>
</bean>
【TestUploadAndDownController.java】
方式一:
代码语言:javascript复制@RequestMapping(value = "/up" ,method = RequestMethod.POST)
public String up (String desc, MultipartFile uploadFile, HttpSession session) throws IOException {
// 获取上传文件的名称
String filename = uploadFile.getOriginalFilename();
String path = session.getServletContext().getRealPath("phone") File.separator filename;
// 获取输入流
InputStream is = uploadFile.getInputStream();
// 获取输出流
File file = new File(path);
OutputStream fos = new FileOutputStream(file);
byte[] bt = new byte[1024];
int len;
while((len = is.read(bt)) != -1){
fos.write(bt, 0, len);
}
is.close();
fos.close();
return "success";
}
注意:上传出现异常
类型 异常报告 消息 E:IDEASpringMVCSpringMVC_demo3outartifactsSpringMVC_demo3_war_explodedphone1.jpg (系统找不到指定的路径。) java.io.FileNotFoundException: E:IDEASpringMVCSpringMVC_demo3outartifactsSpringMVC_demo3_war_explodedphone1.jpg (系统找不到指定的路径。)
解决方法:(以为自己的为例子)E:IDEASpringMVCSpringMVC_demo3outartifactsSpringMVC_demo3_war_exploded 这个目录下创建一个phone 文件就可解决问题
可以在代码中写入 System.out.prinln(path); 方便查看图片上传的位置
方式二:
代码语言:javascript复制@RequestMapping(value = "/up",method = RequestMethod.POST)
public String up (MultipartFile uploadFile, HttpSession session) throws IOException {
// 获取上传文件的名称
String filename = uploadFile.getOriginalFilename();
String finalFileName = UUID.randomUUID() filename.substring(filename.lastIndexOf("."));
String path = session.getServletContext().getRealPath("phone") File.separator finalFileName;
File file = new File(path);
uploadFile.transferTo(file);
return "success";
}
3、多个文件上传
【index.jsp】
代码语言:javascript复制<form action="up" method="post" enctype="multipart/form-data">
头像:<input type="file" name="uploadFile"/>
头像1:<input type="file" name="uploadFile"/>
头像2:<input type="file" name="uploadFile"/>
<input type="submit" value="上传"/>
</form>
【TestUploadAndDownController.java】
代码语言:javascript复制@RequestMapping(value = "/up",method = RequestMethod.POST)
public String ups(MultipartFile[] uploadFile, HttpSession session) throws IOException {
for(MultipartFile uploadFiles : uploadFile){
// 判断文件是否为空
if(! uploadFiles.isEmpty()){
String filename = uploadFiles.getOriginalFilename();
String finalFileName = UUID.randomUUID() filename.substring(filename.lastIndexOf("."));
String path = session.getServletContext().getRealPath("phone") File.separator finalFileName;
File file = new File(path);
uploadFiles.transferTo(file);
}
}
return "success";
}
四、拦截器
1、 自定义拦截器
Spring MVC 也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的 功 能 , 自 定 义 的 拦 截 器 可 以 实 现 HandlerInterceptor 接 口 , 也 可 以 继 承 HandlerInterceptorAdapter 适配器类 。
- **preHandle()**:这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回 true;如果程序员决定不需要再调用其他的组件去处理请求,则返回 false。
- postHandle(): 这个方法在业务处理器处理完请求后,但是 DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求 request 进行处理。
- afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
代码示例:
【spingmvc.xml】中配置拦截器(3 中方式)
①(推荐使用):
代码语言:javascript复制<mvc:interceptors>
<!--默认拦截所有的请求-->
<bean class="com.oy.online.springmvc.interceptor.FirstInterceptor"></bean>
</mvc:interceptors>
②:
代码语言:javascript复制<mvc:interceptors>
<!--此方式要求拦截器类上必须加注解@Component -->
<ref bean="firstInterceptor"></ref>
</mvc:interceptors>
③:
代码语言:javascript复制<mvc:interceptors>
<!-- 设置自定义拦截方式 -->
<mvc:interceptor>
<bean class="com.oy.online.springmvc.interceptor.FirstInterceptor"/>
<!-- 拦截所有的请求,这个必须写在前面,也就是写在【不拦截】的上面 -->
<mvc:mapping path="/**" />
<!-- 但是排除下面这些,也就是不拦截请求 -->
<mvc:exclude-mapping path="/login.html" />
</mvc:interceptor>
</mvc:interceptors>
【index.jsp】
代码语言:javascript复制<body>
<a href="testInterceptor">测试拦截器</a>
</body>
【FirstInterceptor.java】
preHandle(): 中的 return 值 true 表示不拦截放行,反之 false 拦截
代码语言:javascript复制public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("First:preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("First:postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("First:afterCompletion");
}
}
【TestInterceptorController.java】
代码语言:javascript复制@Controller
public class TestInterceptorController {
@RequestMapping(value = "/testInterceptor")
public String testInterceptor(){
return "success";
}
}
测试:
2、拦截器方法执行顺序
3、多个拦截器的执行顺序
图解:
总结:
- 当有多个拦截器时:
- preHandle: 按照拦截器数组的正向顺序执行
- postHandle: 按照拦截器数组得反向顺序执行
- afterCompletion: 按照拦截器的数组反向顺序执行
- 当多个拦截器的 PreHandle 有不同的值时
- 第一个返回 false,第二个返回 false: 只有第一个 preHandle 会执行
五、异常处理
1、异常处理的概述
- SpringMVC 通过 ExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行发生的异常。
- SpringMVC 提供 HandlerExceptionResolver 的实现类
2、HandlerExceptionResolver
- DispatcherServlet 默认装配的 HandlerExceptionResolver
- 没有使用 <mvc:annotation-driven/> 配置
- 使用了 <mvc:annotation-driven/> 配置:
3、异常处理_DefaultHandlerExceptionResolver
对一些特殊的异常进行处理,比如:
- NoSuchRequestHandlingMethodException
- HttpRequestMethodNotSupportedException
- HttpMediaTypeNotSupportedException
- HttpMediaTypeNotAcceptableException
代码示例:
【index.jsp】
代码语言:javascript复制<body>
<a href="testException">异常测试</a>
</body>
【ExceptionControllerTest.java】
代码语言:javascript复制
@RequestMapping(value = "/testException",method = RequestMethod.POST) // 不能是POST请求
public String testException(){
System.out.println("testDefaultHandlerExceptionResolver...");
return "success";
}
测试:
4、异常处理_SimpleMappingExceptionResolver
- 在 springmmvc.xml 中配置
<!--异常处理-->
<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
【ExceptionControllerTest.java】
代码语言:javascript复制@RequestMapping(value = "/testSimpleMappingException")
public String testSimpleMappingException(){
System.out.println("testSimpleMappingException....");
String[] s = new String[10];
// s[i] 下标大于数组的长度
System.out.println(s[12]);
return "success";
}
测试:
【eeor.jsp】
代码语言:javascript复制<body>
<a href="#">操作异常,请稍后重试</a>
<br>
${requestScope.exception }
</body>
六、运行流程
1、 流程图
工作流程描述:
- 用户向服务器发起请求,请求被 SpringMVC 前端控制器 DispatcherServlet 捕获:
- DispatcherServlet 对请求解析,得到请求资源的标识符(URL):
判断请求 URL 对应的映射
① 不存在:
- 再判断是否配置了 mvc:default-servlet-handler:
- 如果没配置,则控制台报映射查找不到,客户端展示 404 错误
② 存在:
- 执行下面的流程
- 根据 URL,调用 HandlerMapping 获取该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后 HandlerExecutionChain 对象的形式返回:
- DispatcherServlet 根据获得的 Handler, 选择一个合适的 HandlerAdapter。
- 如果成功获得 HandlerAdapter 后,此时将开始拦截器的 preHandler(…) 方法【正向】
- 提取 Request 中的模型数据,填充 Handler 入参,开始执行 Handler (Controller)方法,处理请求。在填充 Handler 的入参过程中,根据配置,SpringMVC 将帮助做一些额外的工作: ① HttpMessageConverter: 将请求的信息(如 json、xml 的数据)转换成一个对象,将对象转换成指定的响应信息 ② 数据转换:对请求的消息进行数据转换。如 String 转换 Integer、Double 等。 ③ 数据格式化:对请求的消息进行数据格式化。如将字符串转换成数字或格式化日期等。 ④ 数据验证:验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 EEOR 中。
- Handler 执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象
- 此时开始执行拦截器的 postHasndle(…)方法【逆向】
- 根据返回的 ModelAndView (此时会判断是否存在异常:如果存在异常,则执行 HandelExceptionResolver 进行异常处理)选择一个适合的 DispacherServlet(必须是已经注册到 Spring 容器中 的 ViewResolver )返回给 DispatcherServlet,根据 Model 和 View, 来渲染试图
- 在返回给客户端需要执行拦截器的 AfterCompletion 方法【逆向】
- 将渲染的结果返回给客户端
七、Spring 整合 SpringMVC
1、Spring 与 SpringMVC 的整合问题
- 需要进行 Spring 整合 SpringMVC 吗?
- 还是需要加入 Spring 的 IOC 容器?
- 是否需要在 web.xml 文件中配置启动 SpringMVC 容器的 ContextLoaderListener?
需要:通常情况下,类似于数据源,事务,整合其他框架都是放在 Spring 的配置文件中(而不是放在 SpringMVC 的配置文件中),实际上方入 Spring 配置文件对应的 IOC 容器中还有 Servlet 和 Dao。 不需要:都放在 SpringMVC 的配置文件中,也可以分多个 Spring 的配置文件,然后使用 import 节点导入其他的配置文件
2、Spring 整合 SpringMVC 解决方案配置监听器
- 监听器配置【web.xml】
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:bean.xml</param-value>
</context-param>
- 创建 Spring 的 【bean.xml】配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--设置扫描组件的包-->
<context:component-scan base-package="com.oy.online.springmvc">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--<bean id="teacher" class="com.oy.online.springmvc.bean.Teacher"></bean>-->
</beans>
- SpringMVC 配置文件:【Springmvc.xml】
<!-- 设置扫描组件的包 -->
<context:component-scan base-package="com.oy.online.springmvc.Controller"/>
<!-- 配置视图解析器 -->
<bean id="internalResourceViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
注意:在 Teanch 类中增加构造方法,启动服务器,查看构造器执行情况。若 Spring 的 IOC 容器 和 SpringMVC 的 IOC 容器扫描的包有重合的部分,就会导致有的 bean 会被创建 2 次。
解决:使 Spring 的 IOC 容器扫描的包和 SpringMVC 的 IOC 容器扫描的包没有重合的部分.使用 exclude-filter 和 include-filter 子节点来规定只能扫描的注解 。
【bean.xml】: <!– 不扫描@Controller 注解 –>
【Springmvc.xml】:<!– 扫描@Controller 注解 –>
- 【index.jsp】
<body>
<a href="testListener">监听器测试</a>
</body>
- 【SpringListener.java】
public class SpringListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
ServletContext servletContext = servletContextEvent.getServletContext();
servletContext.setAttribute("context", context);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
【ExceptionControllerTest.java】
代码语言:javascript复制@Controller
public class ExceptionControllerTest {
@RequestMapping()
public void testListener(HttpSession session){
// 获取spring所管理的teacher对象
ServletContext servletContext = session.getServletContext();
ApplicationContext context = (ApplicationContext) servletContext.getAttribute("context");
Teacher teacher = context.getBean("Teacher", Teacher.class);
System.out.println(teacher);
}
}
测试:
3、SpringIOC 容器和 SpringMVC IOC 容器的关系
- SpringMVC 的 IOC 容器中的 bean 可以来引用 Spring IOC 容器中的 bean,之则不行. Spring IOC 容器中的 bean 却不能来引用 SpringMVC IOC 容器中的 bean 。
- 在 Spring MVC 配置文件中引用业务层的 Bean
- 多个 Spring IOC 容器之间可以设置为父子关系,以实现良好的解耦。
- Spring MVC WEB 层容器可作为 “业务层” Spring 容器的子容器:即 WEB 层容器可以引用业务层容器的 Bean,而业务层容器却访问不到 WEB 层容器的 Bean 。
4、SpringMVC 对比 Struts2
- Spring MVC 的入口是 Servlet, 而 Struts2 是 FilterSpring MVC 会稍微比 Struts2 快些.
- Spring MVC 是基于方法设计, 而 Sturts2 是基于类,每次发一次请求都会实例一个 Action.
- Spring MVC 使用更加简洁, 开发效率 Spring MVC 确实比 struts2 高: 支持 JSR303, 处理 ajax 的请求更方便
- Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些