您的位置:首页 > 编程语言 > Java开发

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

2017-11-24 17:18 471 查看
应用场景:页面加载完成后异步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)
@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.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
@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();

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;
}

e391
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;
}


通过这个功能的实现,可以扩展到不同的需求,主要的还是学习这种编程方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: