前言
在接口服务中,如果每个接口单独添加缓存的话会存在很多的重复的逻辑,所以通过拦截器可以简化。
- 通过拦截器实现对请求的拦截,在拦截器中实现缓存的命中。
- 缓存的处理中,仅针对
GET
请求处理,
一、使用步骤
1.引入库
使用redis做缓存,引入坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. application
redis:
host: 192.X.X.X
port: 6379
# password: root@123456
database: 0 #使用0号数据库
#是否开启数据缓存
cache:
enable: true
3. 自定义注解
代码如下(示例):
import java.lang.annotation.*;
/**
* 被标记为Cache的Controller进行缓存,其他情况不进行缓存
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented //标记注解
public @interface Cache {
/**
* 缓存时间,默认为60秒
* @return
*/
String time() default "60";
}
4. 响应结果写入到缓存
import com.fasterxml.jackson.databind.ObjectMapper;
import net.fff.rege.annotation.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.concurrent.TimeUnit;
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {
@Value("${cache.enable}")
private Boolean enable;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 开关处于开启状态 是get请求 包含了@Cache注解
return enable && returnType.hasMethodAnnotation(GetMapping.class)
&& returnType.hasMethodAnnotation(Cache.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (null == body) {
return null;
}
try {
String redisValue = null;
if (body instanceof String) {
redisValue = (String) body;
} else {
redisValue = MAPPER.writeValueAsString(body);
}
String redisKey = RedisCacheInterceptor.createRedisKey(((ServletServerHttpRequest) request).getServletRequest());
Cache cache = returnType.getMethodAnnotation(Cache.class);
//缓存的时间单位是秒
this.redisTemplate.opsForValue().set(redisKey, redisValue, Long.valueOf(cache.time()), TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
}
return body;
}
5. 采用拦截器进行缓存获取
import com.fasterxml.jackson.databind.ObjectMapper;
import net.fff.rege.annotation.Cache;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class RedisCacheInterceptor implements HandlerInterceptor {
@Value("${cache.enable}")
private Boolean enable;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//缓存的全局开关的校验
if (!enable) {
return true;
}
//校验handler是否是HandlerMethod
if (!(handler instanceof HandlerMethod)) {
return true;
}
//判断是否为get请求
if (!((HandlerMethod) handler).hasMethodAnnotation(GetMapping.class)) {
return true;
}
//判断是否添加了@Cache注解
if (!((HandlerMethod) handler).hasMethodAnnotation(Cache.class)) {
return true;
}
//缓存命中
String redisKey = createRedisKey(request);
String cacheData = this.redisTemplate.opsForValue().get(redisKey);
if(StringUtils.isEmpty(cacheData)){
//缓存未命中
return true;
}
// 将data数据进行响应
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(cacheData);
return false;
}
/**
* 生成redis中的key,规则:SERVER_CACHE_DATA_MD5(url + param + token)
*
* @param request
* @return
*/
public static String createRedisKey(HttpServletRequest request) throws Exception {
String url = request.getRequestURI();
String param = MAPPER.writeValueAsString(request.getParameterMap());
String token = request.getHeader("Authorization");
String data = url + "_" + param + "_" + token;
return "SERVER_CACHE_DATA_" + DigestUtils.md5Hex(data);
}
}
6. 注册拦截器到Spring容器
import net.fff.rege.interceptor.RedisCacheInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RedisCacheInterceptor redisCacheInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.redisCacheInterceptor).addPathPatterns("/**");
}
}
7. 测试
@GetMapping("/page")
@Cache(time = "90")
public Result getInfoByPage(int page, int pageSize) {
//分页查询员工信息
IPage pageInfo = catService.getInfoByPage(page, pageSize);
return Result.success(pageInfo);
}