0%

Controller层 Note

对Controller层代码进行优化

对Controller层代码优化

Controller层的一些优化方式

统一返回类包装

返回结果枚举类

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
package com.wakaka.comments.common;

/**
* 返回结果枚举类
* @author Yancy0109
*/
public enum ResultCode implements StatusCode{
/**
* 请求成功
*/
SUCCESS(1000,"请求成功"),
/**
* 请求失败
*/
FAILED(1001,"请求失败"),
/**
* 参数校验失败
*/
VALIDATE_ERROR(1002,"参数校验失败");

private final int code;
private final String message;



ResultCode(int code, String message) {
this.code = code;
this.message = message;
}

@Override
public int getCode() {
return this.code;
}

@Override
public String getMsg() {
return this.message;
}
}

统一返回包装类

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package com.wakaka.comments.common;

import lombok.Data;

/**
* ResultVo
* @author Yancy0109
*/
@Data
public class ResultVo {
/**
* 状态码
*/
private int code;
/**
* 状态信息
*/
private String msg;
/**
* 返回信息
*/
private Object data;

/**
* 自定义返回信息
*/
public ResultVo(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}

/**
* 默认成功返回
*/
public static ResultVo Success() {
return new ResultVo(
ResultCode.SUCCESS.getCode(),
ResultCode.SUCCESS.getMsg(),
null
);
}

/**
* 附带结果成功返回
*/
public static ResultVo Success(Object data) {
return new ResultVo(
ResultCode.SUCCESS.getCode(),
ResultCode.SUCCESS.getMsg(),
data
);
}

/**
* 默认失败返回
*/
public static ResultVo Failed() {
return new ResultVo(
ResultCode.FAILED.getCode(),
ResultCode.FAILED.getMsg(),
null
);
}

/**
* 自定义失败信息返回
*/
public static ResultVo Failed(String msg) {
return new ResultVo(
ResultCode.FAILED.getCode(),
msg,
null
);
}

/**
* 参数校验失败返回
*/
public static ResultVo VALIDATE_ERROR() {
return new ResultVo(
ResultCode.VALIDATE_ERROR.getCode(),
ResultCode.VALIDATE_ERROR.getMsg(),
null
);
}
}


自动封装结果

但是我们不能把所有接口都自动封装,应该有一个判断

通过注解对接口接口是否包装进行判断

1
2
3
4
5
// 不包装返回注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotControllerResponseAdvice {
}

继承ResponseBodyAdvice抽象类,可以通过AOP自动对返回结果包装

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
@RestControllerAdvice(value = "com.wakaka.comments")
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
//如果返回值response为ResultVo类,或注释了NotControllerAdvice则不包装
boolean flag = returnType.getParameterType().isAssignableFrom(ResultVo.class)
//获得返回方法是否包含注解
|| returnType.hasMethodAnnotation(NotControllerResponseAdvice.class);
return !flag;
}

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//String类型不能直接包装
if (returnType.getGenericParameterType().equals(String.class)){
ObjectMapper objectMapper = new ObjectMapper();
try {
//将数据转换为json字符串返回
return objectMapper.writeValueAsString(new ResultVo(
ResultCode.SUCCESS.getCode(),
ResultCode.SUCCESS.getMsg(),
body
));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return new ResultVo(
ResultCode.SUCCESS.getCode(),
ResultCode.SUCCESS.getMsg(),
body
);
}
}

参数校验

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Controller方法

使用@Validated注解

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
/**
* 测试Controller
* @author yancy0109
*/
@RestController
public class TestController {

@PostMapping("/findByVo")
public ResultVo findProdcut(@Validated ProductInfoVo vo){

return ResultVo.Success(vo);
}
@PostMapping("/test2")
public String test2(){

return "sadsd";
}
@PostMapping("/test3")
@NotControllerResponseAdvice
public String test3(){

return "sadsd";
}
@PostMapping("/test4")
public String test4(){
throw new ApiException(
"测试"
);
}
@PostMapping("/test5")
public String test5(){
throw new ApiException(
ResultCode.FAILED,"测试"
);
}
}

实体类

例如:@NotNull,@Min注解;message为BindException返回信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 测试实体
* @author yancy0109
*/
@Data
public class ProductInfoVo {

/**
* 商品名称
*/
@NotNull(message = "商品名称不允许为空")
private String productName;

/**
* 商品价格
*/
@Min(value = 0, message = "商品价格不允许为负数")
private BigDecimal productPrice;

/**
* 上架状态
*/
private Integer productStatus;
}

异常处理

在加入上面参数验证注解后,不符合的请求会抛出异常,这样前端则无法接收到正常的信息,所以我们要对异常进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestControllerAdvice
public class ControllerExceptionAdvice {

//Controller层可以加入参数校验
//不符合便会抛出BindException异常
@ExceptionHandler(BindException.class)
public ResultVo methodArgumentNotValidExceptionHandler(BindException e){
//从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return new ResultVo(
ResultCode.VALIDATE_ERROR.getCode(),
objectError.getDefaultMessage(),
null
);
}
}

@RestControllerAdvice

异常处理

AOP思想,在Controller层对异常进行处理

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
/**
* 异常处理类
* @author yancy0109
*/
@RestControllerAdvice
public class ControllerExceptionAdvice {

//Controller层可以加入参数校验
//不符合便会抛出BindException异常
@ExceptionHandler(BindException.class)
public ResultVo methodArgumentNotValidExceptionHandler(BindException e){
//从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return new ResultVo(
ResultCode.VALIDATE_ERROR.getCode(),
objectError.getDefaultMessage(),
null
);
}

@ExceptionHandler(ApiException.class)
public ResultVo apiExceptionHandler(ApiException e){
return new ResultVo(
e.getCode(),
e.getMsg(),
e.getMessage()
);
}
}

异常类例

在方法内我们抛出了自定义异常,其实这个是无所谓的,我们可以自定义

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
/**
* 自定义ApiException
* @author yancy0109
*/
@Getter
public class ApiException extends RuntimeException{
private final int code;
private final String msg;

/**
* 自定义信息异常
*/
public ApiException(String message){
super(message);
this.code = AppCode.APP_ERROR.getCode();
this.msg = AppCode.APP_ERROR.getMsg();
}

/**
* 自定义错误状态码异常与异常信息
*/
public ApiException(StatusCode statusCode, String message){
super(message);
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
}
}

HandlerInterceptor

权限校验

Token啊巴拉巴拉 ,先跳过了

获得请求时间

RequestMappingHandlerMapping

抽象类

介绍

它会在DispatherServlet初始化过程自动加载,默认会自动加载所有实现HandlerMapping接口的bean。

1
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType)

会对每个方法进行扫描,我们可以在方法上加入注解,实现一些特定功能

使用示例

跳过HandlerInterceptor

示例注解

1
2
3
4
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassUrl {
}

我们首先对RequestMappingHandlerMapping方法内的getMappingForMethod方法进行重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class TestMethodHandler extends RequestMappingHandlerMapping {

@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
//查询注解是否存在
PassUrl annotation = AnnotationUtils.findAnnotation(method, PassUrl.class);
if (annotation != null){
//如果存在,就加入到TestInterceptor的static属性中
Set<String> patterns = mappingForMethod.getPatternsCondition().getPatterns();
TestInterceptor.addPassUrl(patterns);
}
return mappingForMethod;
}
}

TestIntercptor,记得注在WebMvcConfigurer实现类内注册拦截器

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
public class TestInterceptor implements HandlerInterceptor {

private static Set<String> passUrl = new HashSet<>();

public static void addPassUrl(Set<String> passUrls) {
TestInterceptor.passUrl.addAll(passUrls);
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestUrl = request.getRequestURI();
System.out.println(requestUrl);
System.out.println(passUrl);
if (passUrl.contains(requestUrl)){
//如果这个接口包含URI,则通过
return true;
}
//否则返回异常访问
ResultVo result = ResultVo.Failed("非法访问");
String s = JSONObject.toJSONString(result);
response.getOutputStream().write(s.getBytes(StandardCharsets.UTF_8));
//这段其实我们也可以改为权限验证,这样这可以实现部分接口需要权限验证,而一些接口不需要验证即可访问
return false;
}
}

ThreadLocal

为每一个线程开辟单独存储空间,线程之间相互隔离

使用

  • 存储每次请求的信息

注意

在SpringBoot中并不会创建新的线程,而是重复使用线程池中的线程,所以在每次请求声明周期结束后,要清空ThreadLocal中的数据.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class LoginHandler implements HandlerInterceptor {

static ThreadLocal<String> threadLocal = new ThreadLocal<>();

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

threadLocal.set(request.getParameterMap().toString());
return HandlerInterceptor.super.preHandle(request, response, handler);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
threadLocal.remove();
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}