Redis之拦截器实现缓存命中
相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--其他工具包依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
拦截器
@Component
public class RedisCacheInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Value("${redis.cache.enable}")
private Boolean enable;
private static final String REDIS_CACHE_DATA_KEY = "cache:data:";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//缓存开关
if (!enable) {
return true;
}
// 对GET、POST的请求进行缓存处理
String method = request.getMethod();
if (!StringUtils.equalsAnyIgnoreCase(method, "GET", "POST")) {
return true;
}
// 生成缓存key
String redisKey = createRedisKey(request);
// 根据缓存key查询redis
String data = this.redisTemplate.opsForValue().get(redisKey);
if (StringUtils.isEmpty(data)) {
return true;
}
// 响应数据
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(data);
return false;
}
/**
* (请求url + 请求参数)生产MD5
*
* @param request
* @return
* @throws Exception
*/
public static String createRedisKey(HttpServletRequest request) throws Exception {
String paramStr = request.getRequestURI();
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap.isEmpty()) {
// 通过流的形式读取请求体数据
paramStr += IOUtils.toString(request.getInputStream(), "UTF-8");
} else {
paramStr += new ObjectMapper().writeValueAsString(request.getParameterMap());
}
String token= request.getHeader("Token");
if (StringUtils.isNotEmpty(token)) {
paramStr += "_" + token;
}
return REDIS_CACHE_DATA_KEY + DigestUtils.md5Hex(paramStr);
}
}
注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RedisCacheInterceptor redisCacheInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.redisCacheInterceptor).addPathPatterns("/**");
}
}
响应结果写入缓存
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//支持的处理类型,仅对get和post类型的注解标识方法进行处理
return returnType.hasMethodAnnotation(GetMapping.class) || returnType.hasMethodAnnotation(PostMapping.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
try {
// 根据请求生成缓存key
String redisKey = RedisCacheInterceptor.createRedisKey(((ServletServerHttpRequest) request).getServletRequest());
String redisValue;
if (body instanceof String) {
redisValue = (String) body;
} else {
redisValue = new ObjectMapper().writeValueAsString(body);
}
//写入缓存数据
this.redisTemplate.opsForValue().set(redisKey, redisValue, Duration.ofHours(1));
} catch (Exception e) {
e.printStackTrace();
}
return body;
}
}
执行测试
@RestController
@Slf4j
public class TestController {
@GetMapping("/test1")
public String test1(@RequestParam String data, HttpServletRequest request) throws JsonProcessingException {
log.info(data);
log.info("request:{}",request.getParameterMap());
return "test success";
}
@PostMapping("/test2")
public String test2(@RequestBody String data, HttpServletRequest request) throws JsonProcessingException {
log.info(data);
log.info("request:{}",request.getParameterMap());
return "test success";
}
}
请求test1接口,生成缓存数据,多次访问,直接走缓存数据
请求test2接口,报错
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public java.lang.String com.example.springboot.controller.TestController.test2(java.lang.String,javax.servlet.http.HttpServletRequest)]
通过流的形式读取请求体数据后,流的通道关闭,即输入流只能读取一次,再次获取则失败
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap.isEmpty()) {
// 通过流的形式读取请求体数据
paramStr += IOUtils.toString(request.getInputStream(), "UTF-8");
} else {
paramStr += new ObjectMapper().writeValueAsString(request.getParameterMap());
}
对HttpServletRequest包装
public class MyServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public MyServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = IOUtils.toByteArray(super.getInputStream());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new RequestBodyCachingInputStream(body);
}
private class RequestBodyCachingInputStream extends ServletInputStream {
private byte[] body;
private int lastIndexRetrieved = -1;
private ReadListener listener;
public RequestBodyCachingInputStream(byte[] body) {
this.body = body;
}
@Override
public int read() throws IOException {
if (isFinished()) {
return -1;
}
int i = body[lastIndexRetrieved + 1];
lastIndexRetrieved++;
if (isFinished() && listener != null) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
throw e;
}
}
return i;
}
@Override
public boolean isFinished() {
return lastIndexRetrieved == body.length - 1;
}
@Override
public boolean isReady() {
return isFinished();
}
@Override
public void setReadListener(ReadListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cann not be null");
}
if (this.listener != null) {
throw new IllegalArgumentException("listener has been set");
}
this.listener = listener;
if (!isFinished()) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
} else {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
}
}
@Override
public int available() throws IOException {
return body.length - lastIndexRetrieved - 1;
}
@Override
public void close() throws IOException {
lastIndexRetrieved = body.length - 1;
body = null;
}
}
}
替换Request对象
@Component
public class RequestReplaceFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (!(request instanceof MyServletRequestWrapper)) {
request = new MyServletRequestWrapper(request);
}
filterChain.doFilter(request, response);
}
}
包装Request后测试
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137035.html