Spring切面使用缓存
Spring的AOP
真是是一个好东西,还不太清楚是什么的同学建议先自行Google
下吧。
在不使用切面的时候如果我们想给某个方法加入缓存的话肯定是在方法返回之前就要加入相应的逻辑判断,只有一个或几个倒还好,如果有几十上百个的话那GG了,而且维护起来也特别麻烦。
好在Spring的AOP可以帮我们解决这个问题。
这次就在我们需要加入缓存方法的切面加入这个逻辑,并且只需要一个配置即可搞定,就是上文中所提到的配置文件,如下:
<!--配置切面拦截方法 --> <aop:config proxy-target-class="true"> <!--将com.crossoverJie.service包下的所有select开头的方法加入拦截 去掉select则加入所有方法w --> <aop:pointcut id="controllerMethodPointcut" expression=" execution(* com.crossoverJie.service.*.select*(..))"/> <aop:pointcut id="selectMethodPointcut" expression=" execution(* com.crossoverJie.dao..*Mapper.select*(..))"/> <aop:advisor advice-ref="methodCacheInterceptor" pointcut-ref="controllerMethodPointcut"/> </aop:config>
这里我们使用表达式execution(* com.crossoverJie.service.*.select*(..))
来拦截service
中所有以select
开头的方法。这样只要我们要将加入的缓存的方法以select命名开头的话每次进入方法之前都会进入我们自定义的MethodCacheInterceptor
拦截器。
这里贴一下MethodCacheInterceptor
中处理逻辑的核心方法:
@Override public Object invoke(MethodInvocation invocation) throws Throwable { Object value = null; String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); // 不需要缓存的内容 //if (!isAddCache(StringUtil.subStrForLastDot(targetName), methodName)) { if (!isAddCache(targetName, methodName)) { // 执行方法返回结果 return invocation.proceed(); } Object[] arguments = invocation.getArguments(); String key = getCacheKey(targetName, methodName, arguments); logger.debug("redisKey: " + key); try { // 判断是否有缓存 if (redisUtil.exists(key)) { return redisUtil.get(key); } // 写入缓存 value = invocation.proceed(); if (value != null) { final String tkey = key; final Object tvalue = value; new Thread(new Runnable() { @Override public void run() { if (tkey.startsWith("com.service.impl.xxxRecordManager")) { redisUtil.set(tkey, tvalue, xxxRecordManagerTime); } else if (tkey.startsWith("com.service.impl.xxxSetRecordManager")) { redisUtil.set(tkey, tvalue, xxxSetRecordManagerTime); } else { redisUtil.set(tkey, tvalue, defaultCacheExpireTime); } } }).start(); } } catch (Exception e) { e.printStackTrace(); if (value == null) { return invocation.proceed(); } } return value; }
- 先是查看了当前方法是否在我们自定义的方法中,如果不是的话就直接返回,不进入拦截器。
- 之后利用反射获取的类名、方法名、参数生成
redis
的key
。
- 用key在redis中查询是否已经有缓存。
- 有缓存就直接返回缓存内容,不再继续查询数据库。
- 如果没有缓存就查询数据库并将返回信息加入到redis中。
使用PageHelper
这次为了分页方便使用了比较流行的PageHelper
来帮我们更简单的进行分页。
首先是新增一个mybatis的配置文件mybatis-config
:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings> <plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> <!-- 该参数默认为false --> <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 --> <!-- 和startPage中的pageNum效果一样 --> <property name="offsetAsPageNum" value="true"/> <!-- 该参数默认为false --> <!-- 设置为true时,使用RowBounds分页会进行count查询 --> <property name="rowBoundsWithCount" value="true"/> <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 --> <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) <property name="pageSizeZero" value="true"/> --> <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 --> <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 --> <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 --> <property name="reasonable" value="true"/> <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 --> <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 --> <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 --> <!-- 不理解该含义的前提下,不要随便复制该配置 --> <property name="params" value="pageNum=start;pageSize=limit;"/> </plugin> </plugins> </configuration>
接着在mybatis的配置文件中引入次配置文件:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自动扫描mapping.xml文件 --> <property name="mapperLocations" value="classpath:mapping/*.xml"></property> <!--加入PageHelper--> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean>
接着在service方法中:
@Override public PageEntity<Rediscontent> selectByPage(Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); //因为是demo,所以这里默认没有查询条件。 List<Rediscontent> rediscontents = rediscontentMapper.selectByExample(new RediscontentExample()); PageEntity<Rediscontent> rediscontentPageEntity = new PageEntity<Rediscontent>(); rediscontentPageEntity.setList(rediscontents); int size = rediscontentMapper.selectByExample(new RediscontentExample()).size(); rediscontentPageEntity.setCount(size); return rediscontentPageEntity; }
只需要使用PageHelper.startPage(pageNum, pageSize);
方法就可以帮我们简单的分页了。
这里我自定义了一个分页工具类PageEntity
来更方便的帮我们在之后生成JSON
数据。
package com.crossoverJie.util; import java.io.Serializable; import java.util.List; /** * 分页实体 * * @param <T> */ public class PageEntity<T> implements Serializable { private List<T> list;// 分页后的数据 private Integer count; public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } public List<T> getList() { return list; } public void setList(List<T> list) { this.list = list; } }
更多PageHelper
的使用请查看一下链接: