-
Notifications
You must be signed in to change notification settings - Fork 39
基于注解的缓存装饰器 #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
基于注解的缓存装饰器 #126
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,123 @@ | ||
| package com.github.hcsp.annotation; | ||
|
|
||
| import net.bytebuddy.ByteBuddy; | ||
| import net.bytebuddy.implementation.MethodDelegation; | ||
| import net.bytebuddy.implementation.bind.annotation.*; | ||
|
|
||
| import net.bytebuddy.matcher.ElementMatchers; | ||
|
|
||
| import java.lang.reflect.Method; | ||
| import java.util.Arrays; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.Callable; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| public class CacheClassDecorator { | ||
| // 将传入的服务类Class进行增强 | ||
| // 使得返回一个具有如下功能的Class: | ||
| // 如果某个方法标注了@Cache注解,则返回值能够被自动缓存注解所指定的时长 | ||
| // 这意味着,在短时间内调用同一个服务的同一个@Cache方法两次 | ||
| // 它实际上只被调用一次,第二次的结果直接从缓存中获取 | ||
| // 注意,缓存的实现需要是线程安全的 | ||
| public static <T> Class<T> decorate(Class<T> klass) { | ||
| return klass; | ||
| public static <T> Class<? extends T> decorate(Class<T> klass) { | ||
| return new ByteBuddy() | ||
| .subclass(klass) | ||
| .method(ElementMatchers.isAnnotatedWith(Cache.class)) | ||
| .intercept(MethodDelegation.to(CacheAdvisor.class)) | ||
| .make() | ||
| .load(klass.getClassLoader()) | ||
| .getLoaded(); | ||
| } | ||
|
|
||
| public static class CacheAdvisor { | ||
| private static ConcurrentHashMap<CacheKey, CacheValue> cache = new ConcurrentHashMap<>(); | ||
|
|
||
| @RuntimeType | ||
| public static Object cache( | ||
| @SuperCall Callable<Object> superCall, | ||
| @Origin Method method, | ||
| @This Object thisObject, | ||
| @AllArguments Object[] arguments | ||
| ) throws Exception { | ||
| CacheKey cacheKey = new CacheKey(thisObject, method.getName(), arguments); | ||
| CacheValue getValueFromCache = cache.get(cacheKey); | ||
| if (getValueFromCache != null) { | ||
| if(isExpiredForCache(getValueFromCache,method)){ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return putInCache(superCall, cacheKey); | ||
| } | ||
| return getValueFromCache.value; | ||
| } else { | ||
| return putInCache(superCall, cacheKey); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| private static Object putInCache(Callable<Object> superCall, CacheKey cacheKey) throws Exception { | ||
| long time = System.currentTimeMillis(); | ||
| Object result = superCall.call(); | ||
| cache.put(cacheKey, new CacheValue(result, time)); | ||
| return result; | ||
| } | ||
| private static boolean isExpiredForCache(CacheValue cacheValue, Method method) { | ||
| int cacheSeconds = method.getAnnotation(Cache.class).cacheSeconds(); | ||
| return System.currentTimeMillis() - cacheValue.time >= cacheSeconds*1000; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
||
| public static void main(String[] args) throws Exception { | ||
| DataService dataService = decorate(DataService.class).getConstructor().newInstance(); | ||
|
|
||
| // 有缓存的查询:只有第一次执行了真正的查询操作,第二次从缓存中获取 | ||
| System.out.println(dataService.queryData(1)); | ||
| Thread.sleep(1 * 1000); | ||
| System.out.println(dataService.queryData(1)); | ||
| Thread.sleep(3 * 1000); | ||
| System.out.println(dataService.queryData(1)); | ||
|
|
||
| // 无缓存的查询:两次都执行了真正的查询操作 | ||
| System.out.println(dataService.queryDataWithoutCache(1)); | ||
| Thread.sleep(1 * 1000); | ||
| System.out.println(dataService.queryDataWithoutCache(1)); | ||
| } | ||
|
|
||
| private static class CacheKey { | ||
| private final Object thisObject; | ||
| private final String methodName; | ||
| private final Object[] arguments; | ||
|
|
||
| public CacheKey(Object thisObject, String methodName, Object[] arguments) { | ||
| this.thisObject = thisObject; | ||
| this.methodName = methodName; | ||
| this.arguments = arguments; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) return true; | ||
| if (o == null || getClass() != o.getClass()) return false; | ||
| CacheKey cacheKey = (CacheKey) o; | ||
| return Objects.equals(thisObject, cacheKey.thisObject) && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| Objects.equals(methodName, cacheKey.methodName) && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| Arrays.equals(arguments, cacheKey.arguments); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| int result = Objects.hash(thisObject, methodName); | ||
| result = 31 * result + Arrays.hashCode(arguments); | ||
| return result; | ||
| } | ||
| } | ||
|
|
||
| private static class CacheValue { | ||
| private final Object value; | ||
| private final long time; | ||
|
|
||
| public CacheValue(Object value, long time) { | ||
| this.value = value; | ||
| this.time = time; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,7 @@ public class DataService { | |
| * @param id 数据ID | ||
| * @return 查询到的数据列表 | ||
| */ | ||
| @Cache | ||
| @Cache(cacheSeconds=2) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| public List<Object> queryData(int id) { | ||
| // 模拟一个查询操作 | ||
| Random random = new Random(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
不应使用 '.*' 形式的导入 - net.bytebuddy.implementation.bind.annotation.* 。