Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验

目录

一、Ajax ★

1. 基本类型参数传递  @ResponseBody响应体

2. Ajax传递实体类

3.Ajax传递实体类带级联属性 (非json 普通参数) @DateTimeFormat

4.Ajax传递实体类(JSON格式)带级联属性

5.Ajax返回实体类

6.为什么要加@RequestBody (@RequestBody、@ResponseBody 区别)

二、拦截器 Interceptor ★

1. 区分拦截器(Interceptor) 和 过滤器(filter)

2. 创建拦截器

3. 配置拦截器

三、类型转换

1. 内置类型转换器(自动转换)

2. 手动转换器

四、类型校验  @Validated

五、异常映射

1.XML方式

2. 注解方式  @ExceptionHandler

3.异常映射-区分请求类型(Ajax和非Ajax)


一、Ajax ★

1. 基本类型参数传递  @ResponseBody响应体

@responseBody 

1.引入vue

<script th:src="@{/js/vue.js}"></script>
<script th:src="@{/js/axios.min.js}"></script>

2.参数传递

<h3>Ajax请求1:发送基本类型的数据</h3>
<a href="javascript:void(0)" @click="ajaxDemo1">Ajax请求1</a>

3.Vue代码

<script>
    new Vue({
        el:"#app",
        data:{
        },
        methods:{
            ajaxDemo1:function (){
                axios({     //Ajax请求
                    method:"post",  //请求类型
                    url:"[[@{/ajax/ajaxDemo1}]]",  //请求地址
                    params:{    //携带参数
                        sid:1,
                        sname:"张三",
                        score:90.5
                    }
                }).then(function (result){  //获取响应数据
                    alert(result.data)  //ok

                }).catch(function (){   //失败时执行

                }).finally()
            }
        }
    })
</script>

4.controller控制层

@Controller
@Slf4j
@RequestMapping("/ajax")
public class AjaxController {
    @ResponseBody   //将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。
    @RequestMapping("/ajaxDemo1")
    public String ajaxDemo1(Integer sid,String sname,Double score){
        log.info("ajaxDemo1:"+sid+"  "+sname+"  "+score);

        return "ok";    //将OK放入响应体直接返回
    }
}

关于 @responseBody 

@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。

这个注解表示该方法的返回结果直接写入HTTP response body中,一般在异步获取数据时使用。

在使用@RequestMapping后,返回值通常解析为跳转路径。加上@responsebody后,返回结果直接写入HTTP response body中,不会被解析为跳转路径。比如异步请求,希望响应的结果是json数据,那么加上@responsebody后,就会直接返回json数据。

2. Ajax传递实体类

@ResponseBody响应体(Ajax请求),返回的数据直接放入响应体,而不是转发和重定向
声明在类中表示所有方法都是Ajax请求声明在方法上只针对该方法

@RestController: 合并了@ResponseBody 和 @Controller

        <h3>Ajax请求2:发送实体类型数据</h3>
        <a href="javascript:void(0)" @click="ajaxDemo2">Ajax请求2</a>
        ajaxDemo2:function (){
                axios({
                    method:"post",
                    url:"[[@{/ajax/ajaxDemo2}]]",
                    params:{
                        empName:"张三",
                        empAge:10,
                        empSalary:1000
                    }
                }).then(function (result){
                    alert(result.data)  //ok

                }).catch(function (error){

                }).finally()
            },
@RestController //合并了以下两个注解
//@ResponseBody //当前Controller中所有方法都是Ajax请求,不是转发和重定向
//@Controller

@Slf4j
@RequestMapping("/ajax")
public class AjaxController {

    //@ResponseBody     //提取到类中
    @RequestMapping("/ajaxDemo2")
    public String ajaxDemo2(Employee emp){
        log.info("ajaxDemo2:"+emp);

        return "ok";    //将OK放入响应体直接返回
    }

3.Ajax传递实体类带级联属性 (非json 普通参数) @DateTimeFormat

@DateTimeFormat("yyyy-MM-dd") 普通参数日期格式化,指定日志格式

        <h3>Ajax请求3-1:发送实体类型数据带级联属性(params:非JSON)</h3>
        <a href="javascript:void(0)" @click="ajaxDemo31">Ajax请求3-1</a>
            ajaxDemo31:function (){
                axios({     //Ajax请求
                    method:"post",  //请求类型
                    url:"[[@{/ajax/ajaxDemo31}]]",
                    params:{    //携带参数
                        empName:"张三",
                        empAge:10,
                        empSalary:1000,
                        //级联、日期参数
                        hireDate:"1999-12-23",
                        //级联属性
                        "dept.depton":10,
                        "dept.dname":"教学部"
                    }
                }).then(function (result){  //获取响应数据
                    alert(result.data)  //ok
                }).catch(function (){   //失败时执行
                }).finally()
            },
    @RequestMapping("/ajaxDemo31")
    public String ajaxDemo31(Employee emp){
        log.info("ajaxDemo31:"+emp);

        return "ok";    //将OK放入响应体直接返回
    }

 实体类:(指定日期格式)

注意命名规范,不要第二个字母大写,会影响生成的get、set方法,进而影响spring容器获取、注入

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

    private Integer empId;
    private String empName;
    private int empAge;
    private double empSalary;

    @DateTimeFormat(pattern = "yyyy-MM-dd") //普通参数,指定日志格式
    private Date hireDate;//入职时间

    private Dept dept;//级联属性
}

public class Dept {
    private Integer depton;
    private String dname;
}

4.Ajax传递实体类(JSON格式)带级联属性

注意

  1. 必须添加jackson json组件(幕后英雄)
  2. 分控制器方法中接收数据必须使用 @RequestBody
  3. 前端页面中使用Ajax传递数据使用data属性而不是params
  4. 前端页面中传递数据要使用json格式
  5. 日期如果不是yyyy-MM-dd格式,需要在实体类进行@JsonFormat()指定格式

需要导入Jackson依赖,SpringMVC支持会自动调用(将对象或集合转换为JSON字符串)

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>
        <h3>Ajax请求3-2:发送实体类型数据带级联属性(data:JSON)</h3>
        <a href="javascript:void(0)" @click="ajaxDemo32">Ajax请求3-1</a>
            ajaxDemo32:function (){
                axios({
                    method:"post",
                    url:"[[@{/ajax/ajaxDemo32}]]",
                    data:{    //注意,json数据需要改为data
                        empName:"张三",
                        empAge:10,
                        empSalary:1000,
                        //日期
                        hireDate:"1999/12/23",  //默认为-拼接,所以服务端需要格式转换
                        //Json格式级联属性
                        dept:{
                            depton:10,
                            dname:"教学部",
                        },
                        /*"dept.depton":10,
                        "dept.dname":"教学部"*/
                    }
                }).then(function (result){  //获取响应数据
                    alert(result.data)  //ok
                }).catch(function (){   //失败时执行
                }).finally()
            },
    //json数据没有data格式问题
    //json数据,级联属性
    @RequestMapping("/ajaxDemo32")//从请求体中获取 RequestBody请求体  ResponseBody响应体
    public String ajaxDemo32(@RequestBody Employee emp){
        log.info("ajaxDemo33:"+emp);

        return "ok";    //将OK放入响应体直接返回
    }

实体类:(指定json日期格式)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    private Integer empId;
    private String empName;
    private int empAge;
    private double empSalary;

    @JsonFormat(pattern = "yyyy/MM/dd") //Json格式日期格式化 (timezone = 时区待修改)
    //@DateTimeFormat(pattern = "yyyy-MM-dd") //指定日志格式
    private Date hireDate;//入职时间

    private Dept dept;
}

注意点: 

如果不是yyyy-MM-dd,需要进行类型转换@JsonFormat()指定格式,否则报错

用data传:放到请求体中@RequestBody,用param传,拼接在url后面

5.Ajax返回实体类

1.前端页面接收:

        <h3>Ajax请求4:返回响应实体类,JSON格式</h3>
        <a href="javascript:void(0)" @click="ajaxDemo4">Ajax请求4</a>
            ajaxDemo4:function (){
                axios({
                    method:"post",
                    url:"[[@{/ajax/ajaxDemo4}]]",
                    //没有携带数据
                }).then(function (result){  //获取响应数据
                    console.log(result);
                    alert(result.data)

                }).catch(function (){   //失败时执行

                }).finally()
            }

 2,.后端页面发送

    //Ajax返回实体类 (背后率不开Jackson组件)
    @RequestMapping("/ajaxDemo4")//从请求体中获取 RequestBody请求体  ResponseBody响应体
    public Employee ajaxDemo4(){
        Employee employee = new Employee();
        employee.setEmpName("name");
        employee.setEmpAge(10);
        employee.setEmpSalary(1000.1);
        //当前时间
        employee.setHireDate(new Date());
        //级联对象
        employee.setDept(new Dept(10,"教研部"));

        return employee;    //返回emp对象
    }

注意:

@JsonFormat(pattern = "yyyy/MM/dd")也会起作用

 背后离不开Jackson组件的作用(将对象或集合转换为JSON字符串) 

6.为什么要加@RequestBody (@RequestBody、@ResponseBody 区别)

@RequestBody     接收客户端请求时,从请求体而不是请求头、URL中获取数据

@ResponseBody  给客户端响应时,不是转发和重定向的跳转,而是将数据直接放入响应体

注意:@RestController 底层封装了 ResponseBody 和 RestController

发送Ajax请求时,使用data,将数据放入请求体,服务器的分控制器就从请求体中拿数据

发送Ajax请求时,如果使用params,将数据写到url的后面,
如:
ajax/ajaxDemo31?empId=xx&ename=xx&...,不管是get还是post

二、拦截器 Interceptor ★

Interceptor  和过滤器非常的相似,解决的问题类似。
功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器

     

1. 区分拦截器(Interceptor) 和 过滤器(filter)

[1]三要素相同

  • 拦住:必须先把请求拦住,才能执行后续操作
  • 过滤:拦截器或过滤器存在的意义就是对请求进行统一处理
  • 放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源;也不能不放行,比如安全验证中没有登录。

[2]不同点

  • 工作平台不同
    • 过滤器工作在 Servlet 容器中

    • 拦截器工作在 SpringMVC 的基础上

  • 拦截的范围

    • 过滤器:能够拦截到的最大范围是整个 Web 应用

    • 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求(看DispatcherServlet的<url-pattern>的设置是/还是*.action)

  • IOC 容器支持

    • 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的

    • 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持

2. 创建拦截器

如何创建拦截器

方法1:实现 HandlerInterceptor 接口 推荐使用该方式

方法2:继承HandlerInterceptorAdapter  JDK8后该方式已经过时。因为接口中已经给出了空实现

public class MyInterceptor1 implements HandlerInterceptor {

     /*方法执行顺序
         preHandle() *
         访问目标方法
         postHandle() *
         解析视图  result----/WEB-INF/templates/result.html
         渲染视图  <p th:text="${msg}"></p> ----------<p>username cannot be null</p>
         afterCompletion() *
         给出用户响应
     */

    /**
     * 在处理请求的目标 handler 方法前执行
     * @param request  请求
     * @param response  响应
     * @param handler  可以认为就是目标资源,但是目标资源有多种类型,所以定义为Object
         * 1.分控制器的方法: EmployeeController findAll()
         * 2.静态页面:main.html
         * 3.视图控制器:<mvc:view-controller path="/" view-name="portal"></mvc:view-controller>
     * @return true:放行  false,不放行  但是会直接走afterCompletion()
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1 preHandle() ");
        return false;
    }

    /**
     * 在目标 handler 方法之后,渲染视图之前
     * @param request
     * @param response
     * @param handler
     * @param modelAndView 访问分控制器的返回结果都是一个ModelAndView
     *                      如果返回了一个String (return "result"),SpringMVC也会进步的转换为ModelAndView
     * @throws Exception
     */

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("------MyInterceptor1 postHandle------");
    }

    /**
     * 渲染视图之后执行
     * @param request
     * @param response
     * @param handler
     * @param ex  产生的异常
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("------MyInterceptor1 afterCompletion------");
    }
}

3. 配置拦截器

<mvc:interceptors>
    <!--配置全局拦截器,拦截经过总控制器的所有资源 -->
    <bean class="com.atguigu.interceptor.MyInterceptor1"></bean>
    <!-- 配置局部拦截器,拦截指定的资源-->
    <mvc:interceptor>
        <mvc:mapping path="/employee/save*"/>
        <bean class="com.atguigu.interceptor.MyInterceptor2"></bean>
    </mvc:interceptor>
</mvc:interceptors>

一个拦截器的各个方法执行顺序:

配置的顺序决定拦截器执行顺序,和全局局部无关

4. 拦截器应用

① preHandle()方法

例如:解决中文乱码问题,登录验证,页面跳转(系统维护中)等

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    log.debug("----------HelloInterceptor preHandle----------");
    //return true:放行
    //作用1:解决中文乱码问题
    //request.setCharacterEncoding("utf-8");
    //作用2:判断用户是否登录
    //2.1 放行登录页面
    if(request.getRequestURI().contains("login.jsp")){
        return true;
    }
    //2.2 放行登录Controller
    if(request.getRequestURI().contains("login.action")){
        return true;
    }
    //2.3 如果没有登录,重定向到登录页面
    Object user = request.getSession().getAttribute("user");
    if(user==null){
        response.sendRedirect(request.getContextPath()+"/login.jsp");
        return false;
    }
    //2.4 如果已经登录,放行
    return true;
    //作用3:页面跳转(网站维护中....)
    //response.sendRedirect(request.getContextPath()+"/maintain.jsp");
    //return false;
​
}

②postHandle()方法

例如:网站升级测试时,跳转到新版测试页面;进行敏感字符替换

public void postHandle(HttpServletRequest request, HttpServletResponse response,
                       Object handler, ModelAndView modelAndView) throws Exception {
    log.debug("----------HelloInterceptor postHandle----------");
​
    //作用1:网站升级时,再次修改跳转路径,跳转到测试的页面
    //modelAndView.setViewName("redirect:/index2.jsp");
    //作用2:敏感字符替换
    String obj = (String)modelAndView.getModel().get("error");
    if(obj.contains("zhangsan")){//枪支、黄色、反动
        obj = obj.replace("zhangsan","**");
        modelAndView.addObject("error",obj);
    }
}

③afterCompletion()方法

例如:完成资源关闭,异常处理等操作

三、类型转换

1. 内置类型转换器(自动转换)

SpringMVC 将『把请求参数注入到 POJO 对象』这个操作称为『数据绑定』,英文单词是 binding。数据类型的转换和格式化就发生在数据绑定的过程中。

@NumberFormat:  针对数值

@DateTimeFormat:  针对日期

转换失败后处理方式:在传参中加入 BindingResult

BindingResult 接口和它的父接口 Errors 中定义了很多和数据绑定相关的方法,如果在数据绑定过程中发生了错误,那么通过这个接口类型的对象就可以获取到相关错误信息。

注意:在实体类参数和 BindingResult 之间不能有任何其他参数

①实体类 

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

    private String empName;
    //整数
    @NumberFormat(style = NumberFormat.Style.NUMBER,pattern = "#,###")
    private int empAge;
    //货币
    @NumberFormat(style = NumberFormat.Style.CURRENCY,pattern = "##,###,##")
    private double empSalary;
    //百分比
    @NumberFormat(style = NumberFormat.Style.PERCENT)
    private Double level;    //百分比
    //日期
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date hireDate;
}

②表单

    <h3>自动类型转换</h3>
    <form th:action="@{/employee/addEmp}" method="post">
        名称:<input type="text" name="empName"><br>
        年龄:<input type="text" name="empAge"><br>
        金额:<input type="text"  name="empSalary"><br>
        级别:<input type="text"  name="level"><br>

        日期:<input type="text"  name="hireDate"><br>
    </form>

③分控制器 

@Controller
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {

    @RequestMapping("/addEmp")
    public String addEmp(Employee emp,BindingResult bindingResult,
                         Model model){  //注意参数顺序,bindingResult在前面
        //如果数据绑定中,格式有错,转发错误页面
        boolean flag = bindingResult.hasErrors();
        if(flag){
            return "error";
        }

        //格式正确,跳转成功页面
        log.info("employee"+emp);
        model.addAttribute("emp",emp);
        return "result";
    }

}

失败页面:

<body>
    <h3>失败页面</h3>
    <p th:errors="${employee.empName}"></p><!--通过类名获取数据-->
    <p th:errors="${employee.empAge}"></p>
    <p th:errors="${employee.empSalary}"></p>
    <p th:errors="${employee.level}"></p>
    <p th:errors="${employee.hireDate}"></p>
    <!-- ${注入请求参数的实体类.出问题的字段} -->
</body>

 成功页面:

<body>
    <h3>结果页面</h3>
    <p th:text="${emp.empName}"></p>
    <p th:text="${emp.empAge}"></p>
    <p th:text="${emp.empSalary}"></p>
    <p th:text="${emp.level}"></p>
    <p th:text="${emp.hireDate}"></p>
</body>

2. 手动转换器

在实际开发过程中,难免会有某些情况需要使用自定义类型转换器。因为我们自己自定义的类型在 SpringMVC 中没有对应的内置类型转换器。此时需要我们提供自定义类型来执行转换。

①新增日期类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    private String province;
    private String city;
    private String county;
}

public class Employee {
    ...

    //手动类型转换
    private Address address;

②页面表单

    <h3>手动类型转换</h3>
    <form th:action="@{/employee/addEmp}" method="post">
        名称:<input type="text" name="empName"><br>
        年龄:<input type="text" name="empAge"><br>
        金额:<input type="text"  name="empSalary"><br>
        级别:<input type="text"  name="level"><br>

        日期:<input type="text"  name="hireDate"><br>

        地址:<input type="text"  name="address" value="河北省,张家口市,崇礼县"><br>
        <input type="submit">
    </form>

③springmvc.xml 中注册

<!--注册手动转换器-->
    <bean id="serviceFactoryBean"
          class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <!-- 在 converters 属性中指定自定义类型转换器 -->
        <property name="converters">
            <set>
                <bean class="com.atguigu.converter.AddressTypeConverter"></bean>
            </set>
        </property>
    </bean>

四、类型校验  @Validated

JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。

注解规则
@Null标注值必须为 null
@NotNull标注值不可为 null
@AssertTrue标注值必须为 true
@AssertFalse标注值必须为 false
@Min(value)标注值必须大于或等于 value
@Max(value)标注值必须小于或等于 value
@DecimalMin(value)标注值必须大于或等于 value
@DecimalMax(value)标注值必须小于或等于 value
@Size(max,min)标注值大小必须在 max 和 min 限定的范围内
@Digits(integer,fratction)标注值值必须是一个数字,且必须在可接受的范围内
@Past标注值只能用于日期型,且必须是过去的日期
@Future标注值只能用于日期型,且必须是将来的日期
@Pattern(value)标注值必须符合指定的正则表达式

JSR 303 只是一套标准,需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解

注解规则
@Email标注值必须是格式正确的 Email 地址
@Length标注值字符串大小必须在指定的范围内
@NotEmpty标注值字符串不能是空字符串
@Range标注值必须在指定的范围内

Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。

配置 mvc:annotation-driven 后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。

①添加依赖


<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>6.2.0.Final</version>
</dependency>

②使用校验规则  @Size  @Email

public class Employee {

    //数据校验
    @Size(min = 5,max = 15) //最小5,最大15
    @Email//邮箱格式
    private String email;

③在handler 方法形参标记注解  @Validated

@RequestMapping("/employee")
public class EmployeeController {

    @RequestMapping("/addEmp")//Validated:数据校验
    public String addEmp(@Validated Employee emp, BindingResult bindingResult,
                         Model model){  //注意参数顺序,bindingResult在前面
        //如果格式有错,转发错误页面
        boolean flag = bindingResult.hasErrors();
        if(flag){
            return "error";
        }

        //格式正确,跳转成功页面
        log.info("employee"+emp);
        model.addAttribute("emp",emp);
        return "result";
    }

}

五、异常映射

将异常类型和某个具体的视图关联起来,建立映射关系。好处是可以通过 SpringMVC 框架来帮助我们管理异常。可以指定不同异常要进行的

  • 声明式管理异常:在配置文件中指定异常类型和视图之间的对应关系。配置文件注解类中统一管理
  • 编程式管理异常:需要我们自己手动 try ... catch ... 捕获异常,然后再手动跳转到某个页面。

异常映射的好处

  • 使用声明式代替编程式来实现异常管理

    • 让异常控制和核心业务解耦,二者各自维护,结构性更好
  • 整个项目层面使用同一套规则来管理异常

    • 整个项目代码风格更加统一、简洁
    • 便于团队成员之间的彼此协作

1.XML方式

①springmvc.xml 中配置异常映射

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <!-- 配置异常映射关系 -->
    <property name="exceptionMappings">
        <props>
            <!-- key属性:指定异常类型 -->
            <!-- 文本标签体:和异常类型对应的逻辑视图 -->
            <prop key="java.lang.NullPointerException">exp-null</prop>
            <prop key="java.io.FileNotFoundException">exp-notfound</prop>
            <prop key="java.lang.RuntimeException">exp-run</prop>
            <prop key="java.lang.Exception">exp</prop>
        </props>
    </property>
    <!-- 使用 exceptionAttribute 属性配置将异常对象存入请求域时使用的属性名 -->
    <!-- 这个属性名默认是exception -->
    <property name="exceptionAttribute" value="atguiguException"></property>
</bean>

②创建相应的异常结果页面

 ③创建发送异常的分控制器并测试

@Controller
@Slf4j
public class UserController {
    @RequestMapping("/user/save1")
    public String save1(){
        String str = null;
        System.out.println(str.length());//空指针
        return "result";
    }
    @RequestMapping("/user/save2")
    public String save2(){
        int n = 10/0; //算术异常  --- >运行异常
        return "result";
    }
    @RequestMapping("/user/save3")
    public String save3() throws FileNotFoundException {
        FileInputStream fis = new FileInputStream("d:/sdf/adfadf.txt");
        return "result";
    }
    @RequestMapping("/user/save4")
    public String save4() throws  SQLException {
        String url ="adfadsf";
        String username ="root";
        String password ="root";
        Connection conn = DriverManager.getConnection(url,username,password);
        return "result";
    }
}

2. 注解方式  @ExceptionHandler

如果XML和注解两种方式同时存在,注解优先。没有必要两种都设置。推荐使用注解

如果要区分请求类型(Ajax、非Ajax),给两种请求类型都给出处理方案,只能采用注解方式。

①创建异常处理器类,在其方法指明异常映射关系

//给异常处理类添加该注解
@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(NullPointerException.class)
    public String resolveNullPointerException(Model model,NullPointerException exc){

        model.addAttribute("atguiguException",exc); //异常消息,添加到请求域

        return "exp-null";//和xml配置相同,返回的页面
    }
    
    @ExceptionHandler(FileNotFoundException.class)
    public String resolveFileNotFoundException(Model model,Exception exc){

        model.addAttribute("atguiguException",exc);
        return "exp-notfound";
    }
}

②组件扫描,扫描该类。

<!--  组件(@Controller @Service)扫描 -->
<context:component-scan base-package="com.atguigu.controller,com.atguigu.exceptionmapping"></context:component-scan>

3.异常映射-区分请求类型(Ajax和非Ajax)

如果要区分请求类型,给两种请求类型都给出处理方案,只能采用注解方式

①准备普通请求和Ajax请求

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" th:src="@{/js/vue.js}"></script>
    <script type="text/javascript" th:src="@{/js/axios.min.js}"></script>
</head>
<body>
   <h3>异常处理:非Ajax请求</h3>
    <a th:href="@{/user/save2}">非Ajax请求</a>
   <h3>异常处理:Ajax请求</h3>
   <div id="app">
       <a href="javascript:void(0)" @click="testAjax()">Ajax请求</a>
   </div>

</body>
<script type="text/javascript">
    new Vue({
        el:"#app",
        methods:{
            testAjax:function(){
                axios({
                    method:"POST",
                    url:"[[@{/user/save5}]]"
                })
                    .then(function(response){//注意,返回的错误信息在这里,而不是catch
                        console.info(response.data);
                    })
                    .catch(function(response){//成功访问到了handler方法,因此不会执行
                        console.info(response)
                    });
            }
        }
    });
</script>
</html>

对于Ajax之前的异常映射也可以起作用,但是返回的整个异常页面,而不是异常字符串

对于Ajax请求,出现了异常,跳到异常页面并返回,要在then中来接收,而不是catch中

3.定义工具类,通过请求头判断是否为ajax请求 。

非Ajax请求:
Ajax请求:     

public class MyUtil {
    /**
     * 判断当前请求是否为Ajax请求
     * @param request 请求对象
     * @return
     *      true:当前请求是Ajax请求
     *      false:当前请求不是Ajax请求
     */
    public static boolean judgeRequestType(HttpServletRequest request) {

        // 1.获取请求消息头
        String acceptHeader = request.getHeader("Accept");
        String xRequestHeader = request.getHeader("X-Requested-With");

        // 2.判断
        return (acceptHeader != null && acceptHeader.contains("application/json"))
                ||
         (xRequestHeader != null && xRequestHeader.equals("XMLHttpRequest"));//只针对jQuery
    }
}

③在实现异常映射的方法中同时处理两种情况

@ExceptionHandler(Exception.class)
    public String resolveException(Model model,Exception e,HttpServletRequest request,HttpServletResponse response) throws IOException {
        boolean flag = MyUtil.judgeRequestType(request);
        System.out.println(flag);
        //如果是ajax
        if(flag){
            response.getWriter().print("有异常");
            return null;
        }
        //不是Ajax
        model.addAttribute("atguiguException",e);
        return "exp";
    }

 再次强调:使用异常映射,Ajax请求出现异常返回的异常信息要在then中获取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值