首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Spring学习笔记(二十一)——规范开发:SpringBoot表单验证、AOP切面编程、统一返回结果和异常处理

Spring学习笔记(二十一)——规范开发:SpringBoot表单验证、AOP切面编程、统一返回结果和异常处理

作者头像
不愿意做鱼的小鲸鱼
发布2022-09-26 18:04:50
发布2022-09-26 18:04:50
9080
举报
文章被收录于专栏:web全栈web全栈

RESTfulAPI设计

实现这些接口的步骤如下

  1. 创建spring boot工程,按需导入坐标
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.kt</groupId>
<artifactId>girl</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>girl</name>
<description>Demo project for Spring Boot</description>
 
<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
 
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
 
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
 
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
 
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--参数校验-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
        <version>2.6.7</version>
    </dependency>
 
</dependencies>
 
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
 
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.3.7.RELEASE</version>
            <configuration>
                <mainClass>cn.kt.girl.GirlApplication</mainClass>
            </configuration>
            <executions>
                <execution>
                    <id>repackage</id>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
</project>
 

  1. 编写相应的配置文件
代码语言:javascript
复制
# 应用服务 WEB 访问端口
server.port=8081
# 数据库驱动:
spring.datasource.driver-class-name= com.mysql.jdbc.Driver
# 数据源名称
spring.datasource.name=dbgirl
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/dbgirl?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
# 配置jpa,根据JavaBean自动建表
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
  1. 编写JavaBean实体类,使用springboot jpa自动创表
代码语言:javascript
复制
/**
* @author tao
* @date 2021-01-23 18:05
* 概要:
*/
@Entity
public class Girl {
//主键
@Id
//自增
@GeneratedValue
private Integer id;
private String cupSize;
//设置验证条件
@Min(value = 18, message = "未成年少女禁止入内")
private Integer age;
 
public Girl() {
}
 
public Integer getId() {
    return id;
}
 
public void setId(Integer id) {
    this.id = id;
}
 
public String getCupSize() {
    return cupSize;
}
 
public void setCupSize(String cupSize) {
    this.cupSize = cupSize;
}
 
public Integer getAge() {
    return age;
}
 
public void setAge(Integer age) {
    this.age = age;
}
 
@Override
public String toString() {
    return "Girl{" +
            "id=" + id +
            ", cupSize='" + cupSize + '\'' +
            ", age=" + age +
            '}';
}
}
 

  1. 编写jap的 dao层
代码语言:javascript
复制
public interface GirlRepository extends JpaRepository<Girl,Integer> {
public List<Girl> findByAge(Integer age);
}
 

  1. 编写controller层
代码语言:javascript
复制
package cn.kt.girl.controller;
import cn.kt.girl.domain.Girl;
import cn.kt.girl.repository.GirlRepository;
import cn.kt.girl.service.GirlService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Optional;
/**
* @author tao
* @date 2021-01-23 17:54
* 概要:
*/
@RestController
public class GirlController {
 
@Autowired
private GirlRepository girlRepository;
 
@Autowired
private GirlService girlService;
 
/**
 * 获取数据列表
 *
 * @author :tao
 * @date :Created in 2021-01-23 18:50
 * @param: :
 * @return:
 */
@GetMapping("/girls")
@ResponseBody
public List<Girl> girlList() {
 
    return girlRepository.findAll();
}
 
/**
 * 添加一条数据
 *
 * @author :tao
 * @date :Created in 2021-01-23 18:51
 * @param: :
 * @return:
 */
@PostMapping("girls")
public Girl girlAdd(@Valid Girl girl, BindingResult bindingResult) {
    //表单参数验证
    if (bindingResult.hasErrors()) {
        System.out.println(bindingResult.getFieldError().getDefaultMessage());
        return null;
    }
    girl.setAge(girl.getAge());
    girl.setCupSize(girl.getCupSize());
    return girlRepository.save(girl);
}
 
//查询一个女生
@GetMapping("/girls/{id}")
public Girl girlFindOne(@PathVariable("id") Integer id) {
    Optional<Girl> girl = girlRepository.findById(id);
    return girl.get();
}
 
//更新一个女生
@PutMapping("/girls/{id}")
public Girl girlUpdate(@PathVariable("id") Integer id,
                       @RequestParam("cutSize") String cutSize,
                       @RequestParam("age") Integer age) {
    Girl girl = new Girl();
    girl.setId(id);
    girl.setCupSize(cutSize);
    girl.setAge(age);
    return girlRepository.save(girl);
}
 
//删除一个女生
@DeleteMapping("/girls/{id}")
public void girlDelete(@PathVariable("id") Integer id) {
    girlRepository.deleteById(id);
}
 
//按年龄查询
//查询一个女生
@GetMapping("/girls/age/{age}")
public List<Girl> girlFindByAge(@PathVariable("age") Integer age) {
    List<Girl> girls = girlRepository.findByAge(age);
    return girls;
}
 
//事务回滚测试
@GetMapping("/girls/two")
public void insertTwo() {
    girlService.insertTwo();
}
}
 

Controller的使用

SpringMVC中使用Controller需要配合ResponseBody来返回json格式,springboot4后只需配置RestController就能实现返回json

Controller获取参数的注解

简单的事务处理

业务需求:当插入两条数据时,插入第一条数据时成功,插入第二条数据时出现了问题,需求时保证两条数据必须同时插入,或者同时回滚不插入。

实现案例如下:

  • Service层
代码语言:javascript
复制
@Service
public class GirlService {
@Autowired
private GirlRepository girlRepository;
 
//事务回滚,要么全部成功,要么全部失败,保证事务的一致性
@Transactional
public void insertTwo() {
    Girl girlA = new Girl();
    girlA.setCupSize("A");
    girlA.setAge(18);
    girlRepository.save(girlA);
 
    Girl girlB = new Girl();
    girlB.setCupSize("B");
    girlB.setAge(19);
    //设置错误
    int a = 1 / 0;
    girlRepository.save(girlB);
}
}
 

  • Controller层
代码语言:javascript
复制
//事务回滚测试
@GetMapping("/girls/two")
public void insertTwo() {
    girlService.insertTwo();
} 

在类上或者方法上使用@Transactiona注解

SpringBoot表单验证

SpringBoot提供了强大的表单验证功能实现。即校验用户提交的数据的合理性的,比如是否为空了,年龄必须是不小于18 ,是否是纯数字等等。 导入坐标

代码语言:javascript
复制
        <!--参数校验-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.6.7</version>
        </dependency>

表单验证示例: 需求是前台传的数据验证年龄是否满18岁

  1. 在JavaBean实体类上对应的属性上加上验证注解@注解(message="提示信息")
代码语言:javascript
复制
验证限制 说明 
@Null    限制只能为null 
@NotNull   限制必须不为null 
@AssertFalse  限制必须为false 
@AssertTrue   限制必须为true 
@DecimalMax(value)   限制必须为一个不大于指定值的数字 
@DecimalMin(value)    限制必须为一个不小于指定值的数字 
@Digits(integer,fraction)   限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction 
@Future    限制必须是一个将来的日期 
@Max(value)   限制必须为一个不大于指定值的数字 
@Min(value)   限制必须为一个不小于指定值的数字 
@Pattern(value)   限制必须符合指定的正则表达式 
@Size(max,min)   限制字符长度必须在min到max之间 
@Past    验证注解的元素值(日期类型)比当前时间早 
@NotEmpty  验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) 
@NotBlank   验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 
@Email   验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 

示例如下

代码语言:javascript
复制
@Entity
public class Girl {
    //主键
    @Id
    //自增
    @GeneratedValue
    private Integer id;
    @NotEmpty(message="cupSize不能为空!")
    private String cupSize;
    //设置验证条件
    @Min(value = 18, message = "未成年少女禁止入内")
    private Integer age;
    public Girl() {
    }
  1. 编写Controller层
代码语言:javascript
复制
@PostMapping("girls")
public Girl girlAdd(@Valid Girl girl, BindingResult bindingResult) {
    //表单参数验证
    //如果有错误
    if (bindingResult.hasErrors()) {
        System.out.println(bindingResult.getFieldError().getDefaultMessage());
        return null;
    }
    girl.setAge(girl.getAge());
    girl.setCupSize(girl.getCupSize());
    return girlRepository.save(girl);
}

  1. 在接收前台参数前加@Valid注解,并且使用BindingResult对象获取验证结果,bindingResult.hasErrors()表示验证出错。bindingResult.getFieldError().getDefaultMessage()获取JavaBean上注解中Message的信息。
  2. 测试数据

后台输出

AOP统一处理请求日志

什么时面向切面

示例:使用AOP记录每一个http请求

  1. 导入依赖坐标
代码语言:javascript
复制
<!--aop切面编程-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
 

  1. 编写切面的执行逻辑类 HttpAspect.java
    • 对GirlController的所有方法都进行拦截
    • 获取url、method、ip、类方法、参数
    • 记录日志
    • 获取调用http接口后返回的数据
代码语言:javascript
复制
package cn.kt.girl.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import javax.servlet.http.HttpServletRequest;

/**
 * @author tao
 * @date 2021-01-23 21:35
 * 概要:
 */
@Aspect
@Component
public class HttpAspect {

    //定义spring自带的slf4j日志对象
    private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);

    /*
     * 第一种写法
     */

    //表示对GirlController的所有方法都进行拦截
    /*@Before("execution(public * cn.kt.girl.controller.GirlController.*(..))")
    public void doBefore() {
        System.out.println("我执行前被拦截了");
    }

    @After("execution(public * cn.kt.girl.controller.GirlController.*(..))")
    public void doAfter() {
        System.out.println("我执行后被拦截了");
    }*/

    /*
     * 第二种写法
     */
    //表示对GirlController的所有方法都进行拦截
    @Pointcut("execution(public * cn.kt.girl.controller.GirlController.*(..))")
    public void log() {
    }

    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
        //使用JoinPoint获取相关类信息
        //先获取HttpServletRequest对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //url
        logger.info("url={}", request.getRequestURL());
        //method
        logger.info("method={}", request.getMethod());
        //ip
        logger.info("ip={}", request.getRemoteAddr());
        //类方法
        logger.info("class_method={}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        //参数
        logger.info("args={}", joinPoint.getArgs());
    }

    @After("log()")
    public void doAfter() {
        //System.out.println("我执行后被拦截了");
        logger.info("我执行后被拦截了");
    }

    //获取调用http接口后返回的数据
    @AfterReturning(returning = "object", pointcut = "log()")
    public void doAfterReturning(Object object) {
        logger.info("response={}", object);
    }
}

执行结果如下

统一异常处理

日常开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管。这种方法对于程序员来说处理也比较麻烦,对客户来说也不太友好,所以我们希望既能方便程序员编写代码,不用过多的自己去处理各种异常编写重复的代码又能提升用户的体验,这时候全局异常处理就显得很重要也很便捷了,是一种不错的选择。

1. 统一结果返回与统一异常

  • 建立一个工具包,再建一个专门用来返回结果的工具类ResultUtils.java,用来封装数据,返回我们想要的数据格式。 package
代码语言:javascript
复制
package cn.kt.girl.utils;
import cn.kt.girl.domain.Result;
/**
* @author tao
* @date 2021-01-23 23:30
* 概要:用来统一处理返回结果
*/
public class ResultUtils {
public static Result success(Object object) {
    Result result = new Result();
    result.setCode(0);
    result.setMsg("成功");
    result.setData(object);
    return result;
}
public static Result success() {
    return success(null);
}
public static Result error(Integer coad, String msg) {
    Result result = new Result();
    result.setCode(coad);
    result.setMsg(msg);
    return result;
}
public static Result error(Integer coad, String msg,Object obj) {
    Result result = new Result();
    result.setCode(coad);
    result.setMsg(msg);
    result.setData(obj);
    return result;
}
}
 

  • 建立一个枚举包,在建立一个枚举类ResultEnums.java,用来统一枚举可能发生的异常和错误码。 枚举的基本用法参考
代码语言:javascript
复制
package cn.kt.girl.enums;
/**
* @author tao
* @date 2021-01-24 0:35
* 概要:
*/
public enum ResultEnums {
UNKNOW_ERROR(-1, "未知错误"),
SUCCESS(0, "成功"),
VAILD_EXCEPTION(10001, "参数格式校验失败"),
PRIMARY_SCHOOL(100, "你可能还在上小学"),
MIDDLE_SCHOOL(101, "你可能在上初中"),
;
private Integer code;
private String msg;
 
ResultEnums(Integer code, String msg) {
    this.code = code;
    this.msg = msg;
}
 
public Integer getCode() {
    return code;
}
 
public String getMsg() {
    return msg;
}
}

2. 自定义异常类 为什么要编写自定义异常? 因为抛出Expection异常时,无法自定义错误码,只能传入异常处理信息,所以自定义类可以处理错误码和提示信息对应,甚至更多。

新建一个自定义异常包exception,再建一个自定义异常类GirlException.java

代码语言:javascript
复制
package cn.kt.girl.exception;
import cn.kt.girl.enums.ResultEnums;
/**
 * @author tao
 * @date 2021-01-24 0:22
 * 概要:自己写的异常类
 * 注意RuntimeException的异常会进行回滚,而直接继承Exception不会进行回滚
 */
public class GirlExpection extends RuntimeException {
    private Integer code;
    public GirlExpection(ResultEnums resultEnums) {
        super(resultEnums.getMsg());
        this.code = resultEnums.getCode();
    }
    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

注意RuntimeException的异常会进行回滚,而直接继承Exception不会进行回滚

3. 自定义一个全局异常处理类

用来全局处理各种异常,包括自己定义的异常和内部异常。这样可以简化不少代码,不用自己对每个异常都使用try,catch的方式来实现。 新建一个异常处理包handle,在里面建一个全局异常处理类ExceptionHandle.java

代码语言:javascript
复制
package cn.kt.girl.handle;

import cn.kt.girl.aspect.HttpAspect;
import cn.kt.girl.controller.GirlController;
import cn.kt.girl.domain.Result;
import cn.kt.girl.enums.ResultEnums;
import cn.kt.girl.exception.GirlExpection;
import cn.kt.girl.utils.ResultUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author tao
 * @date 2021-01-23 23:58
 * 概要:捕获异常,处理异常,记录日志
 */
@ControllerAdvice
public class ExceptionHandle {
    private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);

    //统一处理数据的校验异常
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result handleValidException(MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        logger.error("【数据校验出现问题】{},【异常类型】:{}", e.getMessage(), e.getClass());
        Map<String, String> resmap = new HashMap<>();
        result.getFieldErrors().forEach((item) -> {
            resmap.put(item.getField(), item.getDefaultMessage());
        });
        return ResultUtils.error(ResultEnums.VAILD_EXCEPTION.getCode(), ResultEnums.VAILD_EXCEPTION.getMsg(), resmap);
    }

    // 自定义异常处理方法
    @ExceptionHandler(value = GirlExpection.class)
    public Result handleGirlExpection(GirlExpection e) {
        return ResultUtils.error(e.getCode(), e.getMessage());
    }

    //系统异常处理方法
    @ExceptionHandler(value = Throwable.class)
    public Result handleException(Throwable e) {
        logger.error("【系统异常】{}", e);
        return ResultUtils.error(ResultEnums.UNKNOW_ERROR.getCode(), ResultEnums.UNKNOW_ERROR.getMsg());
    }
    /*@ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Result handle(Exception e) {
        if (e instanceof GirlExpection) {
            GirlExpection girlExpection = (GirlExpection) e;
            return ResultUtils.error(girlExpection.getCode(), girlExpection.getMessage());

        } else {
            logger.error("【系统异常】{}", e);
            return ResultUtils.error(ResultEnums.UNKNOW_ERROR.getCode(), ResultEnums.UNKNOW_ERROR.getMsg());
        }

    }*/
}

其中分组校验等可以参考:https://blog.csdn.net/weixin_43671737/article/details/108578122 说明: 1、@ControllerAdvice注解是Spring3.2中新增的注解,可以理解为是Controller增强器,作用是给Controller控制器添加统一的操作或处理。可以用来指定作用范围:@ControllerAdvice(basePackages={"com.automvc", "com.test"}) ,如果不指定范围,@ControllerAdvice默认对所有的controller起作用。 具体作用可以参考:https://www.cnblogs.com/yanggb/p/10859907.html 2、@ExceptionHandler这个注解的功能是:自动捕获controller层出现的指定类型异常,并对该异常进行相应的异常处理.要求该方法必须要和出现问题的控制器在一个类中,才能生效。因此@ExceptionHandler和@ControllerAdvice经常结合使用,达到全局异常的捕获和处理。 3、每个方法上面加上一个 @ResponseBody的注解,用于将对象解析成json,方便前后端的交互,也可以使用 @ResponseBody放在异常类上面。

4. controller和service层代码测试

处理统一返回结果

代码语言:javascript
复制
    /**
     * 添加一条数据
     *
     * @author :tao
     * @date :Created in 2021-01-23 18:51
     * @param: :
     * @return:
     */
    @PostMapping("girls")
    public Result<Girl> girlAdd(@Valid Girl girl, BindingResult bindingResult) {
        //表单参数验证
        //如果有错误
        if (bindingResult.hasErrors()) {
            return ResultUtils.error(1, bindingResult.getFieldError().getDefaultMessage());

        }
        return ResultUtils.success(girlRepository.save(girl));
    }

表单验证如下

测试结果如下

处理统一返回异常

需求

  • Service层代码
代码语言:javascript
复制
//根据不同年龄抛出异常
public void getAge(Integer id) throws Exception {
    Girl girl = girlRepository.findById(id).get();
    Integer age = girl.getAge();
    if (age < 10) {
        throw new GirlExpection(ResultEnums.PRIMARY_SCHOOL);
    } else if (age > 10 && age < 16) {
        throw new GirlExpection(ResultEnums.MIDDLE_SCHOOL);
    }
 
}
 

  • Controller层代码
代码语言:javascript
复制
@GetMapping("/girls/getAge/{id}")
public void getAge(@PathVariable("id") Integer id) throws Exception {
    girlService.getAge(id);
}
 

  • 测试结果如下

如果发生其他错误

案例代码下载 源码下载 链接:https://pan.baidu.com/s/1touXxe4RBI0Yl6c5VRl3xA 提取码:7qy1

目录结构

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • RESTfulAPI设计
    • Controller的使用
    • Controller获取参数的注解
  • 简单的事务处理
  • SpringBoot表单验证
  • 统一异常处理
    • 1. 统一结果返回与统一异常
    • 3. 自定义一个全局异常处理类
    • 4. controller和service层代码测试
      • 处理统一返回结果
      • 处理统一返回异常
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档