Spring AOP结合Redis利用自定义注解实现通用缓存
1.目的
1. 缓存: 减少与数据库的交互, 提升查询性能;
2. 降低代码的侵入性
3. 提高代码的复用性
2.业务逻辑
1. 去缓存查询;
2. 如果缓存中没有数据, 则代表缓存穿透, 则去数据库查询, 再将数据存入缓存;
3. 如果缓存中有数据, 则直接返回;
3.技术点
1. spring AOP
2. Redis缓存
4.准备操作
1. 对象必须实现serializable接口, 可以被序列化;
2. 在spring核心配置文件中实现cglib动态代理;
3. redis的Template配置为JdkSerializationRedisSerializer; 目的可以将对象序列化为字节数组
<aop:aspectj-autoproxy proxy-target-class="true">
5.spring整合redis的配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="300"/>
<property name="maxWaitMillis" value="3000"/>
<property name="testOnBorrow" value="true"/>
</bean>
<bean id="redisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="localhost" p:port="6379" p:pool-config-ref="poolConfig"
p:database="0"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="redisConnectionFactory"/>
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer">
</bean>
</property>
</bean>
</beans>
6.自定注解
package cn.itcast.bos.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Retention(RetentionPolicy.RUNTIME) : 访问时机运行时生效
* @Target(ElementType.METHOD) : 在哪里生效(方法上,类上或者...)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NeedCache {
}
7.配置一个通知
package cn.itcast.bos.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* 定义一个通知
* @Component: 被spring容器管理
* @Aspect: 声明一个切面(切点 + 通知)
*/
@Component
@Aspect
public class RedisAdvice {
/**
* 注入Template
* 1. string 为存储在redis中的key
* 2. value 我们希望将对象(list集合)按照特定的格式存储在redis中,再按照特定的格式取出来.
* -- 这里使用对象序列化, 实现的方式是将redis配置文件中的template的值设置为JdkSerializationRedisSerializer
* -- 详细键配置文件
*/
private RedisTemplate<String, byte[] > redisTemplate;
/**
* 配置环绕通知 + annotation的切点表达式
* @annotation 注解形式的通知, 那个方法上添加了该注解, 则执行该通知
*/
@Around("@annotation(cn.itcast.bos.aop.NeedCache)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("自定义注解执行了...");
/**
* 确定redis的 key 和 value
* key : 类名 + 方法名 + 参数列表(确定唯一的key)
* value : 对象序列化的的byte数组(redis天然支持byte数组)
*/
/**
* 1. 组装key
*/
StringBuilder sb = new StringBuilder();
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
sb.append(className);
sb.append(methodName);
Object[] args = joinPoint.getArgs();
if(args != null && args.length > 0){
for (Object arg : args) {
String argName = arg.getClass().getName();
sb.append(argName);
}
}
String key = sb.toString();
/**
* 2. 去查询缓存
*/
byte[] value = redisTemplate.opsForValue().get(key);
if(value != null && value.length > 0){
ByteArrayInputStream bais = new ByteArrayInputStream(value);
ObjectInputStream ois = new ObjectInputStream(bais);
Object o = ois.readObject();
return o;
}else {
Object proceed = joinPoint.proceed();
/**
* 将查询到的对象序列化为字节数组
* 对象序列化流: ObjectOutputStream,可以将对象, 序列化存入任意一个地方,
* 目标是内存的一个字节数组 : ByteArrayOutputStream
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(proceed);
byte[] bytes = bos.toByteArray();
redisTemplate.opsForValue().set(key , bytes);
return proceed;
}
}
}
注: 相关概念
1. @Aspect: 声明一个切面(切点 + 通知)
2. redis的存储 key : string, value : 字节数组
3. @Around("@annotation(cn.orange.bos.aop.NeedCache)") 环绕通知 + 注解形式
@annotation 注解形式的通知, 那个方法上添加了该注解, 则执行该通知
4. 对象序列化流: ObjectOutputStream,可以将对象, 序列化存入任意一个地方
5. 将目标转换为内存的一个字节数组 : ByteArrayOutputStream