springmvc controller 面向切面编程,实现数据查询的缓存功能

来源:互联网 发布:大数据毕业设计本科 编辑:程序博客网 时间:2024/06/16 05:01

应用场景:页面加载完成后异步ajax请求后台获取下拉框、列表数据等。后台controller层调用其他服务提供的接口实现数据查询。为了防止频繁的调用其他服务的接口,减少对其他服务的请求压力。在获取了数据以后把数据缓存到redis中,下次相同请求判断距离上次请求是否在有效期内,如果是,就直接从redis取数据返回。
难点:如何在最少更改现有系统的基础上实现此功能,用面向切面编程aop的思想切入controller方法,用注解的方式是比较理想的。把数据序列化和反序列化后实现数据在redis缓存中存取。

1. 定义一个注解
此注解作用在controller方法,指定方法是否使用缓存。由于是根据项目业务要求,此注解使用的http请求是get请求,而且是使用了@ResponseBody注解。

package com.lancy.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import org.springframework.web.bind.annotation.ResponseBody;/**   *  * 由于spring缓存注解不具备在方法上自定义缓存时间,而且如果在controller上异步请求的方法( @ResponseBody)上进行一些个性化的改造不好改,可以使用此注解 *  * @version V1.0   */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DataCacheable {    // 缓存的key,默认是方法签名和方法 参数做key    String value() default "";    // 缓存的时间,默认30分钟   (秒为单位)    int exp() default 1800;}

2. aop实现

package com.lancy.web.interceptor;import java.io.UnsupportedEncodingException;import javax.annotation.PostConstruct;import javax.inject.Inject;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import org.apache.commons.codec.digest.DigestUtils;import org.apache.commons.lang.StringUtils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestAttributes;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import com.lancy.web.constans.ResultConstans;import com.lancy.web.vo.Result;import com.lancy.annotation.dataCacheable;import com.lancy.util.io.ObjectTranscoder;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;/** * 此方法应用场景,get方法请求,controller上返回的是Result对象* @version V1.0 */@Aspect@Componentpublic class CacheableInterceptor {      private static final Logger LOGGER = LoggerFactory.getLogger(CacheableInterceptor.class);        @Inject        private JedisPool jedisPool;        // 定义一个切入点,名称为pointCutMethod(),拦截类的所有controller方法。关于环绕方法的使用可以百度参考        @Pointcut("execution(* com.lancy..*.controller..*.*(..))")        private void pointCutMethod()        {        }        // 定义环绕通知, @annotation表示使用了此注解的才起作用        @Around("pointCutMethod()  && @annotation(DataCacheable)")        public Object aroundMethod(ProceedingJoinPoint pjp, dataCacheable dataCacheable) throws Throwable        {            long startTime = System.currentTimeMillis();            RequestAttributes ra = RequestContextHolder.getRequestAttributes();            ServletRequestAttributes sra = (ServletRequestAttributes) ra;            HttpServletRequest request = sra.getRequest();            String method = request.getMethod();            if (method.equalsIgnoreCase("GET")){                String key = dataCacheable.value();                if (StringUtils.isBlank(key)){                    String url = request.getRequestURL().toString();                    String queryString = request.getQueryString();                    queryString =  (null == queryString) ? "" : queryString;                    key = url +"?"+queryString;                    LOGGER.debug("******CacheableInterceptor******url:"+key);                          /* key = DigestUtils.sha1Hex(key);*/                }                Result cacheResult = null;                Jedis jedis = null;                try{                    jedis = jedisPool.getResource();//连接redis                    byte[] infoList_srl = jedis.get(getKeyByteArray( key));                    if (infoList_srl != null){//如果redis保存了数据                        cacheResult = (Result) ObjectTranscoder.deserialize(infoList_srl);//把数据反序列化为实体                    }                    if (cacheResult != null){                        LOGGER.debug("******CacheableInterceptor Cache hit!\tMethod: {},use time: {}", pjp.getSignature().getName(),(System.currentTimeMillis()-startTime));                        return cacheResult;                    }                    cacheResult = (Result) pjp.proceed();                    if(null != cacheResult && cacheResult.getStatus() == ResultConstans.SUCEESS){                        //第一次查询服务返回后把数据保存在redis中,先序列化                        jedis.setex(getKeyByteArray( key), wbResultCacheable.exp(),                                ObjectTranscoder.serialize(cacheResult));                    }                    return cacheResult;                }catch (Exception e){                    LOGGER.error("******CacheableInterceptor set key Exception: {}", e);/*                  throw new Exception(e);*/                                 }finally{                    if (null != jedisPool && jedis != null){                        jedis.close();                    }                }                return pjp.proceed();            }else{                return pjp.proceed();            }        }        @PostConstruct        public void postConstruct()        {/*          LOGGER.debug("=====CacheableInterceptor is OK!");*/        }        private byte[] getKeyByteArray(String key) throws UnsupportedEncodingException{            return key.getBytes("UTF-8");        }}

3. 相关类
序列化和反序列化工具类

package com.lancy.util.io;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import org.apache.commons.io.IOUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class ObjectTranscoder {     private static final Logger logger = LoggerFactory.getLogger(ObjectTranscoder.class);        public static byte[] serialize(Object value) {            if (value == null) {                throw new NullPointerException("ObjectTranscoder Can't serialize null");            }            byte[] rv = null;            ByteArrayOutputStream bos = null;            ObjectOutputStream os = null;            try {                bos = new ByteArrayOutputStream();                os = new ObjectOutputStream(bos);                os.writeObject(value);                rv = bos.toByteArray();            } catch (IOException e) {                throw new IllegalArgumentException("ObjectTranscoder Non-serializable object", e);            } finally {                IOUtils.closeQuietly(os);                IOUtils.closeQuietly(bos);            }            return rv;        }        public static Object deserialize(byte[] in) {            Object rv = null;            ByteArrayInputStream bis = null;            ObjectInputStream is = null;            try {                if (in != null) {                    bis = new ByteArrayInputStream(in);                    is = new ObjectInputStream(bis);                    rv = is.readObject();                }            } catch (IOException e) {                logger.warn("ObjectTranscoder Caught IOException decoding %d bytes of data", in == null ? 0 : in.length, e);            } catch (ClassNotFoundException e) {                logger.warn("ObjectTranscoder Caught CNFE decoding %d bytes of data", in == null ? 0 : in.length, e);            } finally {                IOUtils.closeQuietly(is);                IOUtils.closeQuietly(bis);            }            return rv;        }}

Result实体类

package com.lancy.web.vo;import java.io.Serializable;import com.sun.org.apache.regexp.internal.RE;import com.lancy.web.constans.ResultConstans;public class Result implements Serializable{    private static final long serialVersionUID = -4719595465158334529L;    private int status = ResultConstans.FAIL; //200表示成功  500失败    private String description = ResultConstans.FAIL_SYSTEM_MSG;//失败提示语or成功提示语    private Object data;//结果    public void setDetail(int status,String msg){        this.status = status;        this.description = msg;    }    public void setFailResult(){        this.status = ResultConstans.FAIL;        this.description = ResultConstans.FAIL_SYSTEM_MSG;        this.data="";    }    public void setNotLoginMsg(String msg){        this.status = ResultConstans.NOT_LOGIN;        this.description = msg;    }    public void setNotLoginMsg(){        this.status = ResultConstans.NOT_LOGIN;        this.description = ResultConstans.NOT_LOGIN_MSG;    }    public void setNotChoseChildMsg(){        this.status = ResultConstans.NOT_CHOSE_CHILD;        this.description = ResultConstans.NOT_CHOSE_CHILD_MSG;    }    public Result setFailResultMsg(String msg){        this.data="";        this.status = ResultConstans.FAIL;        this.description = msg;        return this;    }    public void setApiTokenFailResult(){        this.status = ResultConstans.FAIL;        this.description = ResultConstans.API_TOKEN_FAIL_MSG;    }    public Result setSuccessResult(){        this.status = ResultConstans.SUCEESS;        this.description = ResultConstans.SUCC_SYSTEM_MSG;        return this;    }    public Result setSuccessResult(Object data){        this.status = ResultConstans.SUCEESS;        this.description = ResultConstans.SUCC_SYSTEM_MSG;        this.data = data;        return this;    }    public Result(){}    public int getStatus() {        return status;    }    public Result setStatus(int status) {        this.status = status;        return this;    }    public String getDescription() {        return description;    }    public Result setDescription(String description) {        this.description = description;        return this;    }    public Object getData() {        return data;    }    public Result setData(Object data) {        this.data = data;        return this;    }    public static void main(String[] args) {    }}

ResultConstans 类

package com.lancy.web.constans;public class ResultConstans {    public final static int SUCEESS = 200; //成功    public final static int FAIL = 500; //失败    public final static int NOT_LOGIN = 300; //未登陆    public final static int NOT_CHOSE_CHILD = 400; //没有选择孩子    public final static String FAIL_SYSTEM_MSG = "网络出错,请稍后再试!"; //失败提示语    public final static String API_TOKEN_FAIL_MSG = "鉴权失败!"; //api token错误    public final static String SUCC_SYSTEM_MSG = "操作成功!"; //成功提示语    public final static String NOT_LOGIN_MSG = "请先登录!"; //还没登录提示语提示语    public final static String NOT_CHOSE_CHILD_MSG = "请选择孩子!"; //没有选择孩子提示语}

spring-redis.xml配置文件

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"    xmlns:tx="http://www.springframework.org/schema/tx"    xsi:schemaLocation="http://www.springframework.org/schema/beans             http://www.springframework.org/schema/beans/spring-beans-4.0.xsd             http://www.springframework.org/schema/aop              http://www.springframework.org/schema/aop/spring-aop-4.0.xsd             http://www.springframework.org/schema/tx             http://www.springframework.org/schema/tx/spring-tx-4.0.xsd             http://www.springframework.org/schema/context             http://www.springframework.org/schema/context/spring-context-4.0.xsd"    default-autowire="byName" default-lazy-init="false">    <description>spring redis配置</description>    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">        <property name="testWhileIdle" value="true" />        <property name="minEvictableIdleTimeMillis" value="60000" />        <property name="timeBetweenEvictionRunsMillis" value="30000" />        <property name="numTestsPerEvictionRun" value="-1" />        <property name="maxTotal" value="${redis.maxTotal}" /> <!-- 最大连接数   -->        <property name="maxIdle" value="${redis.maxIdle}" /> <!-- 最大空闲连接数  -->        <property name="minIdle" value="${redis.minIdle}" /><!-- 最小空闲连接数  -->    </bean>    <bean id="jedisPool" class="redis.clients.jedis.JedisPool"        destroy-method="destroy">        <constructor-arg index="0" ref="jedisPoolConfig" />        <constructor-arg index="1" value="${redis.host}" type="java.lang.String"  />        <constructor-arg index="2" value="${redis.port}" type="int" />        <constructor-arg index="3" value="360000" type="int" /> <!-- 超时时间 单位ms -->        <constructor-arg index="4" value="${redis.password}" />        <constructor-arg index="5" value="${redis.db}" />      </bean></beans>

4. 控制层方法添加注解使用

@ResponseBody@RequestMapping(value="/test/getDept",method= RequestMethod.GET)@DataCacheable(exp=600)public Result getDept(HttpServletRequest request){    Result result = new Result();    //业务代码实现      return result;}

通过这个功能的实现,可以扩展到不同的需求,主要的还是学习这种编程方式。

阅读全文
0 0
原创粉丝点击