Spring MVC 异步调用、拦截器、异常处理
目录
异步调用
页面 Ajax.jsp:
<%@page pageEncoding="UTF-8" language="java" contentType="text/html;UTF-8" %>
<a href="javascript:void(0);" id="testAjax">访问springmvc后台controller</a><br/>
<a href="javascript:void(0);" id="testAjaxPojo">访问springmvc后台controller,传递Json格式POJO</a><br/>
<a href="javascript:void(0);" id="testAjaxList">访问springmvc后台controller,传递Json格式List</a><br/>
<a href="javascript:void(0);" id="testAjaxReturnString">访问springmvc后台controller,返回字符串数据</a><br/>
<a href="javascript:void(0);" id="testAjaxReturnJson">访问springmvc后台controller,返回Json数据</a><br/>
<a href="javascript:void(0);" id="testAjaxReturnJsonList">访问springmvc后台controller,返回Json数组数据</a><br/>
<br/>
<a href="javascript:void(0);" id="testCross">跨域访问</a><br/>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
$(function () {
//为id="testAjax"的组件绑定点击事件
$("#testAjax").click(function(){
//发送异步调用
$.ajax({
//请求方式:POST请求
type:"POST",
//请求的地址
url:"ajaxController",
//请求参数(也就是请求内容)
data:'ajax message',
//响应正文类型
dataType:"text",
//请求正文的MIME类型
contentType:"application/text",
});
});
//为id="testAjaxPojo"的组件绑定点击事件
$("#testAjaxPojo").click(function(){
$.ajax({
type:"POST",
url:"ajaxPojoToController",
data:'{"name":"Jock","age":39}',
dataType:"text",
contentType:"application/json",
});
});
//为id="testAjaxList"的组件绑定点击事件
$("#testAjaxList").click(function(){
$.ajax({
type:"POST",
url:"ajaxListToController",
data:'[{"name":"Jock","age":39},{"name":"Jockme","age":40}]',
dataType:"text",
contentType:"application/json",
});
});
//为id="testAjaxReturnString"的组件绑定点击事件
$("#testAjaxReturnString").click(function(){
//发送异步调用
$.ajax({
type:"POST",
url:"ajaxReturnString",
//回调函数
success:function(data){
//打印返回结果
alert(data);
}
});
});
//为id="testAjaxReturnJson"的组件绑定点击事件
$("#testAjaxReturnJson").click(function(){
//发送异步调用
$.ajax({
type:"POST",
url:"ajaxReturnJson",
//回调函数
success:function(data){
alert(data);
alert(data['name']+" , "+data['age']);
}
});
});
//为id="testAjaxReturnJsonList"的组件绑定点击事件
$("#testAjaxReturnJsonList").click(function(){
//发送异步调用
$.ajax({
type:"POST",
url:"ajaxReturnJsonList",
//回调函数
success:function(data){
alert(data);
alert(data.length);
alert(data[0]["name"]);
alert(data[1]["age"]);
}
});
});
//为id="testCross"的组件绑定点击事件
$("#testCross").click(function(){
//发送异步调用
$.ajax({
type:"POST",
url:"http://www.jock.com/cross",
//回调函数
success:function(data){
alert("跨域调用信息反馈:"+data['name']+" , "+data['age']);
}
});
});
});
</script>
异步请求
- 注解名称:
@RequestBody
- 类型:形参注解
- 位置:处理器类中的方法形参前方
- 作用:将异步提交数据组织成标准请求参数格式,并赋值给形参
// @RequestMapping("/ajaxController")
// public String ajaxController(){
// System.out.println("ajax request is running...");
// return "page.jsp";
// }
@RequestMapping("/ajaxController")
// 使用@RequestBody注解,可以将请求体内容封装到指定参数中
public String ajaxController(@RequestBody String message){
System.out.println("ajax request is running..."+message);
return "page.jsp";
}
@RequestMapping("/ajaxPojoToController")
// 如果处理参数是POJO,且页面发送的请求数据格式与POJO中的属性对应,@RequestBody注解可以自动映射对应请求数据到POJO中
// 注意:POJO中的属性如果请求数据中没有,属性值为null,POJO中没有的属性如果请求数据中有,不进行映射
public String ajaxPojoToController(@RequestBody User user){
System.out.println("controller pojo :"+user);
return "page.jsp";
}
@RequestMapping("/ajaxListToController")
// 如果处理参数是List集合且封装了POJO,且页面发送的数据是JSON格式的对象数组,数据将自动映射到集合参数中
public String ajaxListToController(@RequestBody List<User> userList){
System.out.println("controller list :"+userList);
return "page.jsp";
}
异步响应
// 使用注解@ResponseBody可以将返回的页面不进行解析,直接返回字符串,该注解可以添加到方法上方或返回值前面
@RequestMapping("/ajaxReturnString")
// @ResponseBody
public @ResponseBody String ajaxReturnString(){
System.out.println("controller return string ...");
return "page.jsp";
}
@RequestMapping("/ajaxReturnJson")
@ResponseBody
// 基于jackon技术,使用@ResponseBody注解可以将返回的POJO对象自动转成json格式数据
public User ajaxReturnJson(){
System.out.println("controller return json pojo...");
User user = new User();
user.setName("Jockme");
user.setAge(39);
return user;
}
@RequestMapping("/ajaxReturnJsonList")
@ResponseBody
// 基于jackon技术,使用@ResponseBody注解可以将返回的保存POJO对象的集合转成json数组格式数据
public List ajaxReturnJsonList(){
System.out.println("controller return json list...");
User user1 = new User();
user1.setName("Tom");
user1.setAge(3);
User user2 = new User();
user2.setName("Jerry");
user2.setAge(5);
ArrayList al = new ArrayList();
al.add(user1);
al.add(user2);
return al;
}
跨域访问
跨域访问介绍
- 当通过域名 A 下的操作访问域名 B 下的资源时,称为跨域访问(包括同个 IP 下的不同域名)。
- 跨域访问时,会出现无法访问的现象:
跨域环境搭建
- 为当前主机添加备用域名
- 修改 windows 安装目录中的 host 文件
- 格式:ip 域名
- 动态刷新 DNS
- 命令:ipconfig /displaydns
- 命令:ipconfig /flushdns
跨域访问支持
- 名称:
@CrossOrigin
- 类型:方法注解、类注解
- 位置:处理器类中的方法上方或类上方
- 作用:设置当前处理器方法/处理器类中所有方法支持跨域访问
@RequestMapping("/cross")
@ResponseBody
// 使用@CrossOrigin开启跨域访问
// 标注在处理器方法上方表示该方法支持跨域访问
// 标注在处理器类上方表示该处理器类中的所有处理器方法均支持跨域访问
@CrossOrigin
public User cross(HttpServletRequest request){
System.out.println("controller cross..."+request.getRequestURL());
User user = new User();
user.setName("Jockme");
user.setAge(39);
return user;
}
拦截器
简介
请求处理过程解析:
拦截器(Interceptor)
-
定义:是一种
动态拦截方法调用
的机制。 -
作用:
- 在指定的方法调用前后执行预先设定后的的代码。
- 阻止原始方法的执行。
-
核心原理:AOP 思想
-
拦截器链:多个拦截器按照一定的顺序,对原始被调用的功能进行增强
拦截器 VS 过滤器:
- 归属不同:Filter 属于 Servlet 技术;Interceptor属于 SpringMVC 技术。
- 拦截内容不同:Filter 对所有访问进行增强;Interceptor 仅针对 SpringMVC 的访问进行增强。
自定义拦截器的开发过程
1)编写 Controller:
@Controller
public class InterceptorController {
@RequestMapping("/handler")
public String handler() {
System.out.println("Handler running...");
return "page.jsp";
}
}
2)编写自定义拦截器(通知):
// 自定义拦截器需要实现 HandleInterceptor 接口
public class MyInterceptor implements HandlerInterceptor {
// 处理器运行之前执行
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("前置运行----a1");
// 返回值为 false 则会拦截原始处理器的运行
// 如果配置多拦截器,返回值为 false 将终止当前拦截器后面配置的拦截器的运行
return true;
}
// 处理器运行之后执行
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("后置运行----b1");
}
// 所有拦截器的后置执行全部结束后,执行该操作
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("完成运行----c1");
}
// 三个方法的运行顺序为:preHandle -> postHandle -> afterCompletion
// 如果 preHandle 返回值为 false,则三个方法仅运行 preHandle
}
3)配置拦截器(切入点):
<mvc:interceptors>
<mvc:interceptor>
<!-- 注意:配置顺序为先配置执行位置,后配置执行类 -->
<mvc:mapping path="/showPage"/> <!-- Controller上的路径 -->
<bean class="com.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
拦截器执行流程
拦截器配置与方法参数
1)前置处理方法
在原始方法之前运行。
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
- 参数:
- request:请求对象。
- response:响应对象。
- handler:被调用的处理器对象,本质上是一个方法对象,对反射中的 Method 对象进行了再包装。
- 返回值:
- 返回值为 false,则被拦截的处理器将不执行。
2)后置处理方法
在原始方法运行后运行,如果原始方法被拦截,则不执行。
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
- 参数 modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整。
3)完成处理方法
拦截器最后执行的方法,无论原始方法是否执行。
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion");
}
- 参数 ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理。
4)拦截器配置项
<mvc:interceptors>
<!-- 开启具体的拦截器的使用,可以配置多个 -->
<mvc:interceptor>
<!-- 设置拦截器的拦截路径,支持*通配 -->
<!-- /** 表示拦截所有映射 -->
<!-- /* 表示拦截所有/开头的映射 -->
<!-- /user/* 表示拦截所有/user/开头的映射 -->
<!-- /user/add* 表示拦截所有/user/开头,且具体映射名称以add开头的映射 -->
<!-- /user/*All 表示拦截所有/user/开头,且具体映射名称以All结尾的映射 -->
<mvc:mapping path="/*"/>
<mvc:mapping path="/**"/>
<mvc:mapping path="/handleRun*"/>
<!-- 设置拦截排除的路径,配置/**或/*,达到快速配置的目的 -->
<mvc:exclude-mapping path="/b*"/>
<!-- 指定具体的拦截器类 -->
<bean class="MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
多拦截器配置
责任链模式:是一种行为模式
- 特征:沿着一条预先设定的任务链顺序执行,每个节点具有独立的工作任务。
- 优势:
- 独立性:只关注当前节点的任务,对其他任务直接放行到下一节点。
- 隔离性:具备链式传递特征,无需知晓整体链路结构,只需等待请求到达后进行处理即可。
- 灵活性:可以任意修改链路结构动态新增或删减整体链路责任。
- 解耦:将动态任务与原始任务解耦。
- 弊端:
- 链路过长时,处理效率低下。
- 可能存在节点上的循环引用现象,造成死循环,导致系统崩溃。
异常处理
异常处理器
如下实现了 HandlerExceptionResolver 接口的自定义异常处理器后,SpringMVC 就能为框架中的每个异常进行拦截处理。
@Component
public class ExceptionResolver implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
System.out.println("异常处理器正在执行中");
ModelAndView modelAndView = new ModelAndView();
//定义异常现象出现后,反馈给用户查看的信息
modelAndView.addObject("msg","出错啦! ");
//定义异常现象出现后,反馈给用户查看的页面
modelAndView.setViewName("error.jsp");
return modelAndView;
}
}
根据异常的种类不同,进行分门别类的管理,返回不同的信息:
public class ExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
System.out.println("my exception is running ...."+ex);
ModelAndView modelAndView = new ModelAndView();
if( ex instanceof NullPointerException){
modelAndView.addObject("msg","空指针异常");
}else if ( ex instanceof ArithmeticException){
modelAndView.addObject("msg","算数运算异常");
}else{
modelAndView.addObject("msg","未知的异常");
}
modelAndView.setViewName("error.jsp");
return modelAndView;
}
}
注解开发异常处理器
使用注解实现异常分类管理:
- 名称:@ControllerAdvice
- 类型:类注解
- 位置:异常处理器类上方
- 作用:设置当前类为异常处理器类
- 范例:
@Component
@ControllerAdvice
public class ExceptionAdvice {
}
使用注解实现异常分类管理:
- 名称:@ExceptionHandler
- 类型:方法注解
- 位置:异常处理器类中针对指定异常进行处理的方法上方
- 作用:设置指定异常的处理方式
- 说明:处理器方法可以设定多个
- 范例:
@ExceptionHandler(Exception.class)
@ResponseBody
public String doOtherException(Exception ex){
return "出错啦,请联系管理员!";
}
异常处理解决方案
- 业务异常:
- 发送对应消息传递给用户,提醒规范操作
- 系统异常:
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给运维人员,提醒维护
- 记录日志
- 其他异常:
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护
- 纳入预期范围内
- 记录日志
自定义异常
异常定义格式:
// 自定义异常继承RuntimeException,覆盖父类所有的构造方法
public class BusinessException extends RuntimeException {
public BusinessException() {
}
public BusinessException(String message) {
super(message);
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public BusinessException(Throwable cause) {
super(cause);
}
public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
异常触发方式:
if(user.getName().trim().length()<4) {
throw new BusinessException("用户名长度必须在2-4位之间,请重新输入! ");
}
通过自定义异常将所有的异常现象进行分类管理,以统一的格式对外呈现异常消息。