SpringMVC——处理json、文件上传和下载、拦截器、国际化
一、springmvc处理json
1.1 HttpMessageConverter介绍
HttpMessageConverter
是 Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象(类型为 T),将对象(类型为 T)输出为响应信息- HttpMessageConverter接口定义的方法:
- Boolean canRead(Class<?> clazz,MediaType mediaType): 指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对象,同时指定支持 MIME 类型(text/html,applaiction/json等)
- Boolean canWrite(Class<?> clazz,MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型 在MediaType 中定义。
List getSupportMediaTypes():该转换器支持的媒体类型。- T read(Class<? extends T> clazz,
HttpInputMessage
inputMessage):将请求信息流转换为 T 类型的对象。- void write(T t,MediaType contnetType,
HttpOutputMessgae
outputMessage):将T类型的对象写到响应流中,同时指定相应的媒体类 型为 contentType。
HttpMessageConverter的实现类:
DispatcherServlet 默认装配 RequestMappingHandlerAdapter,而RequestMappingHandlerAdapter 默认装配如下 HttpMessageConverter:
加入 jackson jar 包后,RequestMappingHandlerAdapter 装配的 HttpMessageConverter 如下:
使用 HttpMessageConverter:
- 使用 HttpMessageConverter 将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:
- 使用
@RequestBody
/@ResponseBody
对处理方法进行标注- 使用
HttpEntity
/ResponseEntity
作为处理方法的入参或返回值- 当控制器处理方法使用到 @RequestBody/@ResponseBody 或 HttpEntity/ResponseEntity 时, Spring
首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter,进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter
, 若找不到可用的 HttpMessageConverter 将报错- @RequestBody 和 @ResponseBody 不需要成对出现
1.2 SringMVC响应json数据示例
使用@ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端。
Springmvc 默认用 MappingJackson2HttpMessageConverter 对 json 数据进行转换,需要加入 jackson 的包。
原生javaWeb返回json数据:
1、导入GSON;
2、返回的数据用GSON转成json
3、写出去;
SpringMVC返回json
-
导包
SpringMVC-ajax: jackson-annotations-2.1.5.jar jackson-core-2.1.5.jar jackson-databind-2.1.5.jar
-
编写目标方法,使其返回JSON对应的对象或集合
-
在方法上添加@ResponseBody注解
/** * 返回JSON对应的对象或集合 * @ResponseBody:如果是返回的是对象,jackson将对象转为json格式 * @return */ @ResponseBody @RequestMapping("/getallajax") public Collection<Employee> ajaxGetAll(){ Collection<Employee> all = employeeDao.getAll(); return all;//jackson将Employee对象集合转为json格式 }
可以通过jackson包中的一些注解实现转换成json的更高级的操作。
例如:
参考博客:SpringMVC中关于Json转换的注解
1.3 发送json数据给服务器示例
使用 @RequestBody 注解将接收到的 json 数据转换成对象。(发送json数据给服务器,并将其封装成Employee对象)
JSP页面:
<a href="${ctp }/testRequestBody">ajax发送json数据</a>
<script type="text/javascript">
$("a:first").click(function(){
var emp = {
lastName:"张三",
email:"[email protected]",
gender:1
};
var empStr = JSON.stringify(emp);//将javscript对象转成json字符串
$.ajax({
url:"${ctp }/testRequestBody",
type:"POST",
data:empStr,
contentType:"application/json",//指定发送的为json类型的数据
success:function(data){
alert(data);
}
});
return false;
});
</script>
控制器方法:(此时形参中即为封装好的employee对象)
/**
* @RequestBody:获取一个请求的请求体
*/
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody Employee employee){
System.out.println("请求体:"+employee);
//请求体:Employee [id=null, lastName=张三, [email protected], gender=0, birth=Sat Mar 27 11:37:22 CST 2021, department=null]
return "success";
}
同时 @RequestBody还可以获取请求体
代码示例:
/**
* @RequestBody:获取一个请求的请求体
*/
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body){
System.out.println("请求体:"+body);
//请求体:username=zhaobin&password=123456
return "success";
}
<form action="testRequestBody" method="post">
<input name="username"/><br/>
<input type="password" name="password"/><br/>
<input type="submit" value="submit"/>
</form>
1.4 HttpEntity、ResponseEntity 示例
httpEntity
获取请求体和请求头
ResponseEntity
返回响应数据同时定制响应头
httpEntity代码示例:(使用httpEntity获取请求头和请求体)
/**
* 如果参数位置写HttpEntity<String> str;
* 比@RequestBody更强,可以拿到请求头;
*/
@RequestMapping("/test02")
public String test02(HttpEntity<String> str){
System.out.println(str);//输出的信息包括请求头
return "success";
}
ResponseEntity 实现文件的下载:
/**
* 文件下载
*/
@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception{
System.out.println("进入下载后台");
//1、得到要下载的文件的流
//找到要下载的文件的真实路径
ServletContext context = request.getServletContext();
String realPath = context.getRealPath("/script/jquery-1.7.2.js");
FileInputStream is = new FileInputStream(realPath);
byte[] tmp = new byte[is.available()];
is.read(tmp);
is.close();
//2、将要下载的文件流返回
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Disposition", "attachment;filename="+"jquery-1.7.2.js");
return new ResponseEntity<byte[]>(tmp,httpHeaders,HttpStatus.OK);
}
二、文件上传和下载
- Springmvc文件上传需要配置
MultipartResolver
(文件解析器)(SpringMVC九大组件之一) - Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:
CommonsMultipartResovler
- Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver
文件上传步骤:
-
导入FileUpload 相关包
commons-fileupload-1.2.1.jar
commons-io-2.0.jar -
配置文件上传解析器(MultipartResolver):
<!-- 配置文件上传解析器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 配置最大上传大小 --> <property name="maxUploadSize" value="1024*1024*20"></property> <property name="defaultEncoding" value="utf-8"></property> <!-- 等等。。。 --> </bean>
-
上传页面(enctype=“multipart/form-data”)
<form action="${ctp }/upload" method="post" enctype="multipart/form-data"> 用户头像:<input type="file" name="headerimg"/><br/> 用户名:<input type="input" name="username"/><br/> <input type="submit" value="提交"/> </form>
-
控制器方法:
@RequestMapping("/upload") public String upload(Model model, @RequestParam("username")String username, @RequestParam("headerimg")MultipartFile file){ System.out.println("文件的名字:"+file.getName());//文件的名字:headerimg System.out.println("文件的名字:"+file.getOriginalFilename());//文件的名字:0a7cae0541825b0cb80e2a92643de805.jpg //文件保存 try { file.transferTo(new File("F:\\haha\\"+file.getOriginalFilename())); model.addAttribute("msg","文件上传成功"); } catch (Exception e) { model.addAttribute("msg","文件上传失败"+e.getMessage()); } return "forward:index.jsp"; }
多文件上传:
文件的下载:
ResponseEntity 实现文件的下载:
/**
* 文件下载
*/
@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception{
System.out.println("进入下载后台");
//1、得到要下载的文件的流
//找到要下载的文件的真实路径
ServletContext context = request.getServletContext();
String realPath = context.getRealPath("/script/jquery-1.7.2.js");
FileInputStream is = new FileInputStream(realPath);
byte[] tmp = new byte[is.available()];
is.read(tmp);
is.close();
//2、将要下载的文件流返回
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Disposition", "attachment;filename="+"jquery-1.7.2.js");
return new ResponseEntity<byte[]>(tmp,httpHeaders,HttpStatus.OK);
}
三、拦截器
- SpringMVC提供了拦截器机制;允许运行目标方法之前进行一些拦截工作,或者目标方法运行之后进行一些其他处理。
3.1 自定义拦截器
- 用户可以自定义拦截器来实现特定的功能,
自定义的拦截器必须实现HandlerInterceptor接口
preHandle()
:这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。postHandle()
:这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。afterCompletion()
:这个方法在 DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
即:
preHandle:在目标方法运行之前调用;返回boolean;return true;(chain.doFilter())放行; return false;不放行
postHandle:在目标方法运行之后调用:目标方法调用之后
afterCompletion:在请求整个完成之后;来到目标页面之后;chain.doFilter()放行;资源响应之后;
实现过程:
-
实现HandlerInterceptor接口;
/** * 1、实现HandlerInterceptor接口 * 2、在SpringMVC配置文件中注册这个拦截器 */ @Controller public class MyFirstInterceptor implements HandlerInterceptor { /** * 目标方法运行之前运行 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyFirstInterecptor...preHandle..."); return true; } /** * 目标方法运行之后运行 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyFirstInterecptor...postHandle..."); } /** * 页面加载完之后运行 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyFirstInterecptor...afterCompletion..."); } }
-
配置拦截器
<!-- 配置拦截器 --> <mvc:interceptors> <!-- 配置某个拦截器,默认是拦截所有请求 --> <!-- <bean class="com.zb.controller.MyFirstInterceptor"></bean> --> <!-- 配置某个拦截器更详细的信息 --> <mvc:interceptor> <!-- 只拦截hello请求 --> <mvc:mapping path="/hello"/> <bean class="com.zb.controller.MyFirstInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
输入/hello的url地址执行结果为:
MyFirstInterecptor...preHandle... 控制器方法运行了 MyFirstInterecptor...postHandle... 来到success页面 MyFirstInterecptor...afterCompletion...
3.2 拦截器方法执行顺序
-
当个拦截器正常的执行顺序
正常运行流程:
拦截器的preHandle------目标方法-----拦截器postHandle-----页面-------拦截器的afterCompletion; -
多拦截器正常执行
执行结果:
MyFirstInterceptor...preHandle... MySecondInterceptor...preHandle... hello.... MySecondInterceptor...postHandle... MyFirstInterceptor...postHandle... success.jsp.... MySecondInterceptor...afterCompletion... MyFirstInterceptor...afterCompletion
-
多拦截器异常流程
执行结果:
MyFirstInterceptor...preHandle... MySecondInterceptor...preHandle... MyFirstInterceptor...afterCompletion
总结:
拦截器的preHandle:是按照顺序执行
拦截器的postHandle:是按照逆序执行
拦截器的afterCompletion:是按照逆序执行;
如果某块的拦截器return false,那他前面已经放行了的拦截器的afterCompletion总会执行
3.3 拦截器源码分析(待补)
ssm211和212
四、国际化
如果需要更深理解看ssm214-219
4.1 国际化简介
SpringMVC中区域信息是由LocaleResolver区域信息解析器得到的;
private LocaleResolver localeResolver;
localeResolver的实现类:
AcceptHeaderLocaleResolver
:根据 HTTP 请求头的 Accept-Language 参数确定本地化类型,如果没有显式定义本地化解析器, SpringMVC 使用该解析器。(默认地)CookieLocaleResolver
:根据指定的 Cookie 值确定本地化类型SessionLocaleResolve
r:根据 Session 中特定的属性确定本地化类型FixedLocaleResolver
:使用系统默认的区域信息
4.2 步骤
-
写好国际化资源文件
login_zh_CN.properties
welcomeinfo=\u6B22\u8FCE\u6765\u5230\u5C1A\u7845\u8C37 username=\u7528\u6237\u540D password=\u5BC6\u7801 login=\u767B\u5F55
login_en_US.properties
welcomeinfo=welcom to atguigu.com username=USERNAME password=PASSWORD login=LOGIN
-
让Spring的ResourceBundleMessageSource管理国际化资源文件
<!--让SpringMVC管理国际化资源文件;配置一个资源文件管理器;id是必须叫messageSource --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <!-- basename指定基础名,如果资源文件不在类路径下写:文件夹/login--> <!-- loginPage文件夹下基础名为login的配置文件 --> <property name="basename" value="loginPage/login"></property> </bean>
-
直接去页面取值(使用哪个国际化资源文件按照浏览器带来语言信息决定)
<body> <h1><fmt:message key="welcomeinfo"/></h1> <form> <fmt:message key="username"/>:<input /><br/> <fmt:message key="password"/>:<input /><br/> <input type="submit" value="<fmt:message key='login'/>"/> </form> </body>
4.2 点击链接切换国际化
-
使用自定义区域信息解析器实现
- 自定域区域信息解析器:(实现LocaleResolver 接口)
public class MyLocaleResolver implements LocaleResolver { /** * 解析返回locale */ @Override public Locale resolveLocale(HttpServletRequest request) { String localeStr = request.getParameter("locale"); System.out.println(localeStr); Locale l = null; //如果带了locale参数就用参数指定的区域信息,如果没带就用请求头的 if(localeStr != null && !"".equals(localeStr)){ l = new Locale(localeStr.split("_")[0],localeStr.split("_")[1]); }else{ l = request.getLocale(); } return l; } /** * 修改locale */ @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { throw new UnsupportedOperationException( "Cannot change HTTP accept header - use a different locale resolution strategy"); } }
- 在配置文件中注册自定义区域信息解析器:
<!-- 自定义区域信息解析器,id固定localeResolver --> <bean id="localeResolver" class="com.zb.controller.MyLocaleResolver"> </bean>
- login.jsp页面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1><fmt:message key="welcomeinfo"/></h1> <form> <fmt:message key="username"/>:<input /><br/> <fmt:message key="password"/>:<input /><br/> <input type="submit" value="<fmt:message key='login'/>"/> </form> <!-- 如果点击超链接切换国际化 --> <a href="toLoginPage?locale=zh_CN">中文</a>|<a href="toLoginPage?locale=en_US">English</a> </body> </html>
断点调试:
- 自定域区域信息解析器:(实现LocaleResolver 接口)
-
SessionLocaleResolver实现点击链接切换国际化
方法一:在请求到login.jsp页面的时把区域信息放到session中。SessionLocaleResolver会从session中获取区域信息
SessionLocaleResolver部分源码:
请求到login.jsp页面的时把区域信息放到session中:@RequestMapping("toLoginPage") public String toLoginPage(Locale locale,HttpSession >session,@RequestParam("locale")String localeStr){ Locale l = null; //如果带了locale参数就用参数指定的区域信息,如果没带就用请求头的 if(localeStr != null && !"".equals(localeStr)){ l = new Locale(localeStr.split("_")[0],localeStr.split("_")[1]); }else{ l = locale; } session.setAttribute(SessionLocaleResolver.class.getName() + ".LOCALE", l); return "login"; }
方法二:使用LocaleChangeInterceptor拦截器,从请求参数中获取本次请求对应的本地化类型
LocaleChangeInterceptor的preHandle方法(在目标方法执行之前运行):
使用:只需要配置SessionLocaleResolver区域信息解析器和LocaleChangeInterceptor拦截器即可。
注意:请求参数中需要含有name=locale的区域信息才有效。<!-- 配置SessionLocaleResolver区域信息解析器 --> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"> </bean> <!-- 配置LocaleChangeInterceptor拦截器 --> <mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean> </mvc:interceptors>
SessionLocaleResolver和LocaleChangeInterceptor的工作原理: