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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013627689/article/details/79966918

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

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

package com.lancy.common.util.annotation.DataCacheable;

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;
/**  
 * 
 * 由于spring缓存注解不具备在方法上自定义缓存时间,而且如果在controller上异步请求的方法( @ResponseBody)上进行一些个性化的改造不好改,可以使用此注解
 * 
 * @version V1.0  
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @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.common.core.ResultConstans;
import com.lancy.common.Result;
import com.lancy.common.util.annotation.DataCacheable;
import com.lancy.common.util.io.ObjectTranscoder;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * 此方法应用场景,get方法请求,controller上返回的是Result对象,进行缓存。
 * 更优雅的方式应该使用nginx+lua
 * @version V1.0
 */
@Aspect
@Component
public 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();
            //如果是get方法。
            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();
                    byte[] infoList_srl = jedis.get(getKeyByteArray( key));
                    if (infoList_srl != null){
                        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){
                        jedis.setex(getKeyByteArray( key), dataCacheable.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. controller使用
在controller中使用注解实现数据缓存。当第一次请求时,把请求url和查询返回的数据作为键值对保存在redis中,有个过期时间,如果第二次请求的url与第一次请求的url完全一致,并且在有效期内,则直接通过url获取redis中缓存的数据,达到减少数据库请求和数据缓存的目的。

@ResponseBody
@RequestMapping(value="/city.json",method= RequestMethod.GET)
@DataCacheable(exp = 3600)
public Result getCityAndArea(HttpServletRequest request){
    //todo 查询记录。
    Result result = new Result();
    List<City> list = cityService.findCitys();
    result.setSuccessResult(cityList);
    return result;
}

三个基础类
序列化反序列化工具类

package com.lancy.common.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;
        }
}
package com.lancy.common.Result;

import com.sun.org.apache.regexp.internal.RE;
import com.lancy.common.core.ResultConstans;

public class Result {
    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) {
    }
}


package com.lancy.common.core.ResultConstans;
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 = "请选择孩子!"; //没有选择提示语


}
阅读更多

没有更多推荐了,返回首页