目录
1、处理Json的过程
核心是转换器HttpMessageConverter<T>
1.1 HttpMessageConverter<T>接口
HttpMessageConverter<T> 是 Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象(类型为 T),将对象(类型为 T)输出为响应信息
同时,框架内部也为我们准备好了实现类(也允许我们添加第三方实现类,比如我们用json,就需要添加jackson jar包,最后就是json实现类)
使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的入 参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径(就是我们需要做的事情):
①注解法: 使用 @RequestBody / @ResponseBody 对处理方法进行标注 (普通的回显内容)
②入参法: 使用 HttpEntity<T> / ResponseEntity<T> 作为处理方法的入参或返回值 (一般用于文件下载效果)
当控制器处理方法使用到 @RequestBody/@ResponseBody或HttpEntity<T>/ResponseEntity<T> 时, Spring 首先根据请求头或响应头的Accept 属性选择匹配的 HttpMessageConverter, 进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的HttpMessageConverter 将报错
案例演示1:用ajax技术处理(页面不发生变化,需要通过Js向服务端发送请求)前后端传输Json对象( 使用 @RequestBody / @ResponseBody )
非常简单,只需要在方法上加入@ResponseBody注解
处理JSON具体步骤:
(1)加入jar包
(2)编写目标方法,使其返回JSON对应的对象或集合(不是java类型的,可以被js解析后显示给客户)
(3)在方法上添加@ResponseBody注解
回到主页面input.jsp,我们用jQuery来写ajax代码简洁的多。首先就要导入jquery和js,然后写好访问代码
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
$(function(){
$("#testJson").click(function(){
var url = this.href;
var args = {};
$.post(url, args, function(data){
for(var i = 0; i < data.length; i++){
var id = data[i].id;
var lastName = data[i].lastName;
alert(id + ": " + lastName);
}
});
return false;
});
})
</script>
</head>
<body>
<a href="emps">List All Employees</a>
<br><br>
<a href="testJson" id="testJson">Test Json</a>
<br><br>
</body>
进入服务端,springmvctest.java类 在处理json的方法上,添加一个@ResponseBody属性,就可以返回一个集合,获取employee的所有信息
package com.atguigu.springmvc.test;
@Controller
public class SpringMVCTest {
@Autowired
private EmployeeDao employeeDao;
@ResponseBody
@RequestMapping("/testJson")
public Collection<Employee> testJson(){
return employeeDao.getAll(); //employeeDao是类的私有属性,而且注入了内容,因此这里才可以直接用
}
}
这里有入参,用一个@RequestBody标注,可以告诉SpringMvc该用哪一种转换器处理
package com.atguigu.springmvc.test;
@Controller
public class SpringMVCTest {
@Autowired
private EmployeeDao employeeDao;
@ResponseBody
@RequestMapping("/testHttpMessageConverter")
public String testHttpMessageConverter(@RequestBody String body){
System.out.println(body);
return "helloworld! " + new Date();
}
}
案例演示2:下载效果(使用HttpEntity<T> / ResponseEntity<T>)
进入index.jsp
<a href="testResponseEntity">Test ResponseEntity</a>
然后进入springmvctest.java编写服务端
package com.atguigu.springmvc.test;
@Controller
public class SpringMVCTest {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping("/testResponseEntity")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException{
byte [] body = null;
ServletContext servletContext = session.getServletContext();
InputStream in = servletContext.getResourceAsStream("/files/abc.txt");
body = new byte[in.available()];
in.read(body);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=abc.txt");
HttpStatus statusCode = HttpStatus.OK;
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(body, headers, statusCode);
return response;
}
}
点击后,即可直接下载(前面是点击后,传数据到前端回显)
2、国际化(前面javaweb已经讲过了,可复习)
(1)关于国际化要求:
① 在页面上能够根据浏览器语言设置的情况对文本(不是内容),时间,数值进行本地化处理
② 可以在bean中获取国际化资源文件Locale对应的消息
③ 可以通过超链接切换Locale,而不再依赖于浏览器的语言设置情况
(2)SpringMvc的国际化
① 默认情况下,SpringMVC 根据 Accept-Language 参数判断客户端的本地化类型。当接受到请求时,SpringMVC 会在上下文中查找一个本地化解析器(LocalResolver),找到后使用它获取请求所对应的本地化类型信息。
② SpringMVC还允许装配一个动态更改本地化类型的拦截器,这样通过指定一个请求参数就可以控制单个请求的本地化类型。
(3)本地化解析器和本地化拦截器
① 工作原理原理概述
② 说明:
- AcceptHeaderLocaleResolver:根据 HTTP 请求头的Accept-Language 参数确定本地化类型,如果没有显式定义本地化解析器, SpringMVC 使用该解析器。
- CookieLocaleResolver:根据指定的 Cookie 值确定本地化类型
- SessionLocaleResolver:根据 Session 中特定的属性确定本地化类型
- LocaleChangeInterceptor:从请求参数中获取本次请求对应的本地化类型。
演示
主页面index.jsp中
<a href="i18n">I18N PAGE</a>
进入springmvc.xml配置文件。经过如下配置,就可以直接去i18n.jsp 以及 i18n2.jsp页面
//回顾上一节4.3:若希望直接响应通过 SpringMVC 渲染的页面(地址输入path后,直接到xxx/success.jsp),可以使用 mvc:view-controller 标签实现。
语法:<mvc:view-controller path="直接去的路径" view-name="视图名">
<!-- 配置国际化资源文件 -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
<!-- 配置 SessionLocalResolver -->
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
<mvc:interceptors>
<!-- 配置自定义的拦截器 -->
<bean class="com.atguigu.springmvc.interceptors.FirstInterceptor"></bean>
<!-- 配置拦截器(不)作用的路径 -->
<mvc:interceptor>
<mvc:mapping path="/emps"/>
<bean class="com.atguigu.springmvc.interceptors.SecondInterceptor"></bean>
</mvc:interceptor>
<!-- 配置 LocaleChanceInterceptor -->
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>
<!--
<mvc:view-controller path="/i18n" view-name="i18n"/>
-->
<mvc:view-controller path="/i18n2" view-name="i18n2"/>
装配进服务端的ResourceBundleMessageSource属性
package com.atguigu.springmvc.test;
@Controller
public class SpringMVCTest {
@Autowired
private ResourceBundleMessageSource messageSource;
}
views文件夹下,新建i18n.jsp 和 i18n2.jsp(使用JSTL的国际化标签)
<body>
<fmt:message key="i18n.user"></fmt:message>
<br><br>
<a href="i18n2">I18N2 PAGE</a>
<br><br>
<a href="i18n?locale=zh_CH">中文</a>
<br><br>
<a href="i18n?locale=en_US">英文</a>
</body>
i18n2.jsp
<body>
<fmt:message key="i18n.password"></fmt:message>
<br><br>
<a href="i18n">I18N PAGE</a>
</body>
编写i18n_en_US配置文件
i18n.user=User
i18n.password=Password
编写i18n_zh_CN配置文件
i18n.user=\u7528\u6237\u540D
i18n.password=\u5BC6\u7801\\和上一节一样,中文会被系统自动编码
点击中文/英文, 再点I18N PAGE进去,里面的页面,就显示对应的中文/英文
3、文件上传
SpringMVC为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用Jakarta Commons FileUpload 技术实现了一个MultipartResolver 实现类:CommonsMultipartResovler
Spring MVC上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver(同样需要先导入jar包)
(1)springmvc.xml中配置MultipartResolver
<!-- 配置 MultipartResolver -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="1024000"></property>
</bean>
(2)index.jsp 中写testFileUpoad表单
<body>
<form action="testFileUpload" method="POST" enctype="multipart/form-data">
File: <input type="file" name="file"/>
Desc: <input type="text" name="desc"/>
<input type="submit" value="Submit"/>
</form>
</body>
(3)在springmvctest中写目标方法/testFileUpload
复习:@RequestParam注解,把请求参数传递过来
package com.atguigu.springmvc.test;
@Controller
public class SpringMVCTest {
@RequestMapping("/testFileUpload")
public String testFileUpload(@RequestParam("desc") String desc,
@RequestParam("file") MultipartFile file) throws IOException{
System.out.println("desc: " + desc);
System.out.println("OriginalFilename: " + file.getOriginalFilename());
System.out.println("InputStream: " + file.getInputStream());
return "success";
}
}
(4)再加一个Success.jsp页面用于回显
<body>
<h4>Success Page</h4>
</body>
4、拦截器
4.1 自定义拦截器
Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口
4.2 拦截器方法执行顺序:
(1)preHandle():这个方法在业务处理器处理请求(handler方法)之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
说明:一般用于做权限、日志、事务等
(2)postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用(在渲染视图之前调用),在该方法中对
用户请求request进行处理。
说明:一般用于对请求域中的属性或视图做出修改.
(3)afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用(渲染视图之后),可以在该方法中进行一些资源清理的操作。
说明:一般用于释放资源
4.3 多个拦截器执行顺序
(1)正常执行顺序
(2)第二个拦截器返回false
案例演示:
新建一个包com.atguigu.springmvc.interceptors 在下面写拦截器 FirstInterceptor.java类
package com.atguigu.springmvc.interceptors;
public class FirstInterceptor implements HandlerInterceptor{
/**
* 该方法在目标方法之前被调用.
* 若返回值为 true, 则继续调用后续的拦截器和目标方法.
* 若返回值为 false, 则不会再调用后续的拦截器和目标方法.
*
* 可以考虑做权限. 日志, 事务等.
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("[FirstInterceptor] preHandle");
return true;
}
/**
* 调用目标方法之后, 但渲染视图之前.
* 可以对请求域中的属性或视图做出修改.
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[FirstInterceptor] postHandle");
}
/**
* 渲染视图之后被调用. 释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[FirstInterceptor] afterCompletion");
}
}
再编写第二个拦截器SecondInterceptor.java
package com.atguigu.springmvc.interceptors;
public class SecondInterceptor implements HandlerInterceptor{
/**
* 该方法在目标方法之前被调用.
* 若返回值为 true, 则继续调用后续的拦截器和目标方法.
* 若返回值为 false, 则不会再调用后续的拦截器和目标方法.
*
* 可以考虑做权限. 日志, 事务等.
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("[SecondInterceptor] preHandle");
return false;
}
/**
* 调用目标方法之后, 但渲染视图之前.
* 可以对请求域中的属性或视图做出修改.
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[SecondInterceptor] postHandle");
}
/**
* 渲染视图之后被调用. 释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[SecondInterceptor] afterCompletion");
}
}
springmvc.xml中,写了拦截器以后,自然要配置自定义拦截器
<mvc:interceptors>
<!-- 配置自定义的拦截器 -->
<bean class="com.atguigu.springmvc.interceptors.FirstInterceptor"></bean>
<!-- 配置拦截器起作用的路径 -->
<!--配置不起作用的路径标签为mvc:exclude-mapping path= -->
<mvc:interceptor>
<mvc:mapping path="/emps"/>
<bean class="com.atguigu.springmvc.interceptors.SecondInterceptor"></bean>
</mvc:interceptor>
<!-- 配置 LocaleChanceInterceptor -->
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>
说明:<mvc:exclude-mapping path="">这个标签可以用于配置不拦截的路径,案例没演示
第一个拦截器全部拦截,第二个拦截器只拦截/emps
5、异常处理
5.1 异常概述
Spring MVC 通过 HandlerExceptionResolver 接口的实现类处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。
5.2 HandlerExceptionResolver接口
SpringMVC 提供了 HandlerExceptionResolver 接口的实现类
DispatcherServlet 默认装配的 HandlerExceptionResolver是(使用了<mvc:annotation-driven/>标签的情况下)
5.2.1 实现类ExceptionHandlerExceptionResolver(最常用)的用法
(1)主要用于处理 Handler 中用 @ExceptionHandler 注解定义的方法。@ExceptionHandler 注解定义的方法优先级问题:例如发生的NullPointerException,但是声明的异常有RuntimeException 和 Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler注解方法,即标记了 RuntimeException 的方法
(2)ExceptionHandlerMethodResolver 内部若找不到@ExceptionHandler注解的话,会找@ControllerAdvice 中的@ExceptionHandler 注解方法
(3)使用注意事项
① 在 @ExceptionHandler 方法的入参中可以加入 Exception 类型的参数, 该参数即对应发生的异常对象
② @ExceptionHandler 方法的入参中不能传入 Map. 若希望把异常信息传导页面上, 需要使用 ModelAndView 作为返回值
③ @ExceptionHandler 方法标记的异常有优先级的问题.
④ @ControllerAdvice: 如果在当前 Handler 中找不到 @ExceptionHandler 方法来出来当前方法出现的异常,
⑤ 则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常.
Exp:异常处理演示
进入springmvctest.java类,写testExeptionHandlerExceptionResolver()方法和 RuntimeException 运行时异常方法
package com.atguigu.springmvc.test;
//i=0 就产生异常
@RequestMapping("/testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){
System.out.println("result: " + (10 / i));
return "success";
}
}
去index.jsp加入超链接 herf=testExeptionHandlerExceptionResolver,写一个?i,即可将i映射到服务端去,用i=0来产生异常
<br><br>
<a href="testExceptionHandlerExceptionResolver?i=0">Test ExceptionHandlerExceptionResolver</a>
继续去springmvctest.java类,写异常处理方法@Exception
package com.atguigu.springmvc.test;
@Controller
public class SpringMVCTest {
@ExceptionHandler({RuntimeException.class})
public ModelAndView handleArithmeticException2(Exception ex){ //异常被封入这个ex
System.out.println("[出异常了]: " + ex);
ModelAndView mv = new ModelAndView("error"); //把异常信息传入域放,方便回显
mv.addObject("exception", ex);
return mv;
}
//这个更具体,因此优先级更高
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
System.out.println("出异常了: " + ex);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}
//i=0 就产生异常
@RequestMapping("/testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){
System.out.println("result: " + (10 / i));
return "success";
}
}
再去view里面新建一个error页面
<body>
<h4>Error Page</h4>
${requestScope.exception }
</body>
在com.atguigu.springmvc.test包下再新建一个ExceptionHandler类。
这里演示如果ExceptionHandlerMethodResolver 内部若找不到@ExceptionHandler注解的话(前面的springmvctest类里面的处理方法都注销掉的话),就会找这个@ControllerAdvice 中的@ExceptionHandler 注解方法
package com.atguigu.springmvc.test;
@ControllerAdvice
public class SpringMVCTestExceptionHandler {
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
System.out.println("----> 出异常了: " + ex);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}
}
5.2.2 ResponseStatusExceptionResolver实现类
(1)使用方法:使用ResponseStatusExceptionResolver实现类处理异常即使用 @ResponseStatus注解
定义一个 @ResponseStatus 注解修饰的异常类
若在处理器方法中抛出了上述异常:
由于触发的异常 UnauthorizedException 带有@ResponseStatus注解。因此会被ResponseStatusExceptionResolver 解析(而不是ExceptionHandlerExceptionResolver 解析述异常),最后响应HttpStatus.UNAUTHORIZED 代码给客户端。HttpStatus.UNAUTHORIZED 代表响应码401,无权限。
关于其他的响应码请参考 HttpStatus 枚举类型源码。
说明:
① 该注解可以用于类,也可以用于方法
② 这种处理方式会直接返回错误页面,页面上显示异常的值比如 404 错误,用户名和密码不匹配。而不是返回到自己编写的某个页面
(2)ResponseStatusExceptionResolver实现类处理异常演示
在包com.atguigu.springmvc.test下再写一个UserNameNotMatchPassordException类( @ResponseStatus 注解用于类)
package com.atguigu.springmvc.test;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!")
public class UserNameNotMatchPasswordException extends RuntimeException{
private static final long serialVersionUID = 1L;
}
回到springmvctest 编写一个testResponseStatusExceptionResolver方法 (或者 @ResponseStatus 注解用于方法)
package com.atguigu.springmvc.test;
@Controller
public class SpringMVCTest {
@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND)
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i){
if(i == 13){ //如果i为13,抛异常,否则,就正常执行
throw new UserNameNotMatchPasswordException();
}
System.out.println("testResponseStatusExceptionResolver...");
return "success";
}
}
回到首页 index.jsp 专门写i=13,好让服务端抛异常,方法上标记了 @ResponseStatus,不管i=任何值,都会抛异常
<br><br>
<a href="testResponseStatusExceptionResolver?i=13">Test ResponseStatusExceptionResolver</a>
异常界面
5.2.3 DefaultHandlerExceptionResolver实现类处理异常
该实现类自动对一些特殊的异常进行处理,比 •如NoSuchRequestHandlingMethodException、HttpReques
tMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException等。
该异常是‘’自动处理的,程序员什么都不用做,不用添加任何注解。程序完成后自动返回如下异常界面
案例演示:
springmvctest.java 下写testDefaultHandlerExceptionResolver。服务端要求前端传post请求过来,然而前端传get请求,就会自动抛异常并处理
package com.atguigu.springmvc.test;
@Controller
public class SpringMVCTest {
@RequestMapping(value="/testDefaultHandlerExceptionResolver",method=RequestMethod.POST)
public String testDefaultHandlerExceptionResolver(){
System.out.println("testDefaultHandlerExceptionResolver...");
return "success";
}
}
index.jsp中,给出超链接,专门传get请求
<br><br>
<a href="testDefaultHandlerExceptionResolver">Test DefaultHandlerExceptionResolver</a>
5.2.3 SimpleMappingExceptionResolver实现类
如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
说明:就是让程序员去配置文件配置一个视图,发生异常就去那个视图
index.jsp中写SimpleMappingExceptionResolver解析器
SimpleMappingExceptionResolver实现类演示
springmvctest类中写一个目标方法,如果前端传来的i>10就会发生数组下标越界异常
package com.atguigu.springmvc.test;
@Controller
public class SpringMVCTest {
@RequestMapping("/testSimpleMappingExceptionResolver")
public String testSimpleMappingExceptionResolver(@RequestParam("i") int i){
String [] vals = new String[10];
System.out.println(vals[i]);
return "success";
}
}
配置发生异常后要去的页面:
由于源码中,这个exceptionMappings属性(异常映射)传入的是properties类型的值,因此用props标签。 key是异常的全类名,值就是要传到的页面
将异常信息传入ex,之后可以在前端页面打印该异常。如果不配置exceptionAttribute,exceptionAttribute的默认值是exception
<beans>
<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionAttribute" value="ex"></property>
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
</beans>
springmvc.xml中配置出异常的时候返回哪个界面
最后去error页面,显示异常
<body>
<h4>Error Page</h4>
${requestScope.ex}
</body>