返回json
目前主流的 JSON 处理工具主要有三种:
在 SpringMVC 中,对 jackson 和 gson 都提供了相应的支持,就是如果使用这两个作为 JSON 转换器,只需要添加对应的依赖就可以了,返回的对象和返回的集合、Map 等都会自动转为 JSON,
但是,如果使用 fastjson,除了添加相应的依赖之外,还需要自己手动配置 HttpMessageConverter 转换器。其实前两个也是使用 HttpMessageConverter 转换器,但是是 SpringMVC 自动提供的,SpringMVC 没有给 fastjson 提供相应的转换器。
jackson
1 2 3 4 5
| <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.1</version> </dependency>
|
依赖添加成功后,凡是在接口中直接返回的对象,集合等等,都会自动转为 JSON。
添加了 jackson ,就能够自动返回 JSON,这个依赖于一个名为 HttpMessageConverter 的类,这本身是一个接口,从名字上就可以看出,它的作用是 Http 消息转换器,既然是消息转换器,它提供了两方面的功能:
- 将返回的对象转为 JSON
- 将前端提交上来的 JSON 转为对象
HttpMessageConverter 只是一个接口,由各个 JSON 工具提供相应的实现,在 jackson 中,实现的名字叫做 MappingJackson2HttpMessageConverter,而这个东西的初始化,则由 SpringMVC 来完成。
注解:
1 2 3 4 5 6
| public class Book { private Integer id; private String name; private String author; @JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai") private Date publish;
|
全局配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <mvc:annotation-driven> <mvc:message-converters> <ref bean="httpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" id="httpMessageConverter"> <property name="objectMapper"> <bean class="com.fasterxml.jackson.databind.ObjectMapper"> <property name="dateFormat"> <bean class="java.text.SimpleDateFormat"> <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/> </bean> </property> <property name="timeZone" value="Asia/Shanghai"/> </bean> </property> </bean>
|
gson
1 2 3 4 5
| <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.6</version> </dependency>
|
如果项目中,同时存在 jackson 和 gson 的话,那么默认使用的是 jackson,为社么呢?在 org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter 类的构造方法中,加载顺序就是先加载 jackson 的 HttpMessageConverter,后加载 gson 的 HttpMessageConverter。
自定义配置:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <mvc:annotation-driven> <mvc:message-converters> <ref bean="httpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean class="org.springframework.http.converter.json.GsonHttpMessageConverter" id="httpMessageConverter"> <property name="gson"> <bean class="com.google.gson.Gson" factory-bean="gsonBuilder" factory-method="create"/> </property> </bean> <bean class="com.google.gson.GsonBuilder" id="gsonBuilder"> <property name="dateFormat" value="yyyy-MM-dd"/> </bean>
|
fastjson
fastjson 号称最快的 JSON 解析器,但是也是这三个中 BUG 最多的一个。在 SpringMVC 并没针对 fastjson 提供相应的 HttpMessageConverter,所以,fastjson 在使用时,一定要自己手动配置 HttpMessageConverter(前面两个如果没有特殊需要,直接添加依赖就可以了)。
使用fastjosn,首先引入依赖:
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.60</version> </dependency>
|
然后在 SpringMVC 的配置文件中配置 HttpMessageConverter:
1 2 3 4 5 6 7 8 9 10 11 12
| <mvc:annotation-driven> <mvc:message-converters> <ref bean="httpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter" id="httpMessageConverter"> <property name="fastJsonConfig"> <bean class="com.alibaba.fastjson.support.config.FastJsonConfig"> <property name="dateFormat" value="yyyy-MM-dd"/> </bean> </property> </bean>
|
fastjson 默认中文乱码,添加如下配置解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <mvc:annotation-driven> <mvc:message-converters> <ref bean="httpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter" id="httpMessageConverter"> <property name="fastJsonConfig"> <bean class="com.alibaba.fastjson.support.config.FastJsonConfig"> <property name="dateFormat" value="yyyy-MM-dd"/> </bean> </property> <property name="supportedMediaTypes"> <list> <value>application/json;charset=utf-8</value> </list> </property> </bean>
|
接收json
在 SpringMVC 中,我们可以通过一个注解来快速的将一个 JSON 字符串转为一个对象:
1 2 3 4 5
| @RequestMapping("/addbook3") @ResponseBody public void addBook3(@RequestBody Book book) { System.out.println(book); }
|
文件上传
SpringMVC 中对文件上传做了封装,我们可以更加方便的实现文件上传。提供了两个处理器:
- CommonsMultipartResolver SpringMVC3.0
- StandardServletMultipartResolver SpringMVC3.1
第一个处理器兼容性较好,可以兼容 Servlet3.0 之前的版本,但是它依赖了 commons-fileupload 这个第三方工具,所以如果使用这个,一定要添加 commons-fileupload 依赖。
第二个处理器兼容性较差,它适用于 Servlet3.0 之后的版本,它不依赖第三方工具,使用它,可以直接做文件上传。
CommonsMultipartResolver
使用 CommonsMultipartResolver 做文件上传,需要首先添加 commons-fileupload 依赖,如下:
1 2 3 4 5
| <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
|
然后,在 SpringMVC 的配置文件中,配置 MultipartResolver:
1
| <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
|
注意,这个 Bean 一定要有 id,并且 id 必须是 multipartResolver。
接下来,创建 jsp 页面:
1 2 3 4
| <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="上传"> </form>
|
文件上传接口:
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
| @Controller public class FileUploadController { SimpleDateFormat sdf = new SimpleDateFormat("/yyyy/MM/dd/");
@RequestMapping("/upload") @ResponseBody public String upload(MultipartFile file, HttpServletRequest req) { String format = sdf.format(new Date()); String realPath = req.getServletContext().getRealPath("/img") + format; File folder = new File(realPath); if (!folder.exists()) { folder.mkdirs(); } String oldName = file.getOriginalFilename(); String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); try { file.transferTo(new File(folder, newName)); String url = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/img" + format + newName; return url; } catch (IOException e) { e.printStackTrace(); } return "failed"; } }
|
在 SpringMVC 中,静态资源默认都是被自动拦截的,无法访问,意味着上传成功的图片无法访问,因此,还需要我们在 SpringMVC 的配置文件中,再添加如下配置:
1
| <mvc:resources mapping="/**" location="/"/>
|
当然,默认的配置不一定满足我们的需求,我们还可以自己手动配置文件上传大小等:
1 2 3 4 5 6 7 8 9 10 11 12
| <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"> <property name="defaultEncoding" value="UTF-8"/> <property name="maxUploadSize" value="1048576"/> <property name="maxUploadSizePerFile" value="1048576"/> <property name="maxInMemorySize" value="4096"/> <property name="uploadTempDir" value="file:///E:\\tmp"/> </bean>
|
StandardServletMultipartResolver
这种文件上传方式,不需要依赖第三方 jar(主要是不需要添加 commons-fileupload 这个依赖),但是也不支持 Servlet3.0 之前的版本。
配置:
1 2
| <bean class="org.springframework.web.multipart.support.StandardServletMultipartResolver" id="multipartResolver"> </bean>
|
注意,这里 Bean 的名字依然叫 multipartResolver
这个 Bean 无法直接配置上传文件大小等限制。需要在 web.xml 中进行配置(这里,即使不需要限制文件上传大小,也需要在 web.xml 中配置 multipart-config):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-servlet.xml</param-value> </init-param> <multipart-config> <location>E:\\temp</location> <max-file-size>1048576</max-file-size> <max-request-size>1048576</max-request-size> <file-size-threshold>4096</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
|
配置完成后,就可以测试文件上传了,测试方式和上面一样。
多文件上传
多文件上传分为两种,一种是 key 相同的文件,另一种是 key 不同的文件。
key相同的文件:
1 2 3 4
| <form action="/upload2" method="post" enctype="multipart/form-data"> <input type="file" name="files" multiple> <input type="submit" value="上传"> </form>
|
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @RequestMapping("/upload2") @ResponseBody public void upload2(MultipartFile[] files, HttpServletRequest req) { String format = sdf.format(new Date()); String realPath = req.getServletContext().getRealPath("/img") + format; File folder = new File(realPath); if (!folder.exists()) { folder.mkdirs(); } try { for (MultipartFile file : files) { String oldName = file.getOriginalFilename(); String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); file.transferTo(new File(folder, newName)); String url = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/img" + format + newName; System.out.println(url); } } catch (IOException e) { e.printStackTrace(); } }
|
key不同的文件:
1 2 3 4 5
| <form action="/upload3" method="post" enctype="multipart/form-data"> <input type="file" name="file1"> <input type="file" name="file2"> <input type="submit" value="上传"> </form>
|
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @RequestMapping("/upload3") @ResponseBody public void upload3(MultipartFile file1, MultipartFile file2, HttpServletRequest req) { String format = sdf.format(new Date()); String realPath = req.getServletContext().getRealPath("/img") + format; File folder = new File(realPath); if (!folder.exists()) { folder.mkdirs(); } try { String oldName = file1.getOriginalFilename(); String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); file1.transferTo(new File(folder, newName)); String url1 = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/img" + format + newName; System.out.println(url1); String oldName2 = file2.getOriginalFilename(); String newName2 = UUID.randomUUID().toString() + oldName2.substring(oldName2.lastIndexOf(".")); file2.transferTo(new File(folder, newName2)); String url2 = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/img" + format + newName2; System.out.println(url2); } catch (IOException e) { e.printStackTrace(); } }
|
静态资源访问
SpringMVC 中,静态资源,默认都是被拦截的,例如 html、js、css、jpg、png、txt、pdf 等等,都是无法直接访问的。因为所有请求都被拦截了,所以,针对静态资源,我们要做额外处理,处理方式很简单,直接在 SpringMVC 的配置文件中,添加如下内容:
1
| <mvc:resources mapping="/static/html/**" location="/static/html/"/>
|
拦截器
SpringMVC 中的拦截器,相当于 Jsp/Servlet 中的过滤器,只不过拦截器的功能更为强大。
拦截器的定义非常容易:
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
| @Component public class MyInterceptor1 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor1:preHandle"); return true; }
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor1:postHandle");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor1:afterCompletion");
} } @Component public class MyInterceptor2 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor2:preHandle"); return true; }
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor2:postHandle");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor2:afterCompletion");
} }
|
springMVC文件配置:
1 2 3 4 5 6 7 8 9 10
| <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <ref bean="myInterceptor1"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <ref bean="myInterceptor2"/> </mvc:interceptor> </mvc:interceptors>
|
全局异常处理
项目中,可能会抛出多个异常,我们不可以直接将异常的堆栈信息展示给用户,有两个原因:
- 用户体验不好
- 非常不安全
SpringMVC 中,针对全局异常也提供了相应的解决方案,主要是通过 @ControllerAdvice 和 @ExceptionHandler 两个注解来处理的。
1 2 3 4 5 6 7 8 9
| @ControllerAdvice public class MyException { @ExceptionHandler(Exception.class) public ModelAndView fileuploadException(Exception e) { ModelAndView error = new ModelAndView("error"); error.addObject("error", e.getMessage()); return error; } }
|