springde@cache注解简单易用,但是应对复杂的业务场景仍然力有不逮。无法应对高并发下的缓存击穿,缓存雪崩,缓存备份等问题。所以自定义注解就是一个相对复杂,但是更好的解决方案。
1.在pom.xml中引入相关依赖
<!-- aop依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<!-- redis客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- spring对redis的支持 -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.4.RELEASE</version>
</dependency>
<!-- google开源的guava工具类 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>22.0</version>
</dependency>
2.编写spring的配置文件applicationContext.xml
在文件头部定义aop和cache的schema
xmlns:aop=”http://www.springframework.org/schema/aop”
xmlns:cache=”http://www.springframework.org/schema/cache”
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.3.xsd
<!-- 使用cglib进行代理 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- 配置reids连接池配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<!-- 配置redis连接池工厂 -->
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="database" value="${redis.default.db}" />
<property name="timeout" value="${redis.timeout}" />
<property name="usePool" value="true" />
<property name="poolConfig" ref="poolConfig" />
</bean>
<!-- key序列化策略 -->
<bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
<!-- value序列化策略 -->
<bean id="valueSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
<!-- spring-redis模板 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer" ref="keySerializer" />
<property name="valueSerializer" ref="valueSerializer" />
<!-- 配置redis是否支持事务 -->
<property name="enableTransactionSupport" value="false" />
</bean>
<!-- 开启缓存注解驱动 -->
<cache:annotation-driven/>
<!-- 声明redis缓存的管理器 -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg index="0" ref="redisTemplate"/>
<!-- 配置数据默认过期时间 ,秒-->
<property name="defaultExpiration" value="1000"/>
<!-- 配置缓存域数据的存在时间,秒 -->
<property name="expires">
<map>
<entry key="userCache" value="10"/>
<entry key="user_backup" value="10"/>
</map>
</property>
</bean>
RedisCacheManager类有两个关于过期时间的属性,默认过期时间是0,也就是永久有效
// 0 - never expire
private long defaultExpiration = 0;
private Map<String, Long> expires = null;
配置了expires之后,在创建相关缓存域的缓存时,过期时间就会覆盖默认过期时间。
@SuppressWarnings("unchecked")
protected RedisCache createCache(String cacheName) {
//调用计算过期时间方法
long expiration = computeExpiration(cacheName);
return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
cacheNullValues);
}
protected long computeExpiration(String name) {
Long expiration = null;
if (expires != null) {
expiration = expires.get(name);
}
//覆盖原来的过期时间
return (expiration != null ? expiration.longValue() : defaultExpiration);
}
3.编写一个自定义缓存注解
/**
* 自定义缓存注解
* @author yuli
*
*/
@Retention(RetentionPolicy.RUNTIME)//设置生命周期为运行时有效
@Target({ElementType.METHOD,ElementType.TYPE})//设置为可以是用在方法上和类上
public @interface CacheResult {
String key();//键名
String cacheName();//关联到哪个缓存域
String backupKey() default "";//是否备份
boolean needBloomFilter() default false;//布隆过滤器,防止缓存击穿
boolean needLock() default false;//是否加锁,缓解缓存雪崩
}
4.在业务类上使用自定义缓存注解
@Service
public class UserServiceImpl3 implements UserService {
public static Logger logger = Logger.getLogger(UserServiceImpl3.class);
@Autowired
private UserMapper userMpper;//usermapper接口
private static final String CACHE_NAME = "userCache";
private static final String CACHE_BACKUP_NAME ="user_backup";
@Transactional
//自定义缓存注解
@CacheResult(key="#user.id",cacheName=CACHE_NAME,backupKey=CACHE_BACKUP_NAME)
public User addUser(User user) {
userMpper.insert(user);
logger.info("向数据库添加用户");
return user;
}
@Transactional
public void deleteUser(String userId) {
userMpper.deleteByPrimaryKey(userId);
}
@Transactional
public User updateUser(User user) {
userMpper.updateByPrimaryKeySelective(user);
return user;
}
@Transactional(readOnly=true)
//自定义缓存注解
@CacheResult(key="#userId",cacheName=CACHE_NAME,backupKey=CACHE_BACKUP_NAME,needBloomFilter=true,needLock=true)
public User queryUser(String userId) {
logger.info("从数据库里取得用户");
User user = userMpper.selectByPrimaryKey(userId);
return user;
}
}
5.编写缓存服务类
/**
* redis缓存服务类
* @author yuli
*
*/
@Service
public class RedisCacheServiceImpl implements CacheService2 {
@Autowired
private CacheManager cm;//导入缓存管理器
/**
* 获取缓存结果
*/
@SuppressWarnings("unchecked")
public <T> T cacheResult(String key, String cacheName) {
ValueWrapper valueWrapper = cm.getCache(cacheName).get(key);
return (T) (valueWrapper == null ? null:valueWrapper.get());
}
/**
* 移除缓存
*/
public void cacheRemove(String key, String cacheName) {
cm.getCache(cacheName).evict(key);
}
/**
* 添加缓存
*/
public <T> void cachePut(String key, T value, String cacheName) {
cm.getCache(cacheName).put(key, value);
}
}
6.编写一个aop缓存切面
@Aspect
@Component
public class CacheAspect {
public static Logger logger = Logger.getLogger(CacheAspect.class);
//互斥锁,用来缓解缓存雪崩
private Lock lock = new ReentrantLock();
//缓存服务类
@Autowired
private CacheService2 cs;
//布隆过滤器,用来防止缓存击穿
private BloomFilter<String> bf;
@Autowired
private UserMapper mapper;
@PostConstruct
public void init(){
//初始化布隆过滤器,使用id来作为过滤名单
List<String> userIds = mapper.selectAllUserId();
bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),userIds.size());
for (String userId : userIds) {
bf.put(userId);
}
}
@Around("@annotation(cr)")
public Object doAround(ProceedingJoinPoint point,CacheResult cr) throws Throwable{
String key = getKey(cr.key(), point);//键名
String buckupkey = cr.backupKey();//备份的键名
String cacheName = cr.cacheName();//缓存域的名称
boolean needBloomFilter = cr.needBloomFilter();//是否使用布隆过滤器
boolean needLock = cr.needLock();//是否加锁
//使用布隆过滤器来校验数据
if(needBloomFilter && !bf.mightContain(key)){
logger.info("id名不存在于名单中");
return null;
}
User rUser = cs.cacheResult(key, cacheName);
if(rUser != null){
logger.info("从缓存中取得数据");
return rUser;
}
//缓存中没有数据,仅允许一个任务去数据库中加载数据,并写入缓存
if(needLock){
//尝试获取锁,没获取到就从备份缓存中中获取数据
if(lock.tryLock()){
Object object = point.proceed();
cs.cachePut(key, object, cacheName);
//放一份到备份缓存中
cs.cachePut(buckupkey+key, object, buckupkey);
lock.unlock();
return object;
}else{
logger.info("从备份中获取数据");
return cs.cacheResult(buckupkey+key, buckupkey);
}
}else{
Object object = point.proceed();
cs.cachePut(key, object, cacheName);
//放一份到备份缓存中
cs.cachePut(buckupkey+key, object, buckupkey);
return object;
}
}
/**
* 解析spring的el表达式方法
* @param key
* @param point
* @return
*/
private String getKey(String key,ProceedingJoinPoint point){
Object[] args = point.getArgs();//获取参数的值
Signature signature = point.getSignature();//从连接点获取数字签名
MethodSignature methodSignature = (MethodSignature)signature;
Method method = methodSignature.getMethod();//获取形参的名字
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(method);
//调用el表达式解析工具类
return SpelParser.getKey(key, parameterNames, args);
}
因为使用了spring的el表达式,所以要编写一个springEl表达式的解析工具类
7.编写spring的El表达式解析工具类
/**
* spring表达式解析器
* @author yuli
*
*/
public class SpelParser {
private static ExpressionParser parser = new SpelExpressionParser();
public static String getKey(String key,String[] paramName,Object[] args){
//将字符串转换为spring的el表达式
Expression exp = parser.parseExpression(key);
//定义赋值上下文
EvaluationContext context = new StandardEvaluationContext();
if(args.length < 1){
return null;
}
for(int i = 0;i<args.length;i++){
//向赋值上下文传入参数名相对应的值
context.setVariable(paramName[i], args[i]);
}
//表达式取值,通过赋值上下文
return exp.getValue(context,String.class);
}
}