SpringBoot与Web
一、使用@Valid注解进行表单验证 要求禁止添加未满18岁女生的信息。
1、对Girl进行改造,使用@Min注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @Entity public class Girl { @Id @GeneratedValue private Integer id; private String name; private String cupSize; @Min(value = 18,message = "未成年少女禁止入内!") private Integer age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } 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; } public Girl() { } @Override public String toString() { return "Girl{" + "id=" + id + ", name='" + name + '\'' + ", cupSize='" + cupSize + '\'' + ", age=" + age + '}'; } }
注意:在age字段上的@Min(value = 18,message = "未成年少女禁止入内!")注解,其表示该字段的最小值为value,提示信息为message。
2、对Controller进行简单改造:
1 2 3 4 5 6 7 8 9 10 11 12 @PostMapping(value = "/girls") public Girl addGirl(@Valid Girl girl, BindingResult result){ if (result.hasErrors()){ System.out.println(result.getFieldError().getDefaultMessage()); return null; } System.out.println(girl); // girl.setCupSize(girl.getCupSize()); // girl.setName(girl.getName()); // girl.setAge(girl.getAge()); return girlRepository.save(girl); }
其中提交的信息为Girl对象,且对该对象进行验证,不通过时的错误信息放在BindingResult对象中。
3、至此,改造完成,此时就可以禁止提交未满18岁女生的信息了。
二、AOP统一处理请求日志 重谈AOP:AOP是一种编程范式,与语言无关,是一种程序设计思想。
关于面向切面编程的一些术语:
(1)切面(Aspect):切面用于组织多个Advice。Advice放在切面中定义;
(2)、连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或是异常的抛出。在Spring AOP中,连接的总是方法的调用;
(3)、增强处理(Advice):AOP框架在特定的切入点执行的增强处理。处理有“around”、“before”和“after”;
(4)、切入点(Pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定的要求时,该连接的将被添加增强处理,该连接的也就变成了切入点。
下面以记录每一个Http请求来实例讲解spring-boot中的AOP。 1、添加依赖
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2、新建切面
1 2 3 4 5 6 7 8 9 10 @Aspect @Component public class HttpAspect { @Before("execution(public * com.njupt.girl.controller.GirlController.*(..))") public void log() { System.out.println("新的HTTP请求到来!"); } }
注意:@Aspect:表示该类是一个切面,即增强功能
@Component:使该类能被spring容器扫描到
@Before:表示该方法在所有的方法执行之前执行,注意:使方法执行之前。
3、至此当所有的方法执行之前都会执行。当然还可以添加@After注解表示在所有方法执行之后执行。
1 2 3 4 @After("execution( * com.njupt.girl.controller.GirlController.*(..))") public void afterlog(){ System.out.println("一个http请求结束!"); }
4、但是如果方法需求躲多起来,每次都要写切点表达式也是很不方便,且记日志不能依靠 System.out.println打印,应该用spring提供的log框架。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Aspect @Component public class HttpAspect { private static final Logger log = LoggerFactory.getLogger(HttpAspect.class); @Pointcut("execution(public * com.njupt.girl.controller.GirlController.*(..))") public void log(){ } @Before("log()") public void deforelog() { log.info("一个新的HTTP请求到来!"); } @After("log()") public void afterlog(){ log.info("一个HTTP请求结束!"); } }
后台打印信息为:
1 2 3 4 2018-06-27 11:14:34.540 INFO 13560 --- [nio-8080-exec-1] com.njupt.girl.aspect.HttpAspect : 一个新的HTTP请求到来! 2018-06-27 11:14:34.572 INFO 13560 --- [nio-8080-exec-1] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory Hibernate: select girl0_.id as id1_0_, girl0_.age as age2_0_, girl0_.cup_size as cup_size3_0_, girl0_.name as name4_0_ from girl girl0_ 2018-06-27 11:14:34.640 INFO 13560 --- [nio-8080-exec-1] com.njupt.girl.aspect.HttpAspect : 一个HTTP请求结束!
5、若希望log中能记录更多的类信息:url、method、参数等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Aspect @Component public class HttpAspect { private static final Logger log = LoggerFactory.getLogger(HttpAspect.class); @Pointcut("execution(public * com.njupt.girl.controller.GirlController.*(..))") public void log(){ } @Before("log()") public void deforelog(JoinPoint joinPoint) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); //url log.info("url={}",request.getRequestURL()); //method log.info("method={}",request.getMethod()); //ip log.info("ip={}",request.getRemoteAddr()); //类方法 log.info("class_method={}",joinPoint.getSignature().getDeclaringType()+"."+joinPoint.getSignature().getName()); //args log.info("arg={}",joinPoint.getArgs()); } @After("log()") public void afterlog(){ log.info("一个HTTP请求结束!"); } //返回结构 @AfterReturning(returning = "object",pointcut = "log()") public void afterReturn(Object object){ log.info("response={}",object); } }
@AfterReturning(returning = "object",pointcut = "log()"):即返回通知,返回方法响应结果。
三、统一异常处理 要求:对于向前台返回值的统一处理:要求返回结果的字段有:msg,data,code。
1、首先,新建一个Result的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Result<T> { /** * 状态码 */ private Integer code; /** * 提示信息 */ private String msg; /** * 数据 */ private T 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; } }
2、再新建一个工具,里面包含success方法与error方法,用于统一处理,复用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ResultUtil { public static Result success(Object object) { Result result = new Result(); result.setCode(0); result.setMsg("success"); result.setData(object); return result; } public static Result success() { return success(null); } public static Result error(Integer code,String message){ Result result = new Result(); result.setCode(code); result.setMsg(message); return result; } }
在此调用时就能统一返回值了。
3、现在有一个要求,当通过id查询age时,如果年纪小于10,抛出异常,当年纪在10~16之间时抛出异常:
(1)、在service中新增一个方法:
1 2 3 4 5 6 7 8 9 10 11 12 public void getAge(Integer id) throws Exception { Optional<Girl> girl = girlRepository.findById(id); Girl g = girl.get(); Integer age = g.getAge(); if (age<10) { throw new Exception("小学生"); }else if (age>10&&age<16) { throw new Exception("中学生"); } }
(2)、在controller中新增一个接口:
1 2 3 4 @GetMapping(value = "/girls/age/{id}") public void getAge(@PathVariable("id") Integer id) throws Exception { girlService.getAge(id); }
(3)、新增一个异常处理类,对异常进行统一处理:
1 2 3 4 5 6 7 8 9 @ControllerAdvice public class ExceptionHandle { @ExceptionHandler(value = Exception.class) @ResponseBody public Result<Girl> handler(Exception e) { return ResultUtil.error(100,e.getMessage()); } }
于是就可以调用接口localhost:8080/girls/age/14就可以获得如下返回:
1 2 3 4 5 { "code": 100, "msg": "中学生", "data": null }
4、自定义异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class GirlException extends RuntimeException { private static final long serialVersionUID = -3276872016474962920L; private Integer code; public GirlException(Integer code,String message) { super(message); this.code = code; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } }
注意:此处应该继承RuntimeException,否则不支持事务。
修改ExceptionHandle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @ControllerAdvice public class ExceptionHandle { @ExceptionHandler(value = Exception.class) @ResponseBody public Result<Girl> handler(Exception e) { if (e instanceof GirlException) { GirlException girlException = (GirlException) e; return ResultUtil.error(((GirlException) e).getCode(),e.getMessage()); } return ResultUtil.error(-1,"未知错误"); } }
修改Service
1 2 3 4 5 6 7 8 9 10 11 12 public void getAge(Integer id) throws Exception { Optional<Girl> girl = girlRepository.findById(id); Girl g = girl.get(); Integer age = g.getAge(); if (age<10) { throw new GirlException(101,"小学生"); }else if (age>10&&age<16) { throw new GirlException(102,"中学生"); } }
5、自定义个枚举类统一管理code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public enum ResultEnum { UNKNOW_ERROR(-1,"未知错误"), SUCESS(0,"成功"), PRIMARY_SCHOOL(101,"小学生"), MIDDLE_SCHOOL(102,"中学生"); private Integer code; private String meg; ResultEnum(Integer code, String meg) { this.code = code; this.meg = meg; } public Integer getCode() { return code; } public String getMeg() { return meg; } }
对其他相应地方的修改:
自定义异常的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class GirlException extends RuntimeException { private static final long serialVersionUID = -3276872016474962920L; private Integer code; public GirlException(ResultEnum resultEnum) { super(resultEnum.getMeg()); this.code = resultEnum.getCode(); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } }
service的修改:
1 2 3 4 5 6 7 8 9 10 11 12 public void getAge(Integer id) throws Exception { Optional<Girl> girl = girlRepository.findById(id); Girl g = girl.get(); Integer age = g.getAge(); if (age<10) { throw new GirlException(ResultEnum.PRIMARY_SCHOOL); }else if (age>10&&age<16) { throw new GirlException(ResultEnum.MIDDLE_SCHOOL); } }