I really hate when my source files are bloated with lines and lines of code that do not implement any business logic at all. One of the sources of this was certainly code related to caching. Using Google’s Memcache service with the low-level API or via JCache facade is not complicated but it really was not elegant enough for my taste. I was hoping to find some annotations-based solution that will work for me, but no luck.
So, this appeared as a challenge and, inspired by the Springmodules caching implementation, I decided to research further and see how complicated could it be to implement. On the other hand, I just waited for an excuse to write my first annotation library.
(Note: If I were using Spring, I would probably try to use Springmodules. However, I’ve decided earlier not to use Spring on my GAE project and took a more lightweight path with Stripes.)
Annotation class
I started defining an annotation class that would allow me to tag methods for which I wanted to cache return value:
view sourceprint?1 @Retention(RetentionPolicy.RUNTIME)
2 @Target(ElementType.METHOD)
3 public @interface MemCacheable {
4 /** Defines expiration for some seconds in the future. */
5 int expirationSeconds() default 0;
6 /** Defines whether to cache null values. **/
7 boolean cacheNull() default true;
8 }
The idea was that @MemCacheable on a method would mean that return value of the method should be cached (in Memcache) as long as possible, automatically generating a caching key from the method signature and its arguments. For example:
view sourceprint?1 @MemCacheable
2 public Result myServiceMethod(Foo arg1, Bar arg2)
However, you could easily override this specifying how long do you want the keep the return value in cache:
view sourceprint?1 @MemCacheable(expirationSeconds=600)
2 public Result myServiceMethod(Foo param1, Bar param2)
OK, creation of the annotation class was easy but it has no real value without a logic behind it.
Method interception
Next, I needed a solution for a method interception that will allow me to implement around advice for tagged methods. Until that moment, my project didn’t use any dependency injection framework so I decided to use Guice because:
it doesn’t force me to change to many things in the project (except few factory classes), and
it uses standard AOPAlliance interfaces for advices so, at least theoretically, interceptors could be reused with some other framework too.
Here’s the simplified version of the method interceptor:
view sourceprint?01 public class CachingInterceptor implements MethodInterceptor {
02 private MemcacheService memcache;
03
04 public CachingInterceptor() {
05 memcache = MemcacheServiceFactory.getMemcacheService();
06 }
07
08 public Object invoke(MethodInvocation invocation) throws Throwable {
09 Method method = invocation.getMethod();
10 MemCacheable memCacheable = method.getAnnotation(MemCacheable.class);
11 if(memCacheable != null) {
12 return handleMemCacheable(invocation, memCacheable);
13 }
14 return invocation.proceed();
15 }
16
17 private Object handleMemCacheable(MethodInvocation invocation, MemCacheable options) throws Throwable {
18 Object key = generateKey(invocation.getThis(), invocation.getMethod(), options.group(), invocation.getArguments());
19 Object result = memcache.get(key);
20 if (result != null)
21 return result;
22 result = invocation.proceed();
23 putToCache(key, result, options);
24 return result;
25 }
26
27 protected boolean putToCache(Object key, Object value, MemCacheable options) {
28 try {
29 if (value == null && !options.cacheNull())
30 return false;
31 Expiration expires = null;
32 if (options.expirationSeconds() > 0)
33 expires = Expiration.byDeltaSeconds(options.expirationSeconds());
34 MemcacheService.SetPolicy setPolicy = MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT;
35 if (options.setAlways())
36 setPolicy = MemcacheService.SetPolicy.SET_ALWAYS;
37 memcache.put(key, value, expires, setPolicy);
38 return true;
39 } catch (Throwable t) {
40 return false;
41 }
42 }
43
44 protected Object generateKey(...) {
45 // generate key using hash codes of the target method, method arguments, etc.
46 }
47 }
For the key generation I used code from the Springmodules project mentioned above.
Gluing it with Guice
Adding Guice to the project was simple. First, I defined a module that ties caching interceptor with the annotation class:
view sourceprint?1 public class CachingInterceptorsModule extends AbstractModule {
2 protected void configure() {
3 bindInterceptor(Matchers.any(),
4 Matchers.annotatedWith(MemCacheable.class),
5 new CachingInterceptor());
6 }
7 }
That’s about it. To instantiate classes that use the caching annotations I created a simple class with the Guice injector:
view sourceprint?1 public class AOP {
2 private static final Injector injector =
3 Guice.createInjector(new CachingInterceptorsModule());
4
5 public static <T> T getInstance(Class<T> clazz) {
6 return injector.getInstance(clazz);
7 }
8 }
Setter annotation (bonus)
After the initial implementation was in place, my appetites were growing… What if I wanted to update the cached value before it expires? I could use something like this:
view sourceprint?1 @MemCacheSetter(group="ResultsCache")
2 public void cacheResult(Foo param1, Bar param2, Result result)
3 {}
With the above annotation, I could generate a cache key using all method arguments except the last one, which is the object we’re storing to cache. So, for the above example, we generate a cache key using the group name specified in the annotation and two method arguments (param1 and param2). Simple isn’t it? (We don’t even need the method body as the interceptor will never invoke it.)
This requires small change to the @MemCacheable annotation. To benefit from the setter annotation, we’ll add support for the group argument:
view sourceprint?1 @MemCacheable(group="ResultsCache")
2 public Result myServiceMethod(Foo arg1, Bar arg2)
In effect, when the group name is specified, it’s used as a part of a cache key instead of the method signature. In cases when the group name is not provided, the method signature will be used instead.
I pushed the complete project sources and jar to the GJUtil project so you can find it there if you’re interested. The code is stable and already running in my BugDigger application. (If you’re a web developer, and I guess you are if you’re reading this, you may find it useful too.)
A downside of this library is its dependency on Guice for AOP things. I think it would be useful to remove dependency on any framework and inject caching interceptors with bytecode manipulation (e.g. using ASM) as a part of the compilation process. This would enable use of caching annotations with any class, not just those instantiated by dependency injector. If anyone is interested in sponsoring this effort (or maybe contributing some code), let me know.
The next week I’ll extend to the above code and show you how to use the same technique for caching in HTTP request or application context, avoiding RPC calls for the Memcache when local or request scope caching is sufficient.
文章不错,转至http://radomirml.com/2010/05/20/declarative-caching-for-appengine
分享到:
相关推荐
Why is GraphQL the most innovative technology for fetching data since Ajax? By providing a query language for your APIs and a runtime for fulfilling queries with your data, GraphQL presents a clear ...
"Declarative dependency management for Matlab.zip" 提供了一种声明式的方式来解决这个问题,旨在简化和标准化Matlab项目的依赖管理过程。这个压缩包可能包含了一个名为`ToolboxToolbox-master`的主项目文件夹和一...
Declarative Services(DS),在OSGi环境中,是一种声明式的方式来管理服务和组件的机制。它的核心思想是通过XML配置文件来定义服务的提供者和消费者,而不是通过代码直接引用和依赖其他服务,从而实现更加灵活和...
"Pure Declarative Programming in Swift, Among Other Things"这个主题深入探讨了如何在Swift中实现这一概念,以及它与其他编程范式的对比。 在Swift中,声明式编程主要体现在Swift的API设计上,比如Swift的数组和...
《声明式解析器:declarative-parser 0.1版本详解》 在当今的软件开发领域,解析器扮演着至关重要的角色,它们能够帮助我们处理和理解各种格式的数据,如XML、JSON、YAML等。开源项目"declarative-parser-0.1.zip...
Swift声明式配置 Swift声明式配置(简称SDC)是一个很小的库,使您能够以符合人体工程学的方式以声明式,一致且易于理解的方式配置对象。 它可用于配置任何平台上的任何对象,包括服务器端swift。...
### Python SQLAlchemy 的 Mapping 与 Declarative 详解 #### 一、引言 在 Python 开发领域中,SQLAlchemy 是一款强大的 ORM (Object Relational Mapper) 工具库,它为开发者提供了高级功能来实现 Python 类与...
This book is for readers who are comfortable building Swift apps, and want to make the exciting leap into building their app UI with modern, declarative code. What is SwiftUI? SwiftUI lets you build...
<declarative> 自定义元素以声明方式创建Shadow DOM 它应该与给出的建议紧密合作演示版安装使用安装组件:$ bower install declarative-shadow-dom --save 或 。用法如果需要,内置的导入自定义元素可扩展polyfill&...
1.Declarative Inspector检查选定的DOM元素的声明性视图模型属性(动作,ctx,数据,dataProvider,i18n,消息,onEvent)。 请参见声明性检查器示例的屏幕截图。 2.Declarative Tracer跟踪关键声明事件的执行流程...
Qt5Declarative_jll.jl (v5.15.2 + 0) 这是使用构造的自动生成的包。 原始的脚本可以在社区构建树上找到。 如果您有任何问题,请向Yggdrasil报告。 有关JLL软件包以及如何使用它们的更多详细信息,请参见...
**PyPI 官网下载 | django_declarative_apis-0.19.0-py3-none-any.whl** PyPI(Python Package Index)是Python社区的官方软件仓库,它为开发者提供了一个平台来发布、分享和安装Python库。在这个案例中,我们关注...
Available with NetWeaver 7.0 (2004s) Web Dynpro for ABAP provides the same declarative UI development paradigm as Web Dynpro for Java directly out of the NetWeaver ABAP Application Server. Web Dynpro...
`declarative_base`是SQLAlchemy的一个核心特性,用于提供一种声明式的编程方式来定义数据库模型。 1. **Flask框架**:Flask是一个基于Werkzeug WSGI工具包和Jinja2模板引擎的小型但功能强大的Web服务器 Gateway ...
React-Router React Router: Declarative Routing for React.js 后端 Node.js Node.js HTTP 服务器 Express Express - Node.js web application framework 详细介绍参考:...
安装App Services 3 开机自检(板载BIG-IP) POST到AS3(LTM Config) POST到AS3(GSLB配置) 档案结构 ├── ansible.cfg # Settings for Ansible ├── declarative_demo.yaml # Playbook ├── files # ...
defi nition, design, and implementation of the Query Processing engine of SQL Server for over a decade, and I deeply respect his insight. They make an outstanding team of guides who can help you ...
Become familiar with immutable data structures and easily transform them with type-safe and declarative operations Create custom infix operators to simplify existing operations or even to start your ...