SpringBoot知识点一

标签: Springboot 分类: Java 创建时间:2020-09-16 01:38:15 更新时间:2025-01-17 10:39:22

1.前言

本章主要讲解一些在开发中常用的技术:过滤器、拦截器、消息转换器和 ResponseBodyAdvice,这几个部分都能修改返回值,我觉得有相同的地方。

(1)关于过滤器和拦截器的区别,spring boot 过滤器、拦截器的区别与使用 这篇文章写的很清楚了。主要有以下几个不同:

  • 使用范围不同
  • 规范不同
  • 使用的资源不同
  • 深度不同

(2)实现了 ResponseBodyAdvice 这个接口的类,处理返回的json值在传递给HttpMessageConverter之前

参考文章:
【1】.解决Spring Boot 拦截器注入service为空的问题 (这里有两个地方,一个是拦截器上加入@Compent注解,一个是WebMvcConfigurer配置中用@Bean注入拦截器)
【2】.SpringBoot统一响应,拦截Response返回 ResponseBodyAdvice接口

2.拦截器

(1) 编写拦截器

(2) 注入拦截器,在SpringMVC中的 Interceptor拦截器才用实现 HandlerInterceptor的方式,实现其中的三个方法。

  • preHandle():
    该方法在请求处理之前执行,SpringMVC 中的Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法。

  • postHandle():
    该方法在请求处理之后执行,但是他会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的 ModeAndView 对象进行操作.需要注意的是,如果 Controller 执行过程中出现了异常,那么并不会执行该方法,而是执行 afterCompletion方法。

  • afterCompletion():
    在postHandle执行之后执行,发生异常也会执行,通常用于释放系统资源。

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
@Slf4j
@Component
public class JsonConvertInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ...");
// ContentCachingResponseWrapper responseWrapper=(ContentCachingResponseWrapper) response;
// WrapperedResponse wrapResponse = new WrapperedResponse(response);
// // 获取请求的返回值
// byte[] data = wrapResponse.getResponseData();
// String responseBody = new String(data, StandardCharsets.UTF_8);
// log.info("原始返回数据: " + responseBody);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
System.out.println("afterCompletion ...");
}

}

需要在WebMvcConfigurer 中注册。

参考文章:
【1】.Spring Boot 配置拦截器 这里可以参考
【2】.【springBoot】springBoot配置拦截器
【3】.springboot HandlerIntercepter拦截器实现修改request body数据
【4】.SpringBoot 拦截器中校验Json数据 为什么使用RequestBody只能读取一遍请求数据流?那是因为流对应的是数据,数据放在内存中,有的是部分放在内存中。read 一次标记一次当前位置(mark position),第二次read就从标记位置继续读(从内存中copy)数据。 所以这就是为什么读了一次第二次是空了。 怎么让它不为空呢?只要inputstream 中的pos 变成0就可以重写读取当前内存中的数据。javaAPI中有一个方法public void reset() 这个方法就是可以重置pos为起始位置,但是不是所有的IO读取流都可以调用该方法!ServletInputStream是不能调用reset方法,这就导致了只能调用一次getInputStream()。

问题

(1)拦截器不生效
我在测试拦截器的时候,出现了部分路径无法匹配的问题,后来我还是找到了解决方法。

1
2
3
4
5
6
7
8
9
10
11
@Autowired
private AuthInterceptor authInterceptor; // 认证拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 不生效
registry.addInterceptor(authInterceptor).addPathPatterns("**/api/**")
// 生效
registry.addInterceptor(authInterceptor).addPathPatterns("/**")
// 生效
registry.addInterceptor(authInterceptor).addPathPatterns("/**/api/v1/**")
}
参考文章:
1.SpringBoot添加Interceptor后addInterceptors方法不执行,拦截器不生效
2.Spring PathPattern-路径匹配器【超详细】 ** 只支持在模式的末尾,例如/pages/{ ** }是有效的,但是/pages/{**}/details是无效的。这同样适用于捕获变量{*spring}。目的是在比较模式的特异性时消除歧义。
3.spring mvc路径匹配原则 这里的匹配规则倒是挺多的。
4.过滤器和拦截器的路径匹配 这里是源码的讲解,倒是没有仔细的看了。

3.过滤器

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
@Slf4j
public class DataFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
String contentType = request.getContentType();
WrapperedResponse wrapResponse = new WrapperedResponse((HttpServletResponse) response);

chain.doFilter(request, wrapResponse);

byte[] data = wrapResponse.getResponseData();
String responseBody = new String(data, StandardCharsets.UTF_8);
log.info("原始返回数据: " + responseBody);
// 返回报文
String responseBodyStr = responseBody;
log.info("处理后返回数据: " + responseBodyStr);
writeResponse(response, responseBodyStr);
}

@Override
public void destroy() {

}

private void writeResponse(ServletResponse response, String responseString) throws IOException {
response.setContentLength(-1);
PrintWriter out = response.getWriter();
out.print(responseString);
out.flush();
out.close();
}
}
参考文章:
1.SpringBoot 拦截并修改某个接口地址的返回值 这里用了一个过滤器进行了过滤,重写了 HttpServletResponseWrapper 方法,获取内容。
2.SpringBoot利用Filter获取请求数据request和修改返回response中的数据

4.ResponseBodyAdvice

在返回前端之前可以修改返回值,我使用了 jackson 的命名策略,使用 消息转换器,使用拦截器无法获取返回的内容,配置了过滤器,还没有实现,最后我通过 使用 ResponseBodyAdvice,更方便的解决了问题。

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
package com.openmap.drone.configuration;

import com.sun.istack.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@ControllerAdvice
public class InterceptResponse implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter,
Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Nullable
@Override
public Object beforeBodyWrite(@Nullable Object body, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
ServletServerHttpResponse responseTemp = (ServletServerHttpResponse) serverHttpResponse;
HttpServletResponse resp = responseTemp.getServletResponse();
ServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;
HttpServletRequest req = sshr.getServletRequest();
return body;
}
}

实现了这个接口的类,处理返回的json值在传递给HttpMessageConverter之前。

参考文章:
【1】.spring boot postHandle处理返回结果 这篇文章错了,postHandle是在controller处理完成之后执行的,这里显示的是处理之前执行。
【2】.SpringBoot通过拦截器获取请求参数和返回结果进行操作 这里提到了 拦截器、过滤器,最后使用了 ResponseBodyAdvide 方法
【3】.SpringBoot利用Filter获取请求数据request和修改返回response中的数据 这里是用的过滤器,这个方法我还没有尝试过。
【4】.spring boot 拦截器 修改response 这篇文章提到了 ContentCachingResponseWrapper 这个包装器,但是我尝试之后报错了。
【5】.SpringBoot 拦截器中校验Json数据
【6】.SpringMVC进阶 - 利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值
【7】.spring mvc之@ResponseBodyAdvice使用

5.消息转换器

除了上面的过滤器和拦截器之外,还有消息转换器可以进行返回值的处理。参考文章5写的挺明白的,使用转换器的时候,有下面的注意事项:

(1)系统有默认配置的消息转换器集合。

(2)处理过程会按集合顺序匹配合适的消息转换器,如果有合适的,就会使用该消息转换器处理(读、写),后续的消息转换器不再执行。

(3)自定义的消息转换器要想生效,必须放到集合中相同类型的消息转换器前面,原因参考第二点。
思考:既然自定义的消息转换器必须放到集合中相同类型的消息转换器前面,那是否能直接改动集合中原有的消息转换器来达到自定义的效果,而不必在加一个(暂未没研究)。

(4)添加自定义消息转换器时注意默认消息转换器是否生效

WebMvcConfigurer.configureMessageConverters方法会覆盖默认消息转换器集合
WebMvcConfigurer.extendMessageConverters方法不会覆盖默认消息转换器集合

我们继承了泛型类AbstractHttpMessageConverter,并重写了它的几个方法。

  • supports:设置处理哪些类。

  • readInternal:处理输入内容,即处理请求值。

  • writeInternal:处理输出内容,即处理返回值。

  • 构造方法定义了要处理的MediaType。

    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
    public class ResultMessageConverter extends AbstractHttpMessageConverter<Object> {

    public ResultMessageConverter(){
    super(new MediaType("application", "json", Charset.forName("UTF-8")));
    }


    @Override
    protected boolean supports(Class<?> clazz) {
    return HttpResultResponse.class.isAssignableFrom(clazz);
    // return true;
    }


    @Override
    protected void writeInternal(Object httpResultResponse, HttpOutputMessage outputMessage)
    throws IOException, HttpMessageNotWritableException {
    ObjectMapper objectMapper = new ObjectMapper();
    //处理bigDecimal
    objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
    objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
    //处理失败
    objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
    objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false);
    //默认的处理日期时间格式
    objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
    javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
    javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
    javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
    objectMapper.registerModule(javaTimeModule);

    // 命名策略,下划线
    objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
    // 执行消息转换
    String dataStr=objectMapper.writeValueAsString(httpResultResponse);
    byte[] bytes = dataStr.getBytes(StandardCharsets.UTF_8);
    // 写入返回值
    outputMessage.getBody().write(bytes);
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz,
    HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    String source = StreamUtils.copyToString(inputMessage.getBody(),Charset.forName("UTF-8"));
    return new ObjectMapper().readValue(source, clazz);
    }
    }
参考文章:
1.SpringBoot 消息转换器 HttpMessageConverter
2.Spring Boot项目中如何定制HTTP消息转换器
3.SpringBoot 拦截器中校验Json数据
4.springboot HandlerIntercepter拦截器实现修改request body数据
5.SpringBoot消息转换器:HttpMessageConverter 消息转化器的作用;消息转化器的主要方法;默认配置的消息转化器;注意事项
6.Spring mvc 之 AbstractHttpMessageConverter 自定义Http消息转化器 只是类型为 text/html 为了解决该问题,可以指定Http的消息转化器,来解析
7.一步到位 SpringBoot 序列化与消息转换器 (你需要的这里都有) 序列化与反序列化;Jackson之ObjectMapper对象的使用;ObjectMapper常用API;配置ObjectMapper;添加自定义的消息转换器
8.自定义HttpMessageConverter 按这个写了。
9.Spring Boot之MessageConverter原理及自定义消息转换器 这个写了原理
10.【Spring】HttpMessageConverter的作用及替换
11.SpringMVC自定义消息转换器其实很简单
小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。