redis+aop实现数据缓存
在ssm项目下首先使用spring集成redis
我用的是maven项目,先导入必要的依赖。这里的集成也是花了点时间,因为jar版本冲突的问题,tomcat启动老是找不到jar。用对了版本之后,还是有异常,就去project structure查看了一下,发现直接在pom.xml添加的jar并没有被添加到打包项目的lib中。。。,导入之后就好了
这个是集成redis的相关依赖,我的spring版本用的是4.2.5.RELEASE
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.5.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
然后修改spring-mybaties.xml 配置文件,新建一个spring-redis.xml也可以,新建的记得添加到web.xml中,让web容器加载。
spring-mybaties.xml
添加如下
<!-- redis config start -->
<!-- 配置JedisPoolConfig实例 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"></property>
<property name="minIdle" value="${redis.minIdle}"></property>
<property name="maxTotal" value="${redis.maxTotal}"></property>
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"></property>
<property name="testOnBorrow" value="${redis.testOnBorrow}"></property>
</bean>
<!-- redis连接池 -->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool" destroy-method="close">
<constructor-arg name="poolConfig" ref="poolConfig"/>
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.port}"/>
</bean>
<!-- 配置JedisConnectionFactory -->
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}"/>
<property name="port" value="${redis.port}"/>
<property name="password" value="${redis.password}"/>
<property name="poolConfig" ref="poolConfig"/>
</bean>
<!-- 配置RedisTemplate -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory">
<!--以下针对各种数据进行序列化方式的选择-->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<!--<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>-->
</bean>
redis.host =127.0.0.1
redis.port =6379
redis.password =123456
redis.maxIdle=300
redis.minIdle=100
redis.maxWaitMillis=3000
redis.testOnBorrow=false
redis.maxTotal=500
这时如果能正常启动说明,配置没错。
接下来是配置aop切点,也是配置在spring-mybatis.xml中,这里的切面是service实现层中所有的get方法。我的本意是缓存所有get方法,然后也可以再controller调用的时候选择不缓存,这个再后面的代码中,可以实现控制。
<bean id="aop" class="com.onelove.untils.AopAspect"/>
<aop:config proxy-target-class="true">
<aop:aspect ref="aop">
<aop:pointcut id="getPointcut" expression="execution(* com.onelove.service.*.get*(..))"/>
<aop:around method="caching" pointcut-ref="getPointcut"/>
</aop:aspect>
</aop:config>
然后,就是核心的代码,AopAspest.class,这个类中实现了在aop切面执行方法caching中使用redis的操作api jedis去控制数据的添加、获取,以及数据的过期时间
@Aspect
@Component
public class AopAspect {
@Resource(name = "redisTemplate")
private RedisTemplate redisTemplate;
@Resource(name = "jedisPool")
private JedisPool jedisPool;
@Resource(name = "jedisConnectionFactory")
JedisConnectionFactory jedisConnectionFactory;
public Object caching(ProceedingJoinPoint joinPoint) throws Throwable {
String redisKey = null;
Object[] args = joinPoint.getArgs();//切点方法中的参数,比如get(User user),args.size=1,args[0]=user。get(User user,String aa),args.size=2
//这里在所有的service接口和impl实现方法中添加了一个String op的参数,这个参数是从controller层中传递下来的
//op这个参数作为redis中的key,保证key不会重复,并且含义可知。比如获取首页的数据,op = index,获取列表数据 op = articleList
for (int i = 0; i < args.length&&args!=null; i++) {
//get方法中,我的项目里,尽量使用实体类去实现查询,所以参数中避免出现有String的类型
if (args[i] instanceof String) {
redisKey = (String) args[i];
break;
}
}
//实际编码过程中,如果save插入数据库时,不同步一份到redis的情况下,有些数据是不能缓存存储的
//比如我写了一篇文章,save到mysql中了,这是我点开文章列表(用redis缓存了),这时发现没有刚才编辑的文章
//有些数据如果需要时时更新,要么同步一份到redis,要么就每次都查询数据库,不走redis
//所以在这里使用 op = "" 来限制,不走redis
//含有op操作,则查询
if (redisKey!=null&&!redisKey.equals("")) {
//获取切点方法的名称
String methodName = joinPoint.getSignature().getName();
//getDataFromRedis中根据key拿取数据
Object objectFromRedis = getDataFromRedis(redisKey,methodName);
//判断数据是否是list集合
if(objectFromRedis instanceof List<?>){
//数据是list集合,判断size是否大于0,如果大于直接返回结果。否则再次查询数据库
if(((List<?>)objectFromRedis).size()>0){
return objectFromRedis;
}
}else{
if (null != objectFromRedis) {
return objectFromRedis;
}
}
}
Object object = null;
try {
//执行数据库操作
object = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
//数据库操作执行完,redisKey不为空,才保存数据到redis
if (redisKey!=null&&!redisKey.equals("")) {
setDataToRedis(redisKey, object);
}
return object;
}
//从redis缓存中查询,反序列化
public Object getDataFromRedis(String redisKey,String methodName) {
//查询
Object result=null;
//Jedis连接池中获取jedis对象
Jedis jedis = jedisPool.getResource();
//这是我的redis密码,redis没有密码的可以去掉。redis设置密码的可以看我其他的博客,在liunx下redis
jedis.auth("123456");
byte[] byteData = jedis.get(redisKey.getBytes());
//这里通过方法名的List判断存储的数据是list集合,还是单一的实体类
//redis中不能直接存放实体类,所以这里存取数据都要先序列化和反序列化
//List集合的序列化和单一实体的序列化方法并不同,所以这里区分
if(methodName.indexOf("List")>0){
try {
result = SerializeUtil.unserializeForList(byteData);
} catch (Exception e) {
e.printStackTrace();
}
}else{
try {
result = SerializeUtil.unSerialize(byteData);
}catch (Exception e){
e.printStackTrace();
}
}
return result;
}
//将数据库中查询到的数据放入redis
public void setDataToRedis(String redisKey, Object obj) {
//存入redis
Jedis jedis = jedisPool.getResource();
jedis.auth("123456");
String success;
//判断obj数据类型,list集合先转化成list集合
if (obj instanceof List<?>) {
List<?> list = (List<?>) obj;
//jedis.setex(key,key有效时间(/秒),序列化对象);
success = jedis.setex(redisKey.getBytes(),600,SerializeUtil.serialize(list));
} else {
success = jedis.setex(redisKey.getBytes(),600, SerializeUtil.serialize(obj));
}
if (success != null && success.equals("OK")) {
System.out.print("保存至redis成功");
}
}
}
redis存取中用到的序列化工具类,序列化的实体类,必须实现Serializable接口,才能正常实例化
public class SerializeUtil {
public static byte[] serialize(Object obj) {
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
//序列化
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
byte[] byteArray = baos.toByteArray();
return byteArray;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 31 *
* 32 * 反序列化
* 33 * @param bytes
* 34 * @return
* 35
*/
public static Object unSerialize(byte[] bytes) throws Exception{
ByteArrayInputStream bais = null;
//反序列化为对象
bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
/**
* 列表序列化(用于Redis整存整取)
*
* @param value
* @return
*/
public static <T> byte[] serialize(List<T> value) {
if (value == null) {
throw new NullPointerException("Can't serialize null");
}
byte[] rv = null;
ByteArrayOutputStream bos = null;
ObjectOutputStream os = null;
try {
bos = new ByteArrayOutputStream();
os = new ObjectOutputStream(bos);
for (T obj : value) {
os.writeObject(obj);
}
os.writeObject(null);
os.close();
bos.close();
rv = bos.toByteArray();
} catch (IOException e) {
throw new IllegalArgumentException("Non-serializable object", e);
} finally {
close(os);
close(bos);
}
return rv;
}
/**
* 反序列化列表(用于Redis整存整取)
*
* @param in
* @return
*/
public static <T> List<T> unserializeForList(byte[] in) throws Exception{
List<T> list = new ArrayList<T>();
ByteArrayInputStream bis = null;
ObjectInputStream is = null;
if (in != null) {
bis = new ByteArrayInputStream(in);
is = new ObjectInputStream(bis);
while (true) {
T obj = (T) is.readObject();
if (obj == null) {
break;
} else {
list.add(obj);
}
}
is.close();
bis.close();
}
close(is);
close(bis);
return list;
}
/**
* 关闭的数据源或目标。调用 close()方法可释放对象保存的资源(如打开文件)
* 关闭此流并释放与此流关联的所有系统资源。如果已经关闭该流,则调用此方法无效。
*
* @param closeable
*/
public static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception e) {
// log.info("Unable to close %s", closeable, e);
}
}
}
}