前言
该博客主要承接上一篇博客springboot入门-快速上手springboot,是对上springboot入门知识点的补充说明,也是为之后我们学习springboot实战打基础的。
主要知识点
- 使用@Valid进行表单验证
- AOP使用
- 统一异常处理
- 单元测试
使用@Valid进行表单验证
- 表单验证目的
为什么要进行表单验证,客户端不是已经进行了表单验证码?
在开发中我们要知道,一切来自客户端的数据都是不可信的,所以我们必须对数据进行相应的校验。
- 补充说明
在springboot 2.3.0 之后放弃了对 javax.validation 的默认支持,所以我们必须主动引入javax.validation。
<!-- 表单验证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 使用@Valid进行表单验证步骤
承接上一篇博客springboot入门-快速上手springboot我们,检查出一个新的分支feature/advance-springboot来进行开发。
我们简单校验,user对象年龄在18岁以下和name为空的数据。
- 对User实现类进行条件限制
@NotBlank(message = "姓名不能为空")
private String name;
@Min(value = 18,message = "年龄必须大于18")
private Integer age;
- 对请求数据进行验证
/**
* C
* @return result
*/
@PostMapping("/user")
public Map<String, Object> create(@RequestBody @Valid User user, BindingResult bindingResult){
Map<String, Object> result = new HashMap<>();
if (bindingResult.hasErrors()) {
result.put("msg", bindingResult.getFieldError().getDefaultMessage());
}else {
user = userDao.save(user);
result.put("msg", user);
}
return result;
}
AOP使用
- 什么是AOP
AOP:面向切面编程,是一种编程范式或者编程思想,与语言无关。不是Java语言特有的性质,在Java语言中AOP的实现原理为动态代理。
- AOP能做什么
顾名思义AOP能做的事情,与其名字面向切面编程一样。它能做的事情就是,将一些业务逻辑或者说代码块切入到我们指定的地方。因为这一特性,我们能很好的解决,开发中要经常使用但又与核心业务逻辑无关的重复操作,例如日志处理,数据校验等等。
- 在springboot中怎么使用AOP(以http请求处理为例)
- 新建一个切面类
package com.itxiaoyuaiit.learnspringboot.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 javax.servlet.http.HttpServletRequest;
/**
* @ClassName HttpAspect
* @Description TODO
* @Author wuyuqing
* @Date 2020/8/24 21:39
* @Version 1.0
*/
@Component
@Aspect
public class HttpAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpAspect.class);
/**
* 设置切入点 对com.itxiaoyuaiit.learnspringboot.controller包下的所有类的所有public方法进行切入(拦截)
*/
@Pointcut("execution(public * com.itxiaoyuaiit.learnspringboot.controller.*.*(..))")
public void log() {
}
/**
* 切入点前执行
*
* @param joinPoint
*/
@Before("log()")
public void doBefore(JoinPoint joinPoint) {
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().getDeclaringType() + "." + joinPoint.getSignature().getName());
//参数
LOGGER.info("args={}", joinPoint.getArgs());
}
/**
* 切入点后执行
*/
@After("log()")
public void doAfter() {
LOGGER.info("--- doAfter ---");
}
/**
* 切入返回执行
* @param result
*/
@AfterReturning(pointcut = "log()", returning = "result")
public void doAfterReturning(Object result) {
LOGGER.info(result.toString());
}
}
统一异常处理
为什么要进行统一异常处理
在我们程序运行的过程中,会遇到各种各种的异常,为避免代码重复,代码冗余,便于统一管理,我们通常都要对异常进行统一处理。
如何进行统一异常处理
统一异常处理准备
- 构建http接口统一返回模型
- 新建统一数据返回类
package com.itxiaoyuaiit.learnspringboot.model;
/**
* @ClassName Result
* @Description http接口统一返回对象
* @Author wuyuqing
* @Date 2020/8/25 10:19
* @Version 1.0
*/
public class Result<T> {
/**
* 返回状态吗
*/
private Integer code;
/**
* 返回信息,错误说明等
*/
private String msg;
/**
* 返回的具体数据实例
*/
private T data;
public Result() {
}
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
- 新建数据返回工具类
package com.itxiaoyuaiit.learnspringboot.util;
import com.itxiaoyuaiit.learnspringboot.model.Result;
/**
* @ClassName ResultUtil
* @Description http接口统一返回对象,工具类
* @Author wuyuqing
* @Date 2020/8/25 10:25
* @Version 1.0
*/
public class ResultUtil {
public static Result success(Object data){
Result result = new Result<>();
result.setCode(0);
result.setMsg("success");
result.setData(data);
return result;
}
public static Result success(){
return success(null);
}
public static Result error(Integer code, String msg){
Result result = new Result<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
- 统一数据返回接口
/**
* C
* @return result
*/
@PostMapping("/user")
public Result<User> create(@RequestBody @Valid User user, BindingResult bindingResult){
if (bindingResult.hasErrors()) {
return ResultUtil.error(100, bindingResult.getFieldError().getDefaultMessage());
}else {
user = userDao.save(user);
return ResultUtil.success(user);
}
}
使用异常处理业务逻辑
- 业务逻辑
当user对象年龄小于18岁时,返回未成年;当user对象年龄大于18岁时,返回老年人。
- 自定义UserException
package com.itxiaoyuaiit.learnspringboot.exception;
import com.itxiaoyuaiit.learnspringboot.enums.ResultEnum; /**
* @ClassName UserException
* @Description 自定义user相关处理逻辑异常
* @Author wuyuqing
* @Date 2020/8/25 10:55
* @Version 1.0
*/
public class UserException extends RuntimeException {
/**
* 异常状态码
*/
private Integer code;
public UserException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
- 定义结果处理枚举ResultEnum
package com.itxiaoyuaiit.learnspringboot.enums;
/**
* @EnumName ResultEnum
* @Description 结果构建枚举
* @Author wuyuqing
* @Date 2020/8/25 11:27
* @Version 1.0
*/
public enum ResultEnum {
UNKNOW_ERROR(-1, "未知错误"),
SUCCESS(0,"success"),
MINOR(100, "未成年"),
OLD(101, "老年人")
;
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
- 定义UserService
package com.itxiaoyuaiit.learnspringboot.service;
import com.itxiaoyuaiit.learnspringboot.dao.UserDao;
import com.itxiaoyuaiit.learnspringboot.enums.ResultEnum;
import com.itxiaoyuaiit.learnspringboot.exception.UserException;
import com.itxiaoyuaiit.learnspringboot.model.Result;
import com.itxiaoyuaiit.learnspringboot.model.User;
import com.itxiaoyuaiit.learnspringboot.util.ResultUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @ClassName UserService
* @Description TODO
* @Author wuyuqing
* @Date 2020/8/25 11:05
* @Version 1.0
*/
@Service
public class UserService {
@Autowired
private UserDao userDao;
public Result getUser(Integer id) {
User user = userDao.findById(id).get();
if(user.getAge() >60){
throw new UserException(ResultEnum.OLD);
}else if (user.getAge() < 18){
throw new UserException(ResultEnum.MINOR);
}
return ResultUtil.success(user);
}
}
- 定义异常处理类ExceptionHandle
package com.itxiaoyuaiit.learnspringboot.handle;
import com.itxiaoyuaiit.learnspringboot.exception.UserException;
import com.itxiaoyuaiit.learnspringboot.model.Result;
import com.itxiaoyuaiit.learnspringboot.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @ClassName ExceptionHandle
* @Description 统一异常处理类
* @Author wuyuqing
* @Date 2020/8/25 10:58
* @Version 1.0
*/
@RestControllerAdvice
public class ExceptionHandle {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandle.class);
@ExceptionHandler
public Result handle(Exception e){
if(e instanceof UserException) {
e = (UserException) e;
return ResultUtil.error(((UserException) e).getCode(), e.getMessage());
}else{
LOGGER.error("msg={}", e);
return ResultUtil.error(-1, e.getMessage());
}
}
}
- 修改controller
/**
* R
* @return result
*/
@GetMapping("/user/{id}")
public Result<User> reader(@PathVariable("id") Integer id){
Result result = userService.getUser(id);
return result;
}
- 执行接口测试,至此统一异常处理就已经完成了。
单元测试
- 为什么要进行单元测试?
作为一个合格的开发者,我们都应该对自己的代码负责,对代码进行单元测试是一个合格程序员的基本素养。
- 如何进行单元测试?
这里有一个小技巧:如果你使用的是IDEA我们只需右键选中要进行测试的代码,点击go to 点击 Test IDEA便会为我们自动创建好测试类。
- 对UserService类进行测试
- 新建UserServiceTest
package com.itxiaoyuaiit.learnspringboot.service;
import com.itxiaoyuaiit.learnspringboot.model.Result;
import com.itxiaoyuaiit.learnspringboot.model.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void getUser() {
Result<User> result = userService.getUser(1);
Assert.assertEquals(result.getData().getJob(), "将军");
}
}
- 对RestFulApiController进行测试
对Controller进行测试,对controller进行测试,我们采用Mocmvc来模型接口调用。
- 新建RestFulApiControllerTest测试类,使用mockMvc进行接口调用执行测试。
package com.itxiaoyuaiit.learnspringboot.controller;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest
public class RestFulApiControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void reader() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user/1")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().json("{}"));
}
}
总结
- 以上便是springboot进阶相关的知识点,主要涉及:表单验证,AOP使用,统一异常处理,单元测试等Java Web开发实战中必不可少的知识。这是为我们下一篇博客讲解springboot实战做准备的,源码地址。