一、处理 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】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <%@ 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 的支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @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】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| <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。
1 2 3 4 5 6
| package org.springframework.http; import java.io.IOException; import java.io.InputStream; public interface HttpInputMessage extends HttpMessage { InputStream getBody() throws IOException; }
|
1 2 3 4 5 6
| 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】
1 2 3
| <body> <a href="down">下载图片</a> </body>
|
【TestUploadAndDownController.java】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @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); 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】
1 2 3
| <body> <a href="down/1">下载图片</a> </body>
|
【TestUploadAndDownController.java】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @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】
1 2 3 4 5
| <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】不配置上传则会报错
1 2 3 4 5 6 7 8 9 10
|
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"></property> <property name="maxUploadSize" value="88888888"></property> </bean>
|
【TestUploadAndDownController.java】
方式一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @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:\IDEA\SpringMVC\SpringMVC_demo3\out\artifacts\SpringMVC_demo3_war_exploded\phone\1.jpg (系统找不到指定的路径。)
java.io.FileNotFoundException: E:\IDEA\SpringMVC\SpringMVC_demo3\out\artifacts\SpringMVC_demo3_war_exploded\phone\1.jpg (系统找不到指定的路径。)
解决方法:(以为自己的为例子)E:\IDEA\SpringMVC\SpringMVC_demo3\out\artifacts\SpringMVC_demo3_war_exploded 这个目录下创建一个phone 文件就可解决问题
可以在代码中写入 System.out.prinln(path); 方便查看图片上传的位置
方式二:
1 2 3 4 5 6 7 8 9 10
| @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】
1 2 3 4 5 6
| <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】
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @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 中方式)
①(推荐使用):
1 2 3 4
| <mvc:interceptors> <bean class="com.oy.online.springmvc.interceptor.FirstInterceptor"></bean> </mvc:interceptors>
|
②:
1 2 3 4
| <mvc:interceptors> <ref bean="firstInterceptor"></ref> </mvc:interceptors>
|
③:
1 2 3 4 5 6 7 8 9 10
| <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】
1 2 3
| <body> <a href="testInterceptor">测试拦截器</a> </body>
|
【FirstInterceptor.java】
preHandle(): 中的 return 值 true 表示不拦截放行,反之 false 拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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】
1 2 3 4 5 6 7 8
| @Controller public class TestInterceptorController {
@RequestMapping(value = "/testInterceptor") public String testInterceptor(){ return "success"; } }
|
测试:
2、拦截器方法执行顺序
3、多个拦截器的执行顺序
图解:
总结:
当有多个拦截器时:
- preHandle: 按照拦截器数组的正向顺序执行
- postHandle: 按照拦截器数组得反向顺序执行
- afterCompletion: 按照拦截器的数组反向顺序执行
当多个拦截器的 PreHandle 有不同的值时
第一个返回 false,第二个返回 false: 只有第一个 preHandle 会执行
第一个返回 true, 第二个返回 false;两个(全部)拦截器的 preHandle 都会执行,但是(全部)postHandle 都不会执行,而 afterCompletion 只有第一个(返回 false 的拦截器之前的拦截器之前的所有 afterCompletion)会执行
第一个返回 fasle,第二个返回 true; 只有第一个的 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】
1 2 3
| <body> <a href="testException">异常测试</a> </body>
|
【ExceptionControllerTest.java】
1 2 3 4 5 6
| @RequestMapping(value = "/testException",method = RequestMethod.POST) public String testException(){ System.out.println("testDefaultHandlerExceptionResolver..."); return "success"; }
|
测试:
4、异常处理_SimpleMappingExceptionResolver
- 在 springmmvc.xml 中配置
1 2 3 4 5 6 7 8
| <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】
1 2 3 4 5 6 7 8
| @RequestMapping(value = "/testSimpleMappingException") public String testSimpleMappingException(){ System.out.println("testSimpleMappingException...."); String[] s = new String[10]; System.out.println(s[12]); return "success"; }
|
测试:
【eeor.jsp】
1 2 3 4 5
| <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】
1 2 3 4 5 6 7 8
| <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】配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?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>
</beans>
|
- SpringMVC 配置文件:【Springmvc.xml】
1 2 3 4 5 6 7 8 9 10
| <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】
1 2 3
| <body> <a href="testListener">监听器测试</a> </body>
|
- 【SpringListener.java】
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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】
1 2 3 4 5 6 7 8 9 10 11 12
| @Controller public class ExceptionControllerTest {
@RequestMapping() public void testListener(HttpSession session){ 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 更高些