1.问题描述
在实际生产应用中,有这样一个接口,需要查询用户的联系人和所属群组,包括群组成员的列表和群组基本信息的接口,关联到的有4张表,客户端请求接口时间过长,需要对接口进行相关优化,就需要对相关数据进行缓存操作,但是需要更新缓存的地方比较多,自己的想法是在需要更新的缓存的地方去调用方法刷新缓存,但是觉得此种方法有问题,于是向@JackSun 求助,给出了解决方案。
2.解决方案
使用AspectJ + 注解,使用AOP思想实现缓存的刷新。
成天AOP,面向切面编程的说,自己用的时候怎么想不到呢,话不多说,直接上干货。
- 注解类 UserGroupCacheUpdate.java 定义一个用户更新缓存的注解
package com.hangtuo.service.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p> 用户所属群组缓存 </p>
*
* @author Shawn
* @since 2017/11/13
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface UserGroupCacheUpdate {
}
- 设置切片
package com.hangtuo.service.redis.aspect;
import com.hangtuo.entity.GroupMember;
import com.hangtuo.service.group.UserGroupRedisCacheService;
import com.xiaoleilu.hutool.util.StrUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
/**
* <p> 类说明 </p>
*
* @author Shawn
* @since 2017/11/13
*/
@Aspect
@Component
@Slf4j //这个注解为lombook注解 不清楚的去看看,推荐大家使用
public class UserGroupCacheAspect {
@Resource(name = "userGroupRedisCacheServiceImpl")
private UserGroupRedisCacheService userGroupRedisCacheService;
//设置切片位置
@Pointcut("@annotation(com.hangtuo.service.annotation.UserGroupCacheUpdate)")
public void setJoinPoint() {
}
/*
* 环绕通知 这里刚开始的想法考虑后 决定使用后置通知,然后就出现了 对象已经被删除,然后查询的情
* 况,最终决定统一使用环绕通知
*/
@Around(value = "setJoinPoint()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
log.info("UserBelongGroupAOP start..");
//获取请求参数
Object[] args = joinPoint.getArgs();
Object param = args[0];
String clientID = null;
/*
* 注意这里 因为需要刷新缓存的方法调用时,传递的参数不一致,所以需要类型判断
* 根据自己的使用场景来判断
*/
if (param instanceof GroupMember) {
log.info("UserBelongGroupAOP:参数类型Integer,param={}", param);
GroupMember groupMember = (GroupMember) param;
clientID = String.valueOf(groupMember.getGmsId());
} else {
log.warn("UserBelongGroupAOP 参数类型未知");
}
// 注意 这里的执行顺序,是需要在原来的方法执行后,还是执行前执行,根据使用场景来定
result = joinPoint.proceed();
//StrUtil 为hutools 工具类 相当好用的一个工具类
if (StrUtil.isNotBlank(clientID)) {
//真正去刷新缓存,调用外部方法
userGroupRedisCacheService.reLoadClientGroupsCache(clientID);
log.info("UserBelongGroupAOP reload clientID={}", clientID);
} else {
log.warn("UserBelongGroupAOP clientID is null or empty");
}
log.info("UserBelongGroupAOP end..");
return result;
}
}
- 具体使用 为service的实现类
@Override
@UserGroupCacheUpdate
public void reloadClientBelongGroup(GroupMember groupMember) {
// do something 相关业务逻辑
}
3.遇到的一些问题
- (1)请求参数 不确定问题
使用 instanceof 判断参数类型,对参数类型进行强制转化,取到我们需要的参数。
- (2)同一个类中,调用其他方法时,如果第二个方法使用了Aspectj注解切片,则第二个方法没有使用代理对象,导致切面的方法未执行的问题,查找相关资料后,有如下两种解决方案:
- 1.把这两个方法分开到不同的类中;
- 2.把注解加到类名上面
(3) 实际应用中,cache部分除了使用了redis缓存外,还使用了guava cache作为本地缓存,但是guava cache获取到相关的实体类对象集合后,对集合进行遍历时,出现了 can not cast LinkedTreeMap to Entity 的错误,让我很是纳闷,相关的主要代码如下:
//cache 定义
/**
* 本地的获取cache http://ifeve.com/google-guava-cachesexplained/
*/
private final LoadingCache<String, List<RedisCacheGroupMemberInfo>> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.weakValues()
// 用来开启Guava Cache的统计功能
.recordStats()
//缓存项在给定时间内没有被读/写访问,则回收
.expireAfterAccess(60, TimeUnit.SECONDS)
.build(new CacheLoader<String, List<MyEntity>>() {
@Override
public List<MyEntity> load(String key) throws Exception {
//doGetGroupMemberInfo 具体去获取相关的内容
List<MyEntity> members = MemberRedisCacheServiceImpl.this.doGetGroupMemberInfo(key);
return members;
}
});
//相关获取的代码如下所示:
List<MyEntity> groups = null;
List<MyEntity> returnGroups = new ArrayList<>();
try {
String redisID = this.makeRedisStr(groupID);
groups = this.cache.get(redisID);
} catch (ExecutionException e) {
e.printStackTrace();
}
if (CollectionUtil.isNotEmpty(groups)) {
Gson gson = new Gson();
/*
* 这部分是后面询问@JackSun 后 加上的,cache在获取后,对相关的的具体的类型进行了擦除
* 需要进行相关的类型转换,要么在后面遍历使用过程中就会出现cast Exception
* 这里要引以为戒
*/
for (Object objectTmp : groups) {
MyEntity infoTmp = null;
if (!(objectTmp instanceof MyEntity)) {
String jsonStr = gson.toJson(objectTmp);
infoTmp = gson.fromJson(jsonStr, MyEntity.class);
}else {
infoTmp = (MyEntity) objectTmp;
}
returnGroups.add(infoTmp);
}
}
return returnGroups;
本文使用了工具类CollectionUtil,StrUtil同为hutools 工具类
后记
就到这吧,今后还是要学会活学活用,骚年,你还是太年轻!!!
网友评论