Spring Cache抽象-使用Java类注解的方式整合EhCache

时间:2022-01-13 05:19:49

概述

Spring Cache抽象-之缓存注解这篇博文中我们介绍了SpringCache抽象注解的使用方式

既然这是一个抽象,我们需要一个具体的缓存存储实现。比价流行的有:基于JDK java.util.concurrent.ConcurrentMap的缓存,EhCache,Gemfire缓存,Caffeine,Guava缓存和兼容JSR-107的缓存等等。这里我们使用Ehcache来实现这个缓存。

同时,我们使用EhCacheManagerFactoryBean的configLocation属性指定Ehcache的设置。如果未明确指定,则默认为ehcache.xml。


工程结构

Spring Cache抽象-使用Java类注解的方式整合EhCache

以及EhCache的配置文件:

Spring Cache抽象-使用Java类注解的方式整合EhCache

pom.xml 关键的依赖

<properties>            <springframework.version>4.3.9.RELEASE</springframework.version>
<ehcache.version>2.10.4</ehcache.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.version}</version>
</dependency>
<!-- EHCache -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>${ehcache.version}</version>
</dependency>
<!-- SLF4J/Logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true"
monitoring="autodetect"
dynamicConfig="true">

<diskStore path="java.io.tmpdir" />
<cache name="products"
maxEntriesLocalHeap="100"
maxEntriesLocalDisk="1000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">

<persistence strategy="localTempSwap" />
</cache>
</ehcache>
  • 我们设置一个名为’products’的缓存。

  • 最多100个products将保存在内存[堆叠]存储中,

  • 最多1000个products将被保留在DiskStore中

  • 指定的路径为“java.io.tmpdir”,它指的是默认的临时文件路径。

  • 如果product闲置超过5分钟,寿命超过10分钟,products缓存将会过期


实体类

package com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain;

import java.io.Serializable;

public class Product implements Serializable {

private static final long serialVersionUID = 123L;
private String name;
private double price;

/**
*
*
* @Title:Product
*
* @Description:构造函数
*
* @param name
* @param price
*/

public Product(String name, double price) {
this.name = name;
this.price = price;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

}

Product接口

package com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.service;

import com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product;

public interface ProductService {

Product getByName(String name);

Product updateProduct(Product product);

void refreshAllProducts();
}

接口实现类 及缓存配置

package com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.service;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product;

/**
*
*
* @ClassName: ProductServiceImpl
*
* @Description:@Service标注的服务层
*
* @author: Mr.Yang
*
* @date: 2017年10月3日 下午5:22:30
*/


@Service("productService")
public class ProductServiceImpl implements ProductService {

private static final Logger logger = Logger.getLogger(ProductServiceImpl.class);

private static List<Product> products;

static {
products = getDummyProducts();
}

@Cacheable(cacheNames = "products", key = "#name", condition = "#name != 'HTC'", unless = "#result==null")
@Override
public Product getByName(String name) {
logger.info("<!----------Entering getByName()--------------------->");
for (Product product : products) {
if (product.getName().equalsIgnoreCase(name)) {
return product;
}
}
return null;
}

@CachePut(cacheNames = "products", key = "#product.name", unless = "#result==null")
@Override
public Product updateProduct(Product product) {
logger.info("<!----------Entering updateProduct()--------------------->");
for (Product p : products) {
if (p.getName().equalsIgnoreCase(product.getName())) {
p.setPrice(product.getPrice());
return p;
}
}
return null;
}

@CacheEvict(cacheNames = "products", allEntries = true)
@Override
public void refreshAllProducts() {

}

private static List<Product> getDummyProducts() {
products = new ArrayList<Product>();
products.add(new Product("IPhone", 500));
products.add(new Product("Samsung", 600));
products.add(new Product("HTC", 800));
return products;
}


}

关键配置类 ,以及加载enhance

package com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.configuration;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@EnableCaching
@Configuration
@ComponentScan(basePackages = "com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache")
public class AppConfig {

@Bean
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}

@Bean
public EhCacheManagerFactoryBean ehCacheCacheManager() {
EhCacheManagerFactoryBean factory = new EhCacheManagerFactoryBean();
factory.setConfigLocation(new ClassPathResource("ehcache/ehcache.xml"));
factory.setShared(true);
return factory;
}
}

单元测试

package com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache;

import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

import com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.configuration.AppConfig;
import com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product;
import com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.service.ProductService;

public class SpringCacheWithEhCacheTest {

private static final Logger logger = Logger
.getLogger(SpringCacheWithEhCacheTest.class);

AbstractApplicationContext context = null;

@Before
public void initContext() {
context = new AnnotationConfigApplicationContext(AppConfig.class);

}

@Test
public void test() {
ProductService service = (ProductService) context
.getBean("productService");

logger.info("IPhone ->" + service.getByName("IPhone"));
logger.info("IPhone ->" + service.getByName("IPhone"));
logger.info("IPhone ->" + service.getByName("IPhone"));

logger.info("HTC ->" + service.getByName("HTC"));
logger.info("HTC ->" + service.getByName("HTC"));
logger.info("HTC ->" + service.getByName("HTC"));

Product product = new Product("IPhone", 550);
service.updateProduct(product);

logger.info("IPhone ->" + service.getByName("IPhone"));
logger.info("IPhone ->" + service.getByName("IPhone"));
logger.info("IPhone ->" + service.getByName("IPhone"));

logger.info("Refreshing all products");

service.refreshAllProducts();
logger.info("IPhone [after refresh]->" + service.getByName("IPhone"));
logger.info("IPhone [after refresh]->" + service.getByName("IPhone"));
logger.info("IPhone [after refresh]->" + service.getByName("IPhone"));
}

@After
public void releaseContext() {
((AbstractApplicationContext) context).close();
}

}

输出结果分析

2017-10-03 20:54:55,026  INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7bd1a567: startup date [Tue Oct 03 20:54:55 BOT 2017]; root of context hierarchy
2017-10-03 20:54:55,858 INFO [main] (EhCacheManagerFactoryBean.java:130) - Initializing EhCache CacheManager
2017-10-03 20:54:56,711 INFO [main] (ProductServiceImpl.java:40) - <!----------Entering getByName()--------------------->
2017-10-03 20:54:56,715 INFO [main] (SpringCacheWithEhCacheTest.java:32) - IPhone ->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@1a8f392
2017-10-03 20:54:56,716 INFO [main] (SpringCacheWithEhCacheTest.java:33) - IPhone ->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@1a8f392
2017-10-03 20:54:56,716 INFO [main] (SpringCacheWithEhCacheTest.java:34) - IPhone ->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@1a8f392
2017-10-03 20:54:56,717 INFO [main] (ProductServiceImpl.java:40) - <!----------Entering getByName()--------------------->
2017-10-03 20:54:56,717 INFO [main] (SpringCacheWithEhCacheTest.java:36) - HTC ->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@68c06cac
2017-10-03 20:54:56,717 INFO [main] (ProductServiceImpl.java:40) - <!----------Entering getByName()--------------------->
2017-10-03 20:54:56,717 INFO [main] (SpringCacheWithEhCacheTest.java:37) - HTC ->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@68c06cac
2017-10-03 20:54:56,718 INFO [main] (ProductServiceImpl.java:40) - <!----------Entering getByName()--------------------->
2017-10-03 20:54:56,718 INFO [main] (SpringCacheWithEhCacheTest.java:38) - HTC ->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@68c06cac
2017-10-03 20:54:56,724 INFO [main] (ProductServiceImpl.java:52) - <!----------Entering updateProduct()--------------------->
2017-10-03 20:54:56,734 INFO [main] (SpringCacheWithEhCacheTest.java:43) - IPhone ->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@1a8f392
2017-10-03 20:54:56,735 INFO [main] (SpringCacheWithEhCacheTest.java:44) - IPhone ->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@1a8f392
2017-10-03 20:54:56,735 INFO [main] (SpringCacheWithEhCacheTest.java:45) - IPhone ->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@1a8f392
2017-10-03 20:54:56,736 INFO [main] (SpringCacheWithEhCacheTest.java:47) - Refreshing all products
2017-10-03 20:54:56,741 INFO [main] (ProductServiceImpl.java:40) - <!----------Entering getByName()--------------------->
2017-10-03 20:54:56,741 INFO [main] (SpringCacheWithEhCacheTest.java:50) - IPhone [after refresh]->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@1a8f392
2017-10-03 20:54:56,742 INFO [main] (SpringCacheWithEhCacheTest.java:51) - IPhone [after refresh]->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@1a8f392
2017-10-03 20:54:56,742 INFO [main] (SpringCacheWithEhCacheTest.java:52) - IPhone [after refresh]->com.xgj.cache.springCacheAnno.CompleteDemoWithEhCache.domain.Product@1a8f392
2017-10-03 20:54:56,742 INFO [main] (AbstractApplicationContext.java:984) - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7bd1a567: startup date [Tue Oct 03 20:54:55 BOT 2017]; root of context hierarchy
2017-10-03 20:54:56,744 INFO [main] (EhCacheManagerFactoryBean.java:187) - Shutting down EhCache CacheManager

查看ProductServiceImpl中的 getName方法中的@Cacheable注解可知

@Cacheable(cacheNames = "products", key = "#name", condition = "#name != 'HTC'", unless = "#result==null")

HTC不缓存, 结果为空不缓存。

查看输出,第一次查询 IPhone Samsung HTC ,分别从慢速设备中加载, 当第二次第三次查询IPhone Samsung ,可以看到 并没有输出

logger.info("<!----------Entering getByName()--------------------->");

可知,其从缓存中加载。

因为不缓存HTC,所以每次查询HTC都从会执行方法,从慢速设备中查询。

当调用service.updateProduct(product); 我们使用的@CachePut注解更新缓存, 然后service.getByName(“IPhone”),缓存没有被清空,所以依然是从缓存中获取。

随后,service.refreshAllProducts(); 将缓存全部清掉,再此查询service.getByName(“IPhone”),然后再此查询可以看到输出了<!----------Entering getByName()--------------------->,紧接着的第二次第三次,是从缓存中获取的数据.


源码

代码已托管到Github—> https://github.com/yangshangwei/SpringMaster