SpringMVC学习(二)——RESTful API、拦截器、异常处理、数据类型转换

一、RESTful

(一)RESTful概述

RESTful是一种软件架构风格,用于设计网络应用程序。REST是"Representational State Transfer"的缩写,中文意思是"表现层状态转移"。它基于客户端-服务器模型和无状态操作,以及使用HTTP请求来处理数据。RESTful架构风格强调利用HTTP协议的四个主要方法(GET、POST、PUT、DELETE)来实现资源的访问和操作。以下是RESTful架构的一些核心原则:

  1. 客户端-服务器分离:客户端和服务器之间的交互应该是简单的,服务器端负责存储数据和业务逻辑,客户端负责展示。

  2. 无状态:每个请求从客户端到服务器必须包含所有必要的信息,以便服务器能够理解请求并独立地处理它,不依赖于之前的任何请求。

  3. 可缓存:数据被标记为可缓存或不可缓存。如果数据被标记为可缓存,那么客户端可以缓存数据以提高效率。

  4. 统一接口:系统组件之间的交互通过统一的接口进行,这简化了整体系统架构,使得系统更容易理解、开发和使用。

  5. 分层系统:客户端不能直接了解它所消费的服务之外的任何服务器信息,也不应该知道它的数据是来自一个服务器还是多个服务器。

  6. 按需代码(可选):服务器可以按需向客户端发送代码,比如JavaScript,以便在客户端执行。

在RESTful架构中,资源(Resources)是核心概念,每个资源都有一个唯一的标识符,通常是一个URI。客户端通过HTTP方法对这些资源进行操作:

  • GET:请求从服务器检索特定资源。GET请求应该是安全的,不会产生副作用。

  • POST:向服务器提交新的资源,通常会导致创建新的资源。

  • PUT:更新服务器上的现有资源。

  • DELETE:从服务器上删除资源。

RESTful API设计简洁、直观,易于理解和使用,因此在现代网络应用中非常流行

代码示例:

java 复制代码
@CrossOrigin  // 允许跨域请求
@RestController
@RequestMapping("/emp")
public class EmpController {
    @Autowired
    private EmpService empService;

    /**
     * 查询员工
     */
    @GetMapping("/{empno}")
    public R queryEmpById(@PathVariable("empno") Integer empno) {
        Emp emp = empService.queryById(empno);
        return R.ok(emp);
    }

    /**
     * 新增员工
     */
    @PostMapping
    public R addEmp(@RequestBody Emp emp) {
        empService.save(emp);
        return R.ok();
    }

    /**
     * 修改员工
     */
    @PutMapping
    public R editEmp(@RequestBody Emp emp) {
        empService.update(emp);
        return R.ok();
    }

    /**
     * 删除员工
     */
    @DeleteMapping("/{empno}")
    public R deleteEmpById(@PathVariable("empno") Integer empno) {
        empService.deleteById(empno);
        return R.ok();
    }

    /**
     * 查询所有员工
     */
    @GetMapping("/getAll")
    public R queryEmpList() {
        List<Emp> empList = empService.getList();
        return R.ok(empList);
    }
}

(二)@PathVariable:从URL中提取路径变量

@PathVariable是Spring MVC中用于从URL中提取路径变量的注解。它允许将URL模板中的占位符映射到方法参数,从而实现动态路由和数据传递。

使用场景:

  • RESTful API:在设计RESTful API时,通常会使用@PathVariable来获取资源的唯一标识符(如 ID)。
  • 动态内容:根据 URL 中的变量来动态生成页面或响应内容。

二、拦截器

(一)HandlerInterceptor

HandlerInterceptor是SpringMVC内置拦截器机制,用于在请求处理的不同阶段插入自定义逻辑。它允许在请求到达控制器之前、控制器处理请求之后以及请求完成之后执行特定的操作。比如:权限验证、日志记录、数据共享等......

使用步骤:

  • 实现HandlerInterceptor接口的组件即可成为拦截器
  • 创建WebMvcConfigurer组件,并配置拦截器的拦截路径
  • 执行顺序:顺序preHandle→目标方法→倒序postHandle→渲染→倒序afterCompletion
  • 只有执行成功的preHandle会倒序执行afterCompletion
  • postHandle、afterCompletion从哪里跑出异常,倒序链路从哪里结束
  • postHandle失败不会影响afterCompletion执行
java 复制代码
@Component  // 拦截器还需要配置(告诉SpringMVC,这个拦截器主要拦截什么请求)
public class MyHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyHandlerInterceptor...preHandle...");
        return false; // true表示放行,false表示拦截
    }

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

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyHandlerInterceptor...afterCompletion...");
    }
}
java 复制代码
@Configuration  // 专门对SpringMVC底层进行配置
public class MySpringMVCConfig implements WebMvcConfigurer {

    @Autowired
    MyHandlerInterceptor myHandlerInterceptor;

    /**
     * 添加拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myHandlerInterceptor)
                .addPathPatterns("/**"); // 拦截所有请求
    }
}
java 复制代码
@CrossOrigin  // 允许跨域请求
@RestController
@RequestMapping("/emp")
public class EmpController {
    @Autowired
    private EmpService empService;

    /**
     * 查询员工
     */
    @GetMapping("/{empno}")
    public R queryEmpById(@PathVariable("empno") Integer empno) {
        System.out.println("查询用户。目标方法执行......");
        Emp emp = empService.queryById(empno);
        return R.ok(emp);
    }
}

(二)拦截器与过滤器的区别(面试题)

三、异常处理

1.编程式异常处理

编程式异常处理:如果大量业务都需要加异常处理代码会很麻烦

java 复制代码
@GetMapping("/hello")
public R hello(@RequestParam(value = "i", defaultValue = "0") Integer i) {
    try {
        int j = 10 / i;
        return R.ok(j);
    } catch (Exception e) {
        return R.error(100, "除数不能为0", e.getMessage());
    }
}

2.声明式异常处理

  1. 如果Controller本类出现异常,会自动在本类中找到有没有@ExceptionHandler标注的方法,如果有,执行这个方法,它的返回值,就是客户端收到的结果;如果发生异常,多个都能处理,就精确的优先。
  2. 异常处理的优先级:本类 > 全局;精确 > 模糊
  3. 如果出现了异常:本类和全局都不能处理,SpringBoot底层对SpringMVC有兜底处理机制:自适应处理(浏览器响应页面、移动端响应JSON)
  4. 最佳实践:编写全局异常处理器,处理所有异常
java 复制代码
@RestController
public class HelloController {
    @GetMapping("/hello")
    public R hello(@RequestParam(value = "i", defaultValue = "0") Integer i) throws FileNotFoundException {
        int j = 10 / i;
//        FileInputStream fileInputStream = new FileInputStream("C:\\Users\\lxm\\Desktop\\test.txt");
        String str = null;
        str.length();
        return R.ok(j);
    }

    @ExceptionHandler(ArithmeticException.class)
    public R handlerArithmeticException(ArithmeticException e) {
        System.out.println("ArithmeticException异常处理");
        return R.error(100, "除数不能为0", e.getMessage());
    }

    @ExceptionHandler(FileNotFoundException.class)
    public R FileNotFoundException(FileNotFoundException e) {
        System.out.println("FileNotFoundException异常处理");
        return R.error(300, "文件找不到", e.getMessage());
    }

    @ExceptionHandler(Throwable.class)
    public R handlerException(Throwable e) {
        System.out.println("Throwable异常处理");
        return R.error(500, "其他异常", e.getMessage());
    }
}
java 复制代码
// @ResponseBody
// @ControllerAdvice   // 告诉SpringMVC,这个类是处理全局异常的
@RestControllerAdvice  // 全局异常处理器:相当于@ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public R handleException(Throwable e) {
        System.out.println("全局异常处理");
        return R.error(500, e.getMessage());
    }

    @ExceptionHandler(ArithmeticException.class)
    public R handlerArithmeticException(ArithmeticException e) {
        System.out.println("算数异常处理");
        return R.error(100, e.getMessage());
    }
}

3.异常处理的最终方式

  1. 必须有业务异常类:BusinessException
  2. 必须有异常枚举类:BusinessExceptionEnum列举项目中每个模块将会出现的所有异常情况
  3. 编写业务代码的时候,只需编写正确逻辑,如果出现预期的问题,需要以抛异常的方式中断逻辑并通知上层
  4. 全局异常处理器:GlobalExceptionHandler 处理所有异常,返回给前端约定的JSON数据与错误码

异常枚举类:

java 复制代码
public enum BusinessExceptionEnum {
    ORDER_NOT_EXIST(10001, "订单不存在"),
    ORDER_STATUS_ERROR(10002, "订单状态错误"),
    ORDER_UPDATE_ERROR(10003, "订单更新失败"),
    ORDER_DELETE_ERROR(10004, "订单删除失败"),
    ORDER_CREATE_ERROR(10005, "订单创建失败"),
    ORDER_QUERY_ERROR(10006, "订单查询失败"),
    ORDER_PAY_ERROR(10007, "订单支付失败"),
    ORDER_CANCEL_ERROR(10008, "订单取消失败");

    @Getter
    private Integer code;
    @Getter
    private String msg;

    BusinessExceptionEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

全局业务异常类:

java 复制代码
@Data
public class BusinessException extends RuntimeException {
    private Integer code;
    private String msg;

    public BusinessException(Integer code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }

    public BusinessException(BusinessExceptionEnum businessExceptionEnum) {
        super(businessExceptionEnum.getMsg());
        this.code = businessExceptionEnum.getCode();
        this.msg = businessExceptionEnum.getMsg();
    }
}

业务代码

java 复制代码
@Override
public void update(Emp emp) {
    // 去数据库查询原来的值
    Integer empno = emp.getEmpno();
    if (empno == null) {
        throw new BusinessException(BusinessExceptionEnum.ORDER_NOT_EXIST);
    }
    Emp empById = empDao.getEmpById(empno);
    if (StringUtils.hasText(empById.getEname())) {
        empById.setEname(emp.getEname());
    }
    empDao.updateEmp(emp);
}

四、SpringMVC原理

五、数据类型转换

1.String转Date类型

java 复制代码
@Controller
@RequestMapping("/book")
public class BookController {

    @GetMapping("/jump")
    public String jump(){
        // int i = 1/0;
        return "book/add";
    }

    @PostMapping("/doAdd")
    @ResponseBody
    public BookModel doAdd(BookModel book){
        return book;
    /**
    * {
    *   "id": null,
    *   "name": "红楼梦",
    *   "ctime": 1735228800000
    * }
    */
    }

    @PostMapping("/doAdd2")
    @ResponseBody
    public Date doAdd2(String name,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") Date ctime){
        return ctime; // 1735142400000
    }
}
java 复制代码
@Data
public class BookModel {
    private Integer id;
    private String name;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date ctime;
}

2.String转LocalDateTime类型

自定义参数类型转换器:

java 复制代码
public class StringToDate implements Converter<String, Date> {
    @Override
    public Date convert(String s) {
        //传入的参数,等待被转换的2024-12-27 字符串
        System.out.println(s);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = simpleDateFormat.parse(s);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        return date;
    }
}


public class StringToDateTime implements Converter<String, LocalDateTime> {
    @Override
    public LocalDateTime convert(String s) {
        return LocalDateTime.parse(s, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    }
}
java 复制代码
@Data
public class BookModel {
    private Integer id;
    private String name;
//    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date ctime;
//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime utime;
}

3.接收JSON字符串

XML 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.11.2</version>
</dependency>
java 复制代码
/**
 * 接收JSON转换日期时间
 */
@PostMapping("/doAdd3")
@ResponseBody
public BookModel doAdd3(@RequestBody BookModel book){
    return book;
}
java 复制代码
@Data
public class BookModel {
    private Integer id;
    private String name;
    // @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date ctime;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime utime;
}

注意:

JSON时间,可以写2024-01-27 09:09:09

不能写 2024-1-27 9:9:9

相关推荐
珹洺3 小时前
Java-Spring入门指南(二十一)Thymeleaf 视图解析器
java·开发语言·spring
EnCi Zheng3 小时前
Spring Security 最简配置完全指南-从入门到精通前后端分离安全配置
java·安全·spring
鸽鸽程序猿6 小时前
【项目】基于Spring全家桶的论坛系统 【下】
后端·spring·restful
Lisonseekpan7 小时前
Spring Boot 中使用 Caffeine 缓存详解与案例
java·spring boot·后端·spring·缓存
小许学java7 小时前
Spring AI快速入门以及项目的创建
java·开发语言·人工智能·后端·spring·ai编程·spring ai
kfepiza9 小时前
Spring 如何解决循环依赖 笔记251008
java·spring boot·spring
kfepiza10 小时前
Spring的三级缓存原理 笔记251008
笔记·spring·缓存
popoxf20 小时前
spring容器启动流程(反射视角)
java·后端·spring
谷哥的小弟21 小时前
Spring Framework源码解析——ApplicationContextAware
spring·源码
武子康21 小时前
Java-141 深入浅出 MySQL Spring事务失效的常见场景与解决方案详解(3)
java·数据库·mysql·spring·性能优化·系统架构·事务