在 Spring 中使用 EhCache 注解进行缓存
已发表: 2022-03-11EhCache 是一种广泛使用的纯 Java 缓存,可以轻松与大多数流行的 Java 框架集成,例如 Spring 和 Hibernate。 它通常被认为是 Java 应用程序最方便的选择,因为它可以轻松集成到项目中。 特别是:
- 只需在项目中包含 JAR 即可设置它。 不需要额外的安装步骤。
- 它与应用程序在同一进程中运行,因此速度很快。 运行不需要额外的服务。
简而言之,EhCache 是任何纯 Java 应用程序的绝佳选择。
此外,EhCache Spring Annotations 允许无缝集成到任何 Spring 应用程序中,只需将注释添加到可缓存方法,而无需修改方法实现。
虽然 EhCache 提供了直接、丰富的 API 来以编程方式操作缓存,但本文主要侧重于使用 EhCache Spring Annotations 以一种不那么侵入性的方式增强您的 Spring 应用程序。 我们将建立一个 Spring MVC 项目并在 Tomcat 中部署一个 RESTful Web 服务。 然后,EhCache 将被集成到 Web 服务中。
项目概况
我们将在示例项目的上下文中演示 EhCache 注释。 我们将建立一个托管在 Tomcat 8 服务器上的基于 Spring MVC 的 Web 服务。
我在 Eclipse 中开发了该项目,可以按照此处的说明进行安装。
可以在此处下载 Tomcat 的最新稳定版本 Tomcat 8。
当然,这些特定平台不是 EhCache 的要求; 您可以随时选择自己喜欢的 IDE 和服务器。
EhCache Spring Annotations JAR 可在此处获得。 正如我们所见,每个版本都有两个 JAR:一个有依赖关系,一个没有。 具有依赖项的还包括 EhCache 2 和 Spring 3,它们是 EhCache 注释工作所必需的。 如果我们下载具有依赖项的文件并将其添加到我们的构建路径中,则设置会更容易。
EhCache Spring Annotations 也与 Spring 4 兼容,但必须单独配置。 目前尚不清楚该项目是否会在不久的将来支持 EhCache 3。 对于正在使用或打算使用 EhCache 3 的用户,不建议使用本文中讨论的注释方法。
最后,我们将使用 Maven 来管理一切。 大多数 Eclipse 安装都预先打包了 Maven,但也可以在此处获取。 可以相当容易地添加 Spring MVC 和 EhCache Spring Annotations 依赖项,如本文后面所示。
项目设置
如果您以前从未设置过 Spring 项目,您可能还会发现 Stefan Varga 关于该主题的帖子提供了丰富的信息。
对于这个演示,我们将使用 Maven 原型maven-archetype-webapp
设置一个基本项目。 整个文件结构将如下所示:
创建一个目录src/main/java
,其中包含三个包: com.toptal.blog
、 com.toptal.blog.cache
和com.toptal.blog.service
。 我们的应用程序源将放在这些包中,如下所述。
让我们在web.xml
中定义一个名为“springrest”的 Tomcat servlet:
<web-app> ... <servlet> <servlet-name>springrest</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springrest</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
除非另有明确说明,否则 Spring MVC DispatcherServlet
将在WEB-INF
目录中查找名为{servlet-name}-servlet.xml
的 XML 配置文件。 让我们创建一个名为springrest-servlet.xml
的配置文件。 要启用使用@RequestMapping
注释的 Spring 流程控制器方法,我们只需将<mvc:annotation-driven />
添加到此文件中。 另外,让我们通过添加<context:component-scan base-package="com.toptal.blog" />
来定义 Spring 的基本包以自动扫描和注册 bean。 springrest-servlet.xml
配置变为:
<beans ... > <mvc:annotation-driven /> <context:component-scan base-package="com.toptal.blog" /> </beans>
一个简单的 RESTful Web 服务
现在我们的项目已正确配置,让我们实现一个简单的“消息服务”API。 在我们的基础包project.toptal.blog
中,我们将添加SpringRestControllerWithEhCache.java
,其中一个 GET 方法通过 ID 获取消息,一个 POST 方法通过 ID 设置消息:
@RestController @RequestMapping( "/" ) public class SpringRestControllerWithEhCache { @Autowired MessageService messageService; @RequestMapping( value = "/message/{id}", method = RequestMethod.GET ) public String getMessage( @PathVariable Integer id ) { String message = messageService.getMessage( id ); System.out.println( "get message ["+message+"] at "+new Date() ); return message; } @RequestMapping( value = "/message/set/{id}/{message}", method = RequestMethod.POST ) public String setMessage( @PathVariable Integer id, @PathVariable String message ) { System.out.println( "set message ["+message+"] at "+new Date() ); messageService.setMessage( id, message ); return message; } }
我们将在com.toptal.blog.service
中定义MessageService
类。 它将访问存储在我们的记录系统 (SOR) 中的消息。 在生产应用程序中,SOR 类似于关系数据库。 为简单起见,我们将使用HashMap
:
@Service public class MessageService { private ConcurrentHashMap<Integer, String> messages = new ConcurrentHashMap<Integer, String>(); public String getMessage( Integer id ) { System.out.println( "Getting data from SOR......" ); return messages.get( id ); } public void setMessage( Integer id, String message ){ messages.put( id, message ); } }
现在,如果我们将项目导出为 WAR 并将其部署在 Tomcat 中,我们应该能够通过在http://localhost:8080/EhCacheExample/message/set/1/test_message
处创建 HTTP POST 请求来为 ID=1 设置消息,例如“test_message” http://localhost:8080/EhCacheExample/message/set/1/test_message
。 然后,我们应该能够通过http://localhost:8080/EhCacheExample/message/1
的 HTTP GET 请求获取“test_message”。 我使用 Insomnia 作为方便的 REST 客户端来进行测试。
插入 EhCache Spring 注解
现在让 EhCache 为我们工作。 只需几个快速步骤即可配置我们的项目以正确运行 EhCache。
第 1 步:更新依赖项以使用 EhCache Spring Annotations
在 Maven 的pom.xml
中添加 EhCache Spring Annotations 依赖项:
<!-- ehcache --> <dependency> <groupId>com.googlecode.ehcache-spring-annotations</groupId> <artifactId>ehcache-spring-annotations</artifactId> <version>1.2.0</version> </dependency>
步骤 2:设置自定义缓存管理器
Spring 有一个内置的 EhCache 缓存管理器org.springframework.cache.ehcache.EhCacheManagerFactoryBean
。 这适用于大多数缓存情况,但我发现定义自定义缓存管理器很有用,因为它允许我使用相同的缓存管理器以编程方式或通过注释控制缓存。 本文重点介绍注解,但让我们继续定义一个自定义缓存管理器,以便在需要时做好准备。 如果您更喜欢使用默认缓存管理器,则可以跳过此步骤。

我们将在com.toptal.blog.cache.CustomCacheManager
中定义新类:
public class CustomCacheManager extends net.sf.ehcache.CacheManager{ public CustomCacheManager(){ super(); } /* Add your own cache methods here. * * public void myCustomCacheMethod(){ * // your code here * } * */ }
通过更新springrest-servlet.xml
来启用它,如下所示:
... <ehcache:annotation-driven cache-manager="customCacheManager" /> <bean class="com.toptal.blog.cache.CustomCacheManager" scope="singleton"></bean> ...
第三步:配置 EhCache
最后,在类路径中创建 EhCache 配置文件ehcache.xml
。 默认情况下,Eclipse 会在类路径中包含src/main/resources
,我们将把文件放在这里。 此文件是 EhCache 正常运行所必需的。 它定义了缓存名称和每个缓存的一些属性,例如timeToLiveSeconds
:
<ehcache xmlms:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="cache" /> <cache name="messageCache" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="0" timeToLiveSeconds="10" overflowToDisk="false" memoryStoreEvictionPolicy="LFU" /> </ehcache>
第 4 步:测试缓存
现在,一切准备就绪,使用 EhCache 应该是一件轻松愉快的工作。 我们可以简单地将@Cacheable
添加到我们想要缓存的方法或类中。 例如,我在MessageService
的getMessage
方法中添加了@Cacheable
。 就这么容易!
@Cacheable( cacheName = "messageCache" ) public String getMessage( Integer id ) { System.out.println( "Getting data from SOR......" ); return messages.get( id ); }
为了测试我们的缓存是否正常工作,我们可以通过在http://localhost:8080/EhCacheExample/message/set/1/newMessage
发出 HTTP POST 请求来创建 ID=1 的消息,然后获取 ID= 的消息1 多次,对http://localhost:8080/EhCacheExample/message/1
的 GET 请求。 正如您在下面的控制台输出中看到的那样,Web 服务在我们第一次请求消息时要求 SOR 获取消息,但对于接下来的两个请求则没有,而是返回缓存的消息。 因为我们将timeToLiveSeconds
定义为 10,所以 Web 服务调用 SOR 以在 10 秒后再次获取消息:
set message [newMessage] at Sun Dec 06 23:55:39 MST 2015 get message [newMessage] at Sun Dec 06 23:55:42 MST 2015 Getting data from SOR...... get message [newMessage] at Sun Dec 06 23:55:47 MST 2015 get message [newMessage] at Sun Dec 06 23:55:49 MST 2015 get message [newMessage] at Sun Dec 06 23:55:54 MST 2015 Getting data from SOR......
刷新缓存
现在,我们正在享受缓存给我们带来的速度和便利,而 EhCache 足够好,可以每 10 秒自行刷新一次。 但是,如果我们希望在 SOR 更新后立即刷新它呢? EhCache Spring Annotation 提供@TriggersRemove
以在调用带注释的方法时从缓存中删除指定的键。 在我们的消息服务 API 中,应该在调用setMessage
时从缓存中删除缓存的消息。 因此,下一次getMessage
请求进入时,缓存将从 SOR 中获取一条新记录:
@Cacheable( cacheName = "messageCache", keyGenerator = @KeyGenerator ( // method name is not included in cache key to work with @TriggersRemove name = "HashCodeCacheKeyGenerator", properties = @Property( name="includeMethod", value="false" ))) public String getMessage( Integer id ) { System.out.println( "Getting data from SOR......" ); return messages.get( id ); } @TriggersRemove( cacheName = "messageCache", keyGenerator = @KeyGenerator ( name = "HashCodeCacheKeyGenerator", properties = @Property( name="includeMethod", value="false" ))) public void setMessage( @PartialCacheKey Integer id, String message ) { messages.put( id, message ); }
缓存管理器使用密钥生成器来生成缓存密钥。 可以在此处找到预定义的缓存密钥生成器列表。 默认情况下, @KeyGenerator
使用方法名和传入的参数来生成缓存键。 但是由于我们希望setMessage
方法生成与getMessage
相同的 key 并删除与该 key 关联的缓存值,因此我们必须仅使用消息 ID 作为 key 并消除 key 生成的方法名称。 因此,对于这两种方法,我们将密钥生成器的includeMethod
属性设置为false
。 此外,由于setMessage
有两个参数,我们在id
参数上使用 EhCache 的@PartialCacheKey
注释来指定它是唯一应该由密钥生成器使用的参数。 最后,回想一下,我们为此资源类型配置了一个专用缓存messageCache
,因此仅将 ID 用作键不会存在与其他资源类型发生冲突的危险。
现在,如果我们对 ID=1 的消息进行多次 HTTP 请求,如下所示:
HTTP POST: http://localhost:8080/EhCacheExample/message/set/1/newMessage1 HTTP GET:http://localhost:8080/EhCacheExample/message/1 HTTP POST: http://localhost:8080/EhCacheExample/message/set/1/newMessage2 HTTP GET:http://localhost:8080/EhCacheExample/message/1
控制台将显示:
set message [newMessage1] at Tue Dec 08 17:53:44 MST 2015 get message [newMessage1] at Tue Dec 08 17:53:47 MST 2015 Getting data from SOR...... set message [newMessage2] at Tue Dec 08 17:53:50 MST 2015 get message [newMessage2] at Tue Dec 08 17:53:53 MST 2015 Getting data from SOR......
结论
最终的项目结构如下所示:
在这个示例中,我们首先创建了一个简单的 Spring MVC RESTful Web 应用程序。 无需修改现有应用程序的任何一行代码,我们就可以使用 EhCache Spring Annotations 将 EhCache 无缝集成到应用程序中。 我们已经证明 EhCache Spring Annotations 既易于安装(通过添加其 Maven 依赖项)又易于使用(通过向方法添加注释)。
延伸阅读
EhCache 文档可以在这里找到,EhCache Spring Annotations 文档在这里。
此外,在 GitHub 上查看本文中描述的示例项目。