SpringBoot之基于拦截器和AOP实现统一缓存处理

前言

最近公司在做一个数据展示项目,很多数据都是存放在每日库里,然而数据库分库查询的性能很低,所以想到用拦截器和AOP对数据接口做一个统一的缓存处理。

Redis缓存的Key初步设计为请求的uri,value为数据的list集合。

1、自定义拦截器拦截请求

拦截器拦截请求,先查询Redis是否有该接口对应的缓存,若没有则放行,查询数据库后将数据存入Redis并返回,若存在则直接从缓存读出数据。

当currentPage为1时,则认为该接口的查询条件改变,表示缓存数据无效,需要查询数据库。

/**
 * @description: 缓存拦截器
 * @author: panyu
 * @create: 2019-04-25 16:22
 **/
@Component
public class RedisInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        // 返回json对象
        // 获取请求路径
        String path = request.getServletPath();
        List data = (ArrayList)redisUtil.get("/xxx"+path);
        System.out.println(path);
        // 从Request中读出数据流,并解决只能读一次导致Controller接收不到数据的问题
        JSONObject requestData = JSON.parseObject(new BodyReaderHttpServletRequestWrapper(request).getSessionStream());
        // 获取当前页,当前页为1时,即更改了查询条件,从数据库查询
        Integer currentPage = (Integer)requestData.get("currentPage");
        Integer pageSize = (Integer)requestData.get("pageSize");
        PrintWriter out = null;
        JSONObject jsonObject = null;
        try {
            jsonObject = new JSONObject();
            if (data != null && currentPage != 1) {// 如果缓存中存在数据,则直接查出返回
                int dataSize = data.size();
                if(dataSize < pageSize) {// 如果数据条数小于页面大小
                    data = data.subList((currentPage - 1) * pageSize, dataSize);
                } else {
                    if(currentPage * pageSize <= dataSize) {// 如果不是最后一页,则显示 页面大小 条
                        data = data.subList((currentPage - 1) * pageSize, currentPage * pageSize);
                    } else {// 如果是最后一页,则不显示 页面大小 条
                        data = data.subList((currentPage - 1) * pageSize, (currentPage - 1) * pageSize + (dataSize % currentPage));
                    }
                }
                jsonObject.put("list", data);
                jsonObject.put("itotal", dataSize);
                jsonObject.put("success", true);
                // 解决中文乱码
                response.setCharacterEncoding("utf-8");
                response.setContentType("text/json;charset=UTF-8");
                out = response.getWriter();
                out.print(jsonObject);
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            jsonObject.put("success", false);
            jsonObject.put("errorMassage", "缓存读取异常");
            return false;
        } finally {
            if(out != null) {
                out.close();
            }
        }
        // 如果不存在缓存,则放行,进行查询后将数据存入数据库
        return true;
    }

}

**注意:**此处有一个问题,由于该项目请求参数为JSON格式,然而Request中的数据流只能读出一次,在拦截器中读取出请求参数后,会导致Controller获取不到Request Body。而又由于@RequestBody注解获取输出参数的方式也是根据流的方式获取的。

解决方案:先读取流,再把流写入。此处摘自

SpringBoot拦截器或过滤器中使用流读取参数后,controller中注解读取不到参数

1.1、自定义Filter读取流,再把流写入

/**
 * @description: 解决Request数据流只能读一次的问题
 * @author: panyu
 * @create: 2019-04-28 11:42
 **/
@Component
@WebFilter(filterName="myFilter",urlPatterns="/*")
public class MyFilter  implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 防止流读取一次后就没有了, 所以需要将流继续写出去
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);

        filterChain.doFilter(requestWrapper, servletResponse);
    }

    @Override
    public void destroy() {
    }
}

1.2、重写HttpServletRequestWrapper

/**
 * @description: 保存流
 * @author: panyu
 * @create: 2019-04-28 11:18
 **/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;

    private final String sessionStream;

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        sessionStream = getBodyString(request);
        body = sessionStream.getBytes(Charset.forName("UTF-8"));
    }

    public String getSessionStream() {
        return sessionStream;
    }

    /**
     * 获取请求Body
     *
     * @param request
     * @return
     */
    public String getBodyString(final ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = cloneInputStream(request.getInputStream());
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }

    /**
     * Description: 复制输入流</br>
     *
     * @param inputStream
     * @return</br>
     */
    public InputStream cloneInputStream(ServletInputStream inputStream) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = inputStream.read(buffer)) > -1) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            byteArrayOutputStream.flush();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        return byteArrayInputStream;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }

}

1.3、在拦截器或过滤器中获取请求参数

// 从Request中读出数据流,并解决只能读一次导致Controller接收不到数据的问题
JSONObject requestData = JSON.parseObject(new BodyReaderHttpServletRequestWrapper(request).getSessionStream());

2、AOP对Controller层方法进行增强

当执行Controller,则表示不存在缓存,需要查询数据库。

对Controller层的方法定义切入点,在方法执行完后判断是否存在list参数(即需要做缓存的数据),若存在则将数据存入Redis并将数据返回。

/**
 * @description: Redis缓存
 * @author: panyu
 * @create: 2019-04-25 17:40
 **/
@Aspect
@Configuration//定义一个切面
public class RedisAspect {

    @Autowired
    private RedisUtil redisUtil;

    @Pointcut("execution(* com.inheritech.gps_data.web.controller..*(..))")
    public void excudeService() {}

    @Around("excudeService()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String queryString = request.getQueryString();
        Object[] args = pjp.getArgs();
        String params = "";
        //获取请求参数集合并进行遍历拼接
        Map paramsMap = null;
        if(args.length>0){
            if("POST".equals(method)){
                Object object = args[0];
                paramsMap = getKeyAndValue(object);
                params = JSON.toJSONString(paramsMap);
            }else if("GET".equals(method)){
                params = queryString;
            }
        }


        System.out.println("请求开始===地址:"+uri);
        System.out.println("请求开始===类型:"+method);
        System.out.println("请求开始===参数:"+params);

        // result的值就是被拦截方法的返回值
        JSONObject result = (JSONObject)pjp.proceed();
        List datas = (ArrayList)result.get("list");
        if(datas != null) {
            redisUtil.set(uri, datas, 1800);
            int dataSize = datas.size();
            Integer currentPage = (Integer)paramsMap.get("currentPage");
            Integer pageSize = (Integer)paramsMap.get("pageSize");
            if(dataSize < pageSize) {// 如果数据条数小于页面大小
                result.put("list", datas.subList((currentPage - 1) * pageSize, dataSize));
            } else {
                if(currentPage * pageSize <= dataSize) {// 如果不是最后一页,则显示 页面大小 条
                    result.put("list", datas.subList((currentPage - 1) * pageSize, currentPage * pageSize));
                } else {// 如果是最后一页,则不显示 页面大小 条
                    result.put("list", datas.subList((currentPage - 1) * pageSize, (currentPage - 1) * pageSize + (dataSize % currentPage)));
                }
            }
        }
        return result;
    }

    public static Map<String, Object> getKeyAndValue(Object obj) {
        Map<String, Object> map = new HashMap<>();
        // 得到类对象
        Class userCla = (Class) obj.getClass();
        /* 得到类中的所有属性集合 */
        Field[] fs = userCla.getDeclaredFields();
        for (int i = 0; i < fs.length; i++) {
            Field f = fs[i];
            f.setAccessible(true); // 设置些属性是可以访问的
            Object val = new Object();
            try {
                val = f.get(obj);
                // 得到此属性的值
                map.put(f.getName(), val);// 设置键值
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
        return map;
    }

}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值