缓存基础
概念
Spring提供了一种在方法级别进行的缓存抽象,如果已经为提供的参数执行过方法,那么再次以同样参数执行时则不必执行方法就可以返回被缓存结果。
第一个缓存应用程序
本章需要的依赖文件为:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples.service.service</groupId>
<artifactId>SpringAOPTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<!-- Generic properties -->
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring -->
<spring-framework.version>4.3.10.RELEASE</spring-framework.version>
<!-- Hibernate / JPA -->
<hibernate.version>5.2.10.Final</hibernate.version>
<!-- Logging -->
<logback.version>1.2.3</logback.version>
<slf4j.version>1.7.25</slf4j.version>
<!-- Test -->
<junit.version>4.12</junit.version>
<!-- AspectJ -->
<aspectj.version>1.8.10</aspectj.version>
<!-- Cache -->
<hazelcast.version>3.8.4</hazelcast.version>
</properties>
<dependencies>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- Test Artifacts -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-framework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<!-- Cache -->
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-all</artifactId>
<version>${hazelcast.version}</version>
</dependency>
</dependencies>
</project>
项目目录结构
首先创建域类User:
public class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" + "id=" + id +", name='" + name + '\'' + '}';
}
}
然后创建UserService类,其中包含一个缓存方法getUser来返回输入id对应的User对象:
public class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" + "id=" + id +", name='" + name + '\'' + '}';
}
}
然后就可以创建上下文XML文件配置缓存,此处使用的是SimpleCacheManager:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.0.xsd">
<cache:annotation-driven />
<bean id="userService" class="com.wiley.beginningspring.ch10.UserService" />
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean id="users" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" />
</set>
</property>
</bean>
</beans>
最后创建Main类执行程序:
public class Main {
public static void main(String... args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean(UserService.class);
User userFetch1 = userService.getUser(1);
System.out.println(userFetch1);
User userFetch2 = userService.getUser(1);
System.out.println(userFetch2);
}
}
输出结果为:
可以观察发现,两次getUser(1)只实际执行了一次方法。
也可以采用Java文件配置缓存,此时配置文件为:
@Configuration
@ComponentScan(basePackages = {"com.wiley.beginningspring.ch10"})
@EnableCaching
public class ApplicationConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("users")));
return cacheManager;
}
}
此时Main函数中导入上下文变为ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
缓存注解
@Cacheable
该注解放在被缓存方法前,必须用字符串或者value特性来提供储存器名称,也可以用value特性提供多换个储存器用花括号括起来。
键生成器
用@Cacheble属性的key特性使用SpEL来自定义键值
@Cacheable(value = "users", key = "#user.nationalId")
public User getUser(User user) {
System.out.println("User with id " + user.getId() + " requested.");
return users.get(user.getId());
}
此处自定义国家id为键值。
条件缓存
使用@Cacheable注解的condition特性,可以根据条件应用缓存:
@Cacheable(value = "users", condition = "#user.age < 35")
public User getUser(User user) {
System.out.println("User with id " + user.getId() + " requested.");
return users.get(user.getId());
}
此处指定缓存35岁一下用户。在Main中测试:
User user1 = new User(2, "Mert", "5552345060", 34);
User userFetch1 = userService.getUser(user1);
System.out.println(userFetch1);
User userFetch2 = userService.getUser(user1);
System.out.println(userFetch2);
User user2 = new User(1, "Kenan", "5554332088", 37);
User userFetch3 = userService.getUser(user2);
System.out.println(userFetch3);
User userFetch4 = userService.getUser(user2);
System.out.println(userFetch4);
结果为:
可发下id=2的请求只执行了一次,id = 1的请求执行了两次。因为二者对应对象年龄区别。
@CacheEvict
定义了相关方法从给定缓存器中逐出一个值。如果刚刚的查找方法会创建缓存,那么更新或者删除操作就需要更新和删除原有缓存以免查找到本来不应该存在的数据。
@CacheEvict("users")
public void removeUser(int id) {
users.remove(id);
}
@CachePut
该注解与@Cacheable类似,但是先执行方法再将返回值放入缓存。
@Caching
可以为一个方法定义提供@Cacheable、@CacheEvict或者@CachePut数组。
@Caching(cacheable = {
@Cacheable(value = "students", condition = "#obj instanceof T(com.wiley.beginningspring.ch10.Student)"),
@Cacheable(value = "teachers", condition = "#obj instanceof T(com.wiley.beginningspring.ch10.Teacher)")
})
public Person getPerson(Person obj) {
return ppl.get(obj.getId());
}
缓存管理器的实现
SimpleCacheManager
该缓存器可以设置缓存列表并利用列表中缓存进行操作。
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean id="users" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" />
</set>
</property>
</bean>
NoOpCacheManager
主要用于测试目的,实际上并不在存储器中缓存任何数据。
<bean id="cacheManager" class="org.springframework.cache.support.NoOpCacheManager" />
ConcurrentMapCacheManager
在幕后使用了JDK的ConcurrentMap,与SimpleCacheManager配置相同功能,但不需要像前面那样定义缓存存储器。
<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" />
CompositeCacheManager
使用单个缓存管理器来定义多个缓存管理器。
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
<property name="cacheManagers">
<list>
<bean class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean id="teachers" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" />
</set>
</property>
</bean>
<bean class="com.hazelcast.spring.cache.HazelcastCacheManager">
<constructor-arg ref="hazelcast" />
</bean>
</list>
</property>
</bean>
上例将简单缓存管理器和Hazelcast缓存管理器捆绑在一起,为他们定义不同的缓存储存器用于缓存不同数据。
将SpEL投射到缓存中
注解的key、unless、condition特性的缓存抽象中使用了SpEL
SpEL根据上下文对表达式进行评估,并通过使用缓存抽象,提供了root对象相关联的缓存特定的内置参数
- #root.methodName:被调用方法的名称。
- #root.method:被调用的方法,将是java.lang.reflect.Method的实例。
- #root.target:被调用方法目标对象的实例。
- #root.targetClass:被调用方法目标对象的类。
- #root.args:传递给被调用方法的参数数组。
- #root.caches:通过注解而调用的方法映射的缓存集合。
- #result:方法调用结果,可用户@Cacheable的unless特性,@CachePut、@CacheEvict注解。
- #p<argIndex>被调用方法参数,argIndex使用参数索引,从0开始。
- #<argument name>被调用方法参数名,如#user,#id,#name等等。
以编程的方式初始化缓存
在@PostConstruct注解的方法中初始化缓存存储器。
@PostConstruct
public void setup() {
Cache usersCache = cacheManager.getCache("users");
for (Integer key : users.keySet()) {
usersCache.put(key, users.get(key));
}
}
User类同上。
和其他缓存框架的集成
Ehcache
此时上下文配置中缓存管理器配置为Ehcache
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcache" />
</bean>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml"/>
</bean>
在配置中加载了ehcache缓存框架的配置。
<ehcache>
<cache name="users"
maxElementsInMemory="1000" />
</ehcache>
Guava
此时只需要在上下文的缓存定义中定义cacheManager Bean既可以获取配置并运行了。
<bean id="cacheManager" class="org.springframework.cache.guava.GuavaCacheManager" />
Hazelcast
配置全在上下文配置文件中完成。
<hz:hazelcast id="hazelcast">
<hz:config>
<hz:map name="users">
<hz:map-store enabled="true" class-name="com.wiley.beginningspring.ch10.User" write-delay-seconds="0"/>
</hz:map>
</hz:config>
</hz:hazelcast>
<bean id="userService" class="com.wiley.beginningspring.ch10.UserService" />
<bean id="cacheManager" class="com.hazelcast.spring.cache.HazelcastCacheManager">
<constructor-arg ref="hazelcast" />