SpringMVC的使用(下)
一、SpringMVC处理JSON对象
到目前为止我们编写的所有Controller的方法的返回值都是String类型,返回的数据通过Map、Model、ModelAndMap来传送,然后再前端页面中使用${}来获取数据。
但是大家应该都知道,我们有时候数据传递特别是在ajax中,我们返回的数据经常需要使用JSON类型的数据,JSON是一种特别重要的数据类型,我们在传输数据的时候,一般都是使用JSON来传递,那么如何来保证返回的数据的是JSON格式呢?使用@ResponseBody注解即可
JSON:
简单来说,JSON就是字符串,只不过这种字符串比较特殊,有自己的格式:
{ key:value, key: value, key: [ ]…}
目前有很多JSON解析工具,不过所有的解析工具其实就是把字符串转换成JSON对象,或者把JSON对象转换成字符串,就是数据格式的自动转换功能,现在最常用的就是alibaba的fastjson。
1、@RequestBody注解
@RequestBody用于接收前台传来的java对象,注意@RequestBody的元注解@Target(ElementType.PARAMETER),说明@RequestBody只作用于方法的对应形参上。
原理是读取http的请求,通过内置的HttpMessageConverter将读取到的内容转为json等格式,然后再转为java对象绑定到对应参数上。简单而言,就是将json等格式的数据转为java对象。
下面方法就会将前台通过AJAX转来的user对象(以json等格式),注入到user参数中
@RequestMapping("/test")
public void test(@RequestBody User user){
System.out.println(user.toString());
}
2、@ResponseBody注解
此注解表示这个方法应该有一个返回值(如json),这个返回值应该绑定到web的响应体里面,直接返回给客户端。
我们在使用@RequestMapping的时候,一般return的值都是跳转的页面,然而加上@ResponseBody之后,return的值会直接打印在@RequestMapping设置的页面上
//会跳转到success页面
@RequestMapping("/test")
public void test(User user){
return "success";
}
//会在test页面上打印success
@RequestMapping("/test")
@ResponseBody
public void test(User user){
return "success";
}
所以@ResponseBody的效果等同与response.getWriter.write("")
1、导入pom依赖
如果想使用json数据,需要在原来的基础之上导入支持json的依赖:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.10.3</version>
</dependency>
2、JsonController.java
@Controller
public class JsonController {
@ResponseBody
@RequestMapping("/json")
public List<User> testJson(){
List<User> list = new ArrayList<User>();
list.add(new User(1, "zhangsan1",17,"man"));
list.add(new User(2, "zhangsan2",18,"woman"));
list.add(new User(3, "zhangsan2",19,"man"));
return list;
}
}
3、测试
可以发现在浏览器能够直接获取JSON类型的数据
补充:有很多xml的配置我们在上篇文章中已经详细讲述,这里不再赘述。
4、总结
其实我们可以发现,添加此注解之后,我们方法返回的数据就是直接作为responsebody返回给客户端。也就是说,其实我们可以自己定义任意类型的responsebody,比如下面我们直接返回html标签给客户端,客户端会把标签进行渲染展示:
JsonController.java
@Controller
public class JsonController {
@ResponseBody
@RequestMapping("/json2")
public String testJson2(){
return "<h1>hello JSON <h1>";
}
}
测试结果:会把html标签也返回,浏览器会进行渲染。
这时注意一个@RestController注解,@RestController=@Controller+@ResponseBody,@RestController标注在类上标示这个类是一个controller同时类中所有方法的返回值,都不会进行页面跳转而是打印在页面上
3、使用RespsonseEntity可以用来定制响应内容
我们可以通过MVC提供的ResponseEntity来定制我们的响应体内容。
@Controller
public class OtherController {
@RequestMapping("/testResponseEntity")
public ResponseEntity<String> testResponseEntity(){
String body = "<h1>hello</h1>";
MultiValueMap<String,String> header = new HttpHeaders();
header.add("Set-Cookie","name=zhangsan");
return new ResponseEntity<String>(body,header, HttpStatus.OK);
}
}
4、其他注解
@Configuration和@Bean
标示这个类是spring配置类,一般与@Bean联用
所以@Configuration相当于配置文件中的,@Bean相当于bean
@Configuration
public class MyConfig {
@Bean
public User user(){
return new User("zhangsan");
}
}
上面这段代码就等价于(方法名就是id,return的类型就是class)
<beans>
<bean id="user" class="com.entity.User">
<constructor-arg>
<value>zhangsan</value>
</constructor-arg>
</bean>
</beans>
二、SpringMVC的使用
1、文件下载
下载操作跟我们之前的所有操作都不太一样。提供下载功能的方法返回值是一个文件,而不是跳转到某一个页面或者显示某一个数据,需要使用MVC内置的各种Servlet原生API的组合使用,还需要用到文件IO流。
@Controller
public class OtherController {
@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception {
//获取要下载文件的路径及输入流对象
ServletContext servletContext = request.getServletContext();
//我们在项目目录下提供的支持下载的文件的存放路径
String realPath = servletContext.getRealPath("/test/下载文件.txt");
FileInputStream fileInputStream = new FileInputStream(realPath);
byte[] bytes = new byte[fileInputStream.available()];
fileInputStream.read(bytes);
fileInputStream.close();
//将要下载文件内容返回
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Disposition","attachment;filename=下载文件.txt");
return new ResponseEntity<byte[]>(bytes,httpHeaders,HttpStatus.OK);
}
}
2、文件上传
Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。上传文件我们直接用IO流会很麻烦,Spring 用Apache提供的 Jakarta Commons FileUpload 组件技术实现了一个 MultipartResolver 实现类:CommonsMultipartResovler
Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需要在上下文中配置 MultipartResolver。
1、添加pom依赖
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
2、在springmvc.xml配置文件中添加处理文件上传的multipartResolver的bean对象
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>//设置文件编码
<property name="maxUploadSize" value="1024000"></property>//限制文件大小
</bean>
3、处理文件下载的方法
@Controller
public class UploadHandler {
@RequestMapping(value = "/testUpload", method = RequestMethod.POST)
public String testUpload(@RequestParam(value = "desc", required = false) String desc, @RequestParam("file") MultipartFile multipartFile) throws IOException {
System.out.println("desc : " + desc);
System.out.println("OriginalFilename : " + multipartFile.getOriginalFilename());
multipartFile.transferTo(new File("D:\\file\\"+multipartFile.getOriginalFilename()));
return "success"; //增加成功页面: /views/success.jsp
}
}
4、上传文件需要编写表单,我们这里省略演示过程。
总结:文件的上传和下载的流程几乎都是固定的,可以自己总结一套上传和下载的代码模板,需要用到文件上传和下载的功能的时候,拿来即用即可。
3、SpringMVC拦截器
SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作或者目标方法运行之后进行一下其他相关的处理。自定义的拦截器必须实现HandlerInterceptor接口,拦截器和过滤器差不多,也是使用了责任链模式,链式执行,对请求进行筛选。
preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false
postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。
afterCompletion():这个方法在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
1、编写自定义拦截器
/**
* 类似于过滤器,但是和过滤器有区别,要注意区分
*/
public class MyInterceptor implements HandlerInterceptor {
/**
* 在处理器(Controller)执行具体的处理方法之前执行
* @param request
* @param response
* @param handler
* @return 因为是链式执行,当返回值是true,才会继续向下执行责任链;如果返回false,表示请求处理到此结束
* 可以使用 if(...){return true}
* else if("不满足条件"){return false}
* 当某一个拦截器判断请求有问题,就可以返回false,不然请求继续向下处理。
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(this.getClass().getName() + "-----preHandle执行");
return true;
}
/**
* 在处理器完成方法的处理之后执行
* 这两个方法类似于PostProcessor的功能,相当于配置了请求处理方法的前置和后置处理器
*/
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(this.getClass().getName() + "-----postHandle执行");
}
/**
* 在整个Servlet处理完成之后才会执行,主要做资源的清理工作
*/
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(this.getClass().getName() + "-----afterCompletion执行");
}
}
2、将拦截器配置进springmvc.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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--开启包的扫描路径,有IOC容器来管理bean对象-->
<context:component-scan base-package="com.mjt"></context:component-scan>
<!--此配置表示 我们自己配置的请求由controller来处理,但是不能请求的处理交由tomcat来处理
静态资源可以访问,但是动态请求无法访问-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!--保证静态资源和动态请求都能够访问-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--配置前端视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置请求资源的前缀 -->
<!--设置成如下的配置表示:必须要在/WEB-INF/page/路径下的文件才允许被访问-->
<property name="prefix" value="/WEB-INF/page/"></property>
<!-- 配置请求资源的后缀 -->
<!--设置成如下的配置表示:必须要是以 .jsp 结尾的文件才允许被访问-->
<property name="suffix" value=".jsp"></property>
</bean>
<!--配置我们自定义的拦截器-->
<mvc:interceptors>
<bean class="com.mjt.interceptor.MyInterceptor"></bean>
</mvc:interceptors>
</beans>
3、进行测试
编写请求处理类
InterceptorController.java
@Controller
public class InterceptorController {
@ResponseBody
@RequestMapping("/testInterceptor")
public String testInterceptor(){
System.out.println(this.getClass().getName() + "处理请求");
return "<h2>success</h2>";
}
}
执行结果
页面正常渲染,截图略。控制台输出日志信息如下:
4、总结
通过以上日志信息我们可以分析出,拦截器方法的执行顺序是:
可以看到先执行拦截器的preHandle方法----》执行目标方法----》执行拦截器的postHandle方法----》执行页面跳转----》执行拦截器的afterCompletion方法
在配置拦截器的时候有两个需要注意的点:
- 如果preHandle方法返回值 为false,那么意味着不放行,那么就会造成后续的所有操作都中断
- 如果执行到方法中出现异常,那么后续流程不会处理,但是afterCompletion方法会执行
5、多个拦截器
我们已经知道拦截器和过滤器类似,都是进行链式执行的,也就是说一般会有多个拦截器,那么多个拦截器的执行顺序应该是什么样的?
首先添加另一个拦截器
MySecondInterceptor.java
public class MySecondInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(this.getClass().getName() + "-----preHandle执行");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(this.getClass().getName() + "-----postHandle执行");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(this.getClass().getName() + "-----afterCompletion执行");
}
}
在springmvc.xml文件中配置多个拦截器
<!--配置我们自定义的拦截器,多个拦截器的配置顺序即拦截器的执行顺序-->
<mvc:interceptors>
<bean class="com.mjt.interceptor.MyInterceptor"></bean>
</mvc:interceptors>
<mvc:interceptors>
<bean class="com.mjt.interceptor.MySecondInterceptor"></bean>
</mvc:interceptors>
进行测试
大家可以看到对应的效果,配置多个拦截器时谁先执行取决于配置的顺序:
-
拦截器的preHandle方法是按照顺序执行的
-
拦截器的postHandle和afterCompletion方法是按照逆序执行的
-
如果执行的时候核心的业务代码出问题了,那么已经通过的拦截器的afterCompletion会接着执行。
多个拦截器必然要用集合存放,执行preHandle方法按0~length遍历集合顺序执行,执行postHandle和afterCompletion方法时按length-0遍历集合逆序执行
4、拦截器和过滤器的区别
-
过滤器是基于函数回调的,而拦截器是基于java反射的
-
过滤器依赖于servlet容器,而拦截器不依赖与Servlet容器
-
过滤器几乎对所有的请求都起作用,而拦截器只能对action请求起作用
-
拦截器可以访问action的上下文,而过滤器不可以
-
在action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化的时候调用一次
5、其他
国际化
除了上面这些操作之外,SpringMVC还可以完成国际化操作。
在日常工作中,如果你的网站需要给不同语言地区的人进行查看,此时就需要使用国际化的基本操作,springmvc的国际化操作比较容易。也可以完成通过超链接切换国际化。
SpringMVC异常处理机制
在SpringMVC中拥有一套非常强大的异常处理机制,SpringMVC通过HandlerExceptionResolver处理程序的异常,包括请求映射,数据绑定以及目标方法的执行时发生的异常。
在容器启动好,进入DispatcherServlet之后,会对HandlerExceptionResolver进行初始化操作:
会默认的从DispatcherServlet.properties中找到对应的异常处理类:
#默认的处理类
org.springframework.web.servlet.HandlerExceptionResolver=
#处理@ExceptionHandler
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
#处理@ResponseStatus,给自定义异常使用
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
#判断是否是SpringMVC自带异常
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
三、总结
在以上内容中我们学习了如下内容:
- SpringMVC如何通过@RequestBody获取请求体传输里面的数据(如JSON)
- 如何返回给前台特定类型的数据(如JSON)
- 如何通过RespsonseEntity来定制我们的响应内容
- 文件上传、下载操作
- 拦截器的相关概念和实现
- 拦截器和过滤器的区别
- 国际化、MVC的异常处理机制(未实现)
附:SpringMVC的启动流程
SpringMVC请求流程