概述
Spring Cache抽象-之缓存注解这篇博文中我们介绍了SpringCache抽象注解的使用方式
既然这是一个抽象,我们需要一个具体的缓存存储实现。比价流行的有:基于JDK java.util.concurrent.ConcurrentMap的缓存,EhCache,Gemfire缓存,Caffeine,Guava缓存和兼容JSR-107的缓存等等。这里我们使用Ehcache来实现这个缓存。
同时,我们使用EhCacheManagerFactoryBean的configLocation属性指定Ehcache的设置。如果未明确指定,则默认为ehcache.xml。
工程结构
以及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