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

Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验_第1张图片


2.参数传递

Ajax请求1:发送基本类型的数据

Ajax请求1

3.Vue代码


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

        

Ajax请求2:发送实体类型数据

Ajax请求2
        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") 普通参数日期格式化,指定日志格式

        

Ajax请求3-1:发送实体类型数据带级联属性(params:非JSON)

Ajax请求3-1
            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字符串)


    com.fasterxml.jackson.core
    jackson-databind
    2.12.1
        

Ajax请求3-2:发送实体类型数据带级联属性(data:JSON)

Ajax请求3-1
            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.前端页面接收:

        

Ajax请求4:返回响应实体类,JSON格式

Ajax请求4
            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字符串) 

Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验_第2张图片

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 的拦截器能够实现,就不使用过滤器

Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验_第3张图片     Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验_第4张图片

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

[1]三要素相同

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

[2]不同点

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

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

  • 拦截的范围

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

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

  • IOC 容器支持

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

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

Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验_第5张图片

2. 创建拦截器

如何创建拦截器

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

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

public class MyInterceptor1 implements HandlerInterceptor {

     /*方法执行顺序
         preHandle() *
         访问目标方法
         postHandle() *
         解析视图  result----/WEB-INF/templates/result.html
         渲染视图  

----------

username cannot be null

afterCompletion() * 给出用户响应 */ /** * 在处理请求的目标 handler 方法前执行 * @param request 请求 * @param response 响应 * @param handler 可以认为就是目标资源,但是目标资源有多种类型,所以定义为Object * 1.分控制器的方法: EmployeeController findAll() * 2.静态页面:main.html * 3.视图控制器: * @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. 配置拦截器


    
    
    
    
        
        
    

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

Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验_第6张图片

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

Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验_第7张图片

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;
}

②表单

    

自动类型转换

名称:
年龄:
金额:
级别:
日期:

③分控制器 

@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";
    }

}

失败页面:


    

失败页面

 成功页面:


    

结果页面

2. 手动转换器

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

①新增日期类

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

public class Employee {
    ...

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

②页面表单

    

手动类型转换

名称:
年龄:
金额:
级别:
日期:
地址:

③springmvc.xml 中注册


    
        
        
            
                
            
        
    

四、类型校验  @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 在完成数据绑定后执行数据校验的工作。

①添加依赖




    org.hibernate.validator
    hibernate-validator
    6.2.0.Final



    org.hibernate.validator
    hibernate-validator-annotation-processor
    6.2.0.Final

②使用校验规则  @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 中配置异常映射


    
    
        
            
            
            exp-null
            exp-notfound
            exp-run
            exp
        
    
    
    
    

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

Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验_第8张图片

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

@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";
    }
}

②组件扫描,扫描该类。


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

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

①准备普通请求和Ajax请求




    
    Title
    
    


   

异常处理:非Ajax请求

非Ajax请求

异常处理:Ajax请求

对于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";
    }

Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验_第9张图片

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

你可能感兴趣的:(SSM,ajax,服务器,springMVC,interceptor)