springmvc controller 面向切面编程,实现数据查询的缓存功能
2017-11-24 17:18
471 查看
应用场景:页面加载完成后异步ajax请求后台获取下拉框、列表数据等。后台controller层调用其他服务提供的接口实现数据查询。为了防止频繁的调用其他服务的接口,减少对其他服务的请求压力。在获取了数据以后把数据缓存到redis中,下次相同请求判断距离上次请求是否在有效期内,如果是,就直接从redis取数据返回。
难点:如何在最少更改现有系统的基础上实现此功能,用面向切面编程aop的思想切入controller方法,用注解的方式是比较理想的。把数据序列化和反序列化后实现数据在redis缓存中存取。
1. 定义一个注解
此注解作用在controller方法,指定方法是否使用缓存。由于是根据项目业务要求,此注解使用的http请求是get请求,而且是使用了@ResponseBody注解。
2. aop实现
3. 相关类
序列化和反序列化工具类
Result实体类
ResultConstans 类
spring-redis.xml配置文件
4. 控制层方法添加注解使用
通过这个功能的实现,可以扩展到不同的需求,主要的还是学习这种编程方式。
难点:如何在最少更改现有系统的基础上实现此功能,用面向切面编程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; }
通过这个功能的实现,可以扩展到不同的需求,主要的还是学习这种编程方式。
相关文章推荐
- 利用JQuery方便实现基于Ajax的数据查询、排序和分页功能
- 利用JQuery方便实现基于Ajax的数据查询、排序和分页功能
- WMS仓储管理系统实现"收发存日报表"数据查询功能Sql语句--Oracle
- Java 语义网编程系列五: Jena+sparql实现语义数据查询
- spring学习笔记7--使用spring进行面向切面的(AOP)编程(1)注解方式实现
- UNIX环境高级编程学习之第六章系统数据文件和信息-修改第四章实现的Shell的“ls -l”功能
- 应用MapX编程实现地图数据查询
- EXTJS4 Grid Filter 插件的使用 与后台数据解析------Extjs 查询筛选功能的实现
- 模块化与缓存结合的小功能实现,可用于传递参数调用对应模块查询并缓存之用
- jQuery数据缓存功能的解析及简单实现
- 如何用存储过程实现下面功能:以表名为参数,查询出这个表中的所有数据
- 使用Aop面向切面技术实现记录详细操作日志功能
- js实现面向切面的编程(AOP)
- python 多个装饰器组合应用,实现面向切面之AOP编程
- JQuery实现基于Ajax的数据查询、排序和分页功能
- jQuery数据缓存功能的实现思路及简单模拟
- spring aop 面向切面编程 如何来做一个强大的日志记录功能.原创
- jQuery数据缓存功能的解析及简单实现
- 在PB的数据窗口中怎样实现某一列的增量查询功能
- 利用JQuery方便实现基于Ajax的数据查询、排序和分页功能