springboot 二、AOP统一处理请求日志 三、统一异常处理

SpringBoot与Web

要求禁止添加未满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);
}
}