1、实现效果
当一个请求线程多次请求A方法时,只会触发一次A方法的实际调用,会将方法结果缓存起来,避免多次调用。
2、实现过程
1. 需要一个注解ThreadLocalCache,在需要缓存的方法上加上该注解
2. 需要一个切面,借助ThreadLocal,将结果缓存起来,利用环绕通知来实现方法拦截从缓存中返回方法执行结果
3、代码实现
3.1、ThreadLocalCache注解创建
作用于方法级别
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ThreadLocalCache {
}
3.2、ThreadLocalTestAspect切面创建
java
@Aspect
@Component
public class ThreadLocalTestAspect {
private ThreadLocal<Map<Object, Object>> threadLocal = new ThreadLocal<>();
@Around("@annotation(com.example.test.ThreadLocalCache)")
private Object myPointcut(ProceedingJoinPoint proceedingJoinPoint) {
//获取方法的入参
Object[] args = proceedingJoinPoint.getArgs();
Signature signature = proceedingJoinPoint.getSignature();
//获取目标方法名
String name = signature.getName();
//获取目标方法的类的完全限定名
String declaringTypeName = signature.getDeclaringTypeName();
//生成缓存key
Object key = SimpleKeyGenerator.generateKey(args, declaringTypeName, name);
if (Objects.isNull(threadLocal.get())) {
threadLocal.set(new HashMap<>(8));
}
try {
if (!threadLocal.get().containsKey(key)) {
threadLocal.get().put(key, proceedingJoinPoint.proceed());
}
} catch (Throwable e) {
//日志记录
e.printStackTrace();
}
return threadLocal.get().get(key);
}
public void removeThreadLocal(){
threadLocal.remove();
}
}
4、测试过程
- 创建一个接口及实现
java
public interface ThreadLocalTestService {
Long getParentIdByName(String name);
}
java
@Service
public class ThreadLocalTestServiceImpl implements ThreadLocalTestService{
@ThreadLocalCache
@Override
public Long getParentIdByName(String name) {
//根据name查询父级ID
System.out.println("com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了");
return 666L;
}
}
- 方法调用
java
@RestController
@RequestMapping("/ThreadLocalTest")
public class ThreadLocalTest {
@Autowired
private ThreadLocalTestService threadLocalTestService;
@Autowired
private ThreadLocalTestAspect threadLocalTestAspect;
@GetMapping("getParentIdByName")
public Long getParentIdByName(String name){
System.out.println(Thread.currentThread().getName());
threadLocalTestService.getParentIdByName(name);
threadLocalTestService.getParentIdByName(name);
Long parentId = threadLocalTestService.getParentIdByName(name);
threadLocalTestAspect.removeThreadLocal();
return parentId;
}
}
3.执行结果
java
http-nio-8087-exec-1
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-2
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-4
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-5
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-6
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-7
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-8
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-10
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-9
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-3
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-1
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-2
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-是线程名字,可以看到http-nio-8087-exec-1执行了两次,每次都调用四次getParentIdByName 方法,但getParentIdByName 方法实际至执行了一次,剩下的三次是从缓存中获取的。
这里需要注意的是:线程每次结束的时候都需要调用threadLocalTestAspect.removeThreadLocal();为的是把当前线程threadLocal里的缓存抹掉,因为同一个线程可能会被重复使用,所以不抹掉,可能会导致多次请求使用同一个线程,目标方法只会执行一次,和我们的最初的实现效果是违背的。
下面是不调用threadLocalTestAspect.removeThreadLocal();的执行结果
java
http-nio-8087-exec-1
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-3
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-8
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-5
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-6
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-7
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-4
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-9
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-10
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-2
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-1
http-nio-8087-exec-3
http-nio-8087-exec-8
http-nio-8087-exec-5
可以很清楚的看到http-nio-8087-exec-1、3、5、8再次请求的时候getParentIdByName 方法并没有执行了,因为之前的threadlocal缓存没有被remove导致的。