SpringBoot入门二
表单验证
表单验证,就是对用户的输入数据进行有效性检查
例如在添加某个实体Girl时,要满足其年龄必须大于18岁,否则不能提交
此时可以对Girl的age属性,使用@Min
注解
@Min(value = 18, message = "年龄未满18岁")
private Integer age;
同时对于添加数据的方法,需添加:
@Valid
- 表示验证的对象BindingResult
- 获取验证的结果
如下:
@PostMapping(value = "/girls")
public Girl girlAdd(@Valid Girl girl, BindingResult bindingResult){
if (bindingResult.hasErrors()){
System.out.println(bindingResult.getFieldError().getDefaultMessage());
return null;
}
return girlReponsitory.save(girl);
}
此时,如果我们添加一个age小于18
的对象,此时控制台会输出年龄未满18岁
其它验证注解,参考Spring Boot 表单验证篇
Bean Validation 规范,运行时的数据验证框架。它是 JSR 303 规范,Hibernate Validator 实现了这套规范,并扩展了一些注解,如下:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内
AOP
AOP是一种编程范式,与语言无关,是一种编程设计思想。将通用逻辑从业务逻辑中分离出来
AOP统一处理请求日志
步骤:
1.在pom.xml
中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.创建处理文件HttpAspect
,添加@Aspect
、@Component
注解
@Aspect
@Component
public class HttpAspect {
@Before("execution(public * com.wz.girl.controller.GirlController.*(..))")
public void logBefore(){
System.out.println("logBefore");
}
@After("execution(public * com.wz.girl.controller.GirlController.*(..))")
public void logAfter(){
System.out.println("logAfter");
}
}
上面的代码中,execution(public * com.wz.girl.controller.GirlController.*(..))
是重复的,可以这样优化:
@Aspect
@Component
public class HttpAspect {
//表示切点
@Pointcut("execution(public * com.wz.girl.controller.GirlController.*(..))")
public void log(){
}
@Before("log()")
public void logBefore(){
System.out.println("logBefore");
}
@After("log()")
public void logAfter(){
System.out.println("logAfter");
}
}
此时,如果调用某个http://localhost:8083/girl/girls/1
,控制台会输出:
logBefore
Hibernate: select girl0_.id as id1_0_0_, girl0_.age as age2_0_0_, girl0_.cup_size as cup_size3_0_0_ from girl girl0_ where girl0_.id=?
logAfter
3.使用日志输出
@Aspect
@Component
public class HttpAspect {
private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
//表示切点
@Pointcut("execution(public * com.wz.girl.controller.GirlController.*(..))")
public void log(){
}
@Before("log()")
public void logBefore(){
logger.info("logBefore");
}
@After("log()")
public void logAfter(){
logger.info("logAfter");
}
}
此时输出日志的形式为:
2017-11-15 21:23:03.582 INFO 63856 --- [nio-8083-exec-1] com.wz.girl.aspect.HttpAspect : logBefore
Hibernate: select girl0_.id as id1_0_0_, girl0_.age as age2_0_0_, girl0_.cup_size as cup_size3_0_0_ from girl girl0_ where girl0_.id=?
2017-11-15 21:23:03.683 INFO 63856 --- [nio-8083-exec-1] com.wz.girl.aspect.HttpAspect : logAfter
4.记录Http请求
@Before("log()")
public void logBefore(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().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());
//参数
logger.info("args={}", joinPoint.getArgs());
}
//获取返回的内容
@AfterReturning(returning = "object", pointcut = "log()")
public void doAfterReturning(Object object){
logger.info("response={}", object);
}
有某一个请求时,控制台输出为:
2017-11-15 21:38:26.536 INFO 64813 --- [nio-8083-exec-1] com.wz.girl.aspect.HttpAspect : url=http://localhost:8083/girl/girls/1
2017-11-15 21:38:26.537 INFO 64813 --- [nio-8083-exec-1] com.wz.girl.aspect.HttpAspect : method=GET
2017-11-15 21:38:26.537 INFO 64813 --- [nio-8083-exec-1] com.wz.girl.aspect.HttpAspect : ip=0:0:0:0:0:0:0:1
2017-11-15 21:38:26.539 INFO 64813 --- [nio-8083-exec-1] com.wz.girl.aspect.HttpAspect : class_method=com.wz.girl.controller.GirlController.girlFindOne
2017-11-15 21:38:26.539 INFO 64813 --- [nio-8083-exec-1] com.wz.girl.aspect.HttpAspect : args=1
Hibernate: select girl0_.id as id1_0_0_, girl0_.age as age2_0_0_, girl0_.cup_size as cup_size3_0_0_ from girl girl0_ where girl0_.id=?
2017-11-15 21:38:26.627 INFO 64813 --- [nio-8083-exec-1] com.wz.girl.aspect.HttpAspect : logAfter
2017-11-15 21:38:26.627 INFO 64813 --- [nio-8083-exec-1] com.wz.girl.aspect.HttpAspect : response=Girl{id=1, cupSize='C', age=20}
统一异常处理
现在假设返回统一格式的信息,如下结构所示:
{
"code": 0,
"msg": "成功",
"data": {
"id": 6,
"cupSize": "D",
"age": 18
}
}
所以先需要定义一个Result
类,来表示返回的结果:
public class Result<T> {
//错误码
private Integer code;
//错误信息
private String msg;
//数据
private T data;
//getter setter方法
.....
}
//Result工具类
public class ResultUtil {
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 code, String msg){
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
但如果调用某个方法抛出了某个异常,此时返回的数据格式,可能就会如下:
{
"timestamp": 1510755505374,
"status": 404,
"error": "Not Found",
"exception": "java.lang.Exception",
"message": "你可能还在上小学",
"path": "/girl/girls/getAge/4"
}
由此可见,需要一种统一处理异常的方式,来返回相同格式的数据,示例如下:
1.创建一个枚举,对应错误码和错误信息
public enum ResultEnum {
UNKONW_ERROR(-1, "未知错误"),
SUCCESS(0, "成功"),
PRIMARY_SCHOOL(100, "你可能还在上小学"),
MIDDLE_SCHOOL(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;
}
}
2.自定义一个GirlException
异常类,Spring只会捕捉RuntimeException
异常,所以要继承RuntimeException
public class GirlException extends RuntimeException{
private Integer code;
public GirlException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
3.在某个方法抛出异常
public void getAge(Integer id) throws Exception{
Girl girl = girlReponsitory.findOne(id);
Integer age = girl.getAge();
if (age < 10){
throw new GirlException(ResultEnum.PRIMARY_SCHOOL);
}else if(age > 10 && age < 16){
throw new GirlException(ResultEnum.MIDDLE_SCHOOL);
}
}
4.新建一个异常处理类ExceptionHandle
,捕获异常,返回自定义结果。在这里统一处理异常
@ControllerAdvice
public class ExceptionHandle {
private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
@ExceptionHandler(value = GirlException.class)
@ResponseBody
public Result handler(Exception e){
if (e instanceof GirlException){
GirlException girlException = (GirlException) e;
return ResultUtil.error(girlException.getCode(), e.getMessage());
}
logger.error("[系统异常]{}", e);
return ResultUtil.error(-1, "位置错误");
}
}
@ControllerAdvice
注解用来处理异常:
- Spring中添加@ControllerAdvice增强Controller
- Spring:异常统一处理的三种方式与Rest接口异常的处理
- 使用Spring MVC的@ControllerAdvice注解做Json的异常处理
5.此时,如果调用某个接口抛出了异常,则会返回如下结构的json数据:
{
"code": 101,
"msg": "你可能在上中学",
"data": null
}
单元测试
参考:
Service测试
Service测试,需添加@RunWith(SpringRunner.class)
,@SpringBootTest
注解
@RunWith(SpringRunner.class)
@SpringBootTest
public class GirlServiceTest {
@Autowired
private GirlService girlService;
@Test
public void findOneTest(){
Girl girl = girlService.findOne(1);
Assert.assertEquals(new Integer(20), girl.getAge());
}
}
Controller测试
创建单元测试还可以右键,有IDE来生成单元测试类:
需额外添加@AutoConfigureMockMvc
注解
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class GirlControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void girlList() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/girls"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("abc"));
}
}
其它
教程: