• JSD-2204-RESTful-Service-SpringMVC-Day06


    1.关于RESTful(续)

    在设计URL时,使用{}的占位符时,可以在名称右侧添加:,并在其右侧配置正则表达式,以对URL中的参数的基本格式进行约束,例如:

    1. // http://localhost:9080/album/9527/delete
    2. @RequestMapping("/{id:[0-9]+}/delete")
    3. public String delete(@PathVariable Long id) {
    4. log.debug("开始处理删除id={}请求", id);
    5. return "处理了/" + id + "/delete的请求";
    6. }

    通过使用以上正则表达式,纯数字的id可以匹配以上路径,可以正常访问,如果不是纯数字的id,则根本匹配不到以上路径,以上方法也不会执行,服务器端将直接响应404错误。

    提示:404错误相比400错误,能更早的回绝客户端的错误请求。

    在使用{}占位符且使用了正则表达式时,不冲突的匹配(每个URL只会匹配到其中某1个正则表达式,不会同时匹配到多个正则表达式)是可以共存的,例如:

    1. // http://localhost:9080/album/9527/delete
    2. @RequestMapping("/{id:[0-9]+}/delete")
    3. public String delete(@PathVariable Long id) {
    4. log.debug("开始处理删除id={}请求", id);
    5. return "处理了/" + id + "/delete的请求";
    6. }
    7. // http://localhost:9080/album/huawei/delete
    8. @RequestMapping("/{name:[a-zA-Z]+}/delete")
    9. public String delete(@PathVariable String name) {
    10. log.debug("开始处理删除name={}请求", name);
    11. return "处理了/" + name + "/delete的请求";
    12. }

    甚至,不使用正则表达式的,也可以与之共存,例如,在以上基础上,还可以添加:

    1. // http://localhost:9080/album/test/delete
    2. @RequestMapping("/test/delete")
    3. public String delete() {
    4. log.debug("开始处理测试删除请求");
    5. return "处理了测试删除的请求";
    6. }

    Spring MVC在处理时,会优先匹配没有使用正则表达式的,所以,当提交 /album/test/delete 时,会成功匹配到以上delete()方法,不会匹配到delete(String name)方法。

    在RESTful的建议中,对于不同的数据操作,应该使用不同的请求类型,例如:

    • GET >>> /albums/9:对id值为9的相册数据执行查询(执行数据的select操作)
    • PUT >>> /albums/9:对id值为9的相册数据执行编辑(执行数据的update操作)
    • DELETE >>> /albums/9:对id值为9的相册数据执行删除(执行数据的delete操作)
    • POST >>> /albums:新增相册数据(执行数据的insert操作)

    通常,绝大部分应用中,在处理业务时(并不是直接操作某数据),并不会采纳以上建议!

    最后,在开发实践中,更多的还是只使用GETPOST这2种请求方式,关于RESTful 风格的URL设计参考:

    • 查询列表:/数据类型的复数
      • 例如:/albums
    • 查询指定id的数据:/数据类型的复数/id值
      • 例如:/albums/{id}
    • 对指定id的数据进行某操作:/数据类型的复数/id值/操作
      • 例如:/albums/{id}/delete

    2.关于MVC

    MVC = Model + View + Controller

    MVC为设计软件提供了基本的思想,它认为每个软件都应该至少包含这3大部分,且各部分分工明确,只负责整个数据处理流程中的一部分功能。

    例如V通常表现为“软件的界面”,用于呈现数据、提供用户操作的控件。

    C表示控制器,用于接收请求、响应结果,并不会处理实质业务。

    M表示数据模型,通常由业务逻辑和数据访问这2部分组成,在开发实践中,数据访问通常指的就是数据库编程,而业务逻辑是由专门的类来实现的,这样的类通常使用Service作为类名的关键字。

    在整个数据处理过程中,将会是:Controller调用Service,而Service调用Mapper。

    业务逻辑的主要职责是:设计业务流程,处理业务逻辑,以保证数据的完整性和安全性。

    3.开发Service

    Service的开发规范是先写接口,再写实现类。

    通常,会在项目的根包下创建service子包,Service接口将存放在这个包中,并且,还会在service包下创建impl子包,Service实现类都将放在这个包中,实现类都会使用ServiceImpl作为类名的后缀。

    例如:在项目的根包下创建service.IAlbumService接口,然后,再创建service.impl.AlbumServiceImpl类,且此类将实现IAlbumService接口。

    为了保证项目启动时可以正确的创建此实现类,需要类上添加@Service注解。

    1. package cn.tedu.csmall.product.service;
    2. public interface IAlbumService {
    3. }
    1. package cn.tedu.csmall.product.service.impl;
    2. import cn.tedu.csmall.product.service.IAlbumService;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.springframework.stereotype.Service;
    5. @Slf4j
    6. @Service
    7. public class AlbumServiceImpl implements IAlbumService {
    8. public AlbumServiceImpl() {
    9. log.info("创建业务对象:AlbumServiceImpl");
    10. }
    11. }

    假设需要实现:添加相册。

    则在接口中添加抽象方法,关于抽象方法的设计:

    • 返回值类型:当客户端提交了相应的请求到服务器端,业务逻辑层正确的处理了数据后,是否需要返回某个数据(设计返回值类型时,不需要考虑失败的情况,因为将通过抛出异常来表示失败
    • 方法名称:自定义
    • 参数列表:所有应该由客户端提交的数据属性
    void addNew(AlbumAddNewDTO albumAddNewDTO);
    

    然后,在实现类中:

    1. @Override
    2. public void addNew(AlbumAddNewDTO albumAddNewDTO) {
    3. log.debug("开始处理【添加相册】的业务,参数:{}", albumAddNewDTO);
    4. // 调用AlbumMapper对象的int countByName(String name)方法统计此名称的相册的数量
    5. String name = albumAddNewDTO.getName();
    6. int countByName = albumMapper.countByName(name);
    7. log.debug("尝试添加的相册名称是:{},在数据库中此名称的相册数量为:{}", name, countByName);
    8. // 判断统计结果是否大于0
    9. if (countByName > 0) {
    10. // 是:相册名称已经存在,抛出RuntimeException异常
    11. String message = "添加相册失败!相册名称【" + name + "】已存在!";
    12. log.warn(message);
    13. throw new RuntimeException(message);
    14. }
    15. // 获取当前时间:LocalDateTime now = LocalDateTime.now()
    16. LocalDateTime now = LocalDateTime.now();
    17. // 创建Album对象
    18. Album album = new Album();
    19. // 补全Album对象中各属性的值:name:来自参数
    20. // 补全Album对象中各属性的值:description:来自参数
    21. // 补全Album对象中各属性的值:sort:来自参数
    22. BeanUtils.copyProperties(albumAddNewDTO, album);
    23. // 补全Album对象中各属性的值:gmtCreate:now
    24. album.setGmtCreate(now);
    25. // 补全Album对象中各属性的值:gmtModified:now
    26. album.setGmtModified(now);
    27. // 调用AlbumMapper对象的int insert(Album album)方法插入相册数据
    28. log.debug("即将向数据库中插入数据:{}", album);
    29. albumMapper.insert(album);
    30. }

    3.1关于业务异常

    通常,建议自定义异常,用于表示在业务逻辑层中的“失败”(或错误),而不要使用已知的异常类型,避免捕获、处理不准确!

    可以在项目的根包下创建ex.ServiceException类,继承自RuntimeException

    1. public class ServiceException extends RuntimeException {
    2. // 生成5个构造方法
    3. }

    4.Spring MVC统一处理异常

    Spring MVC框架提供了统一处理异常的机制,使得每种类型的异常在处理时,只需要编写1次相关代码即可。

    通常,统一处理异常的代码会写在专门的类中,此类应该添加@ControllerAdvice,则类中相关的方法会在处理每个请求时生效!

    由于目前采取前后端分离的模式,处理异常后的响应方式是响应正文,所以,还应该使用@ResponseBody,或者,使用@RestControllerAdvice,它同时具有@ControllerAdvice@ResponseBody的效果。

    1. @RestControllerAdvice
    2. public class GlobalExceptionHandler {
    3. }

    然后,在此类中添加处理异常的方法:

    • 注解:必须添加@ExceptionHandler注解,表示此方法是统一处理异常的方法
    • 访问权限:应该使用public
    • 返回值类型:参考处理请求的方法
    • 方法名称:自定义
    • 参数列表:至少包含1个异常类型的参数,表示需要处理的异常,或理解为Spring MVC框架在调用控制器的方法后捕获的异常,另外,可按需添加HttpServletRequestHttpServletResponse等少量特定类型的参数

    所以,完整的处理异常的代码为:

    1. package cn.tedu.csmall.product.ex.handler;
    2. import cn.tedu.csmall.product.ex.ServiceException;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.springframework.stereotype.Controller;
    5. import org.springframework.web.bind.annotation.ControllerAdvice;
    6. import org.springframework.web.bind.annotation.ExceptionHandler;
    7. import org.springframework.web.bind.annotation.ResponseBody;
    8. import org.springframework.web.bind.annotation.RestControllerAdvice;
    9. @Slf4j
    10. @RestControllerAdvice
    11. public class GlobalExceptionHandler {
    12. @ExceptionHandler
    13. public String handleServiceException(ServiceException e) {
    14. log.debug("处理ServiceException:{}", e.getMessage());
    15. return e.getMessage();
    16. }
    17. }

    并且,在任何控制器类中,都不必再处理ServiceException了。

    另外,在以上类中,可以同时存在多个处理不同异常的方法(允许多个处理的异常之间存在继承关系)!

    建议在每个项目中,在统一处理异常的类中,都添加对Throwable的处理,以保证所有异常都会被处理,粗糙的异常信息不会响应到客户端去!

    1. package cn.tedu.csmall.product.ex.handler;
    2. import cn.tedu.csmall.product.ex.ServiceException;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.springframework.web.bind.annotation.ExceptionHandler;
    5. import org.springframework.web.bind.annotation.RestControllerAdvice;
    6. @Slf4j
    7. @RestControllerAdvice
    8. public class GlobalExceptionHandler {
    9. @ExceptionHandler
    10. public String handleServiceException(ServiceException e) {
    11. log.debug("处理ServiceException:{}", e.getMessage());
    12. return e.getMessage();
    13. }
    14. @ExceptionHandler
    15. public String handleThrowable(Throwable e) {
    16. log.debug("处理Throwable");
    17. e.printStackTrace();
    18. return "程序运行过程中出现意外错误,请联系系统管理员!";
    19. }
    20. }

    4.1使用Spring Validation检查请求参数

    Spring Validation框架的主要作用:实现了简化检查请求参数的基本格式

    在Spring Boot中,需要添加spring-boot-starter-validation依赖项。

    当需要检查请求参数时,需要在处理请求的方法的参数列表中,对需要检查的参数添加@Validated注解,表示此参数是需要通过Spring Validation进行检查的:

    1. @RequestMapping("/add-new")
    2. public String addNew(@Validated AlbumAddNewDTO albumAddNewDTO) {
    3. // 省略方法体的代码
    4. }

    然后,在类的属性上,添加相关检查注解,并在检查注解中配置message属性以指定错误时的提示文本:

    1. @Data
    2. public class AlbumAddNewDTO implements Serializable {
    3. @NotNull(message = "必须提交相册名称!")
    4. private String name;
    5. private String description;
    6. private Integer sort;
    7. }

    当Spring Validation检查不通过时,将抛出BindException,所以,可以在统一处理异常的类中对此类异常进行处理:

    1. @ExceptionHandler
    2. public String handleBindException(BindException e) {
    3. log.debug("处理BindException:{}", e.getMessage());
    4. StringBuilder stringBuilder = new StringBuilder();
    5. List fieldErrors = e.getFieldErrors();
    6. for (FieldError fieldError : fieldErrors) {
    7. String message = fieldError.getDefaultMessage();
    8. stringBuilder.append(message);
    9. }
    10. return stringBuilder.toString();
    11. }

    除了@NotNull以外,框架还提供了许多检查注解,

    • @Pattern:通过此注解的regexp属性配置正则表达式,并使用message配置验证失败时的提示文本
      • 注意:此注解只能添加在字符串类型的属性上
      • 注意:此注解不能检查“为null”的情况,如果不允许为null,则必须同时配置@NotNull@Pattern
    • @Range:通过此注解的minmax属性可以指定整型数据的最小值和最大值
      • 提示:此注解可以和@NotNull一起使用

    周末作业

    关于根据id删除数据,在处理业务时,应该先根据id查询数据,检查此数据是否存在,然后再删除。

    完成各数据的添加和根据id删除,包含Mapper层、业务逻辑层、控制器层。

  • 相关阅读:
    RSTP详解:对比STP,到底改进了什么?
    图像运算和图像增强六
    北京建筑笔记
    easyexcel的使用
    Java(十八)---单链表
    Leetcode 完全平方数
    超融合时序数据库YMatrixDB与PostGIS案例
    LIME Low light Image Enhancement via Illumination Map Estimation
    Ubuntu18.04 velodyne 运行loam_velodyne
    MySQL中BETWEEN AND(范围查询)
  • 原文地址:https://blog.csdn.net/TheNewSystrm/article/details/126073974