一、概述:

Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

Spring Cache特点:

Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的缓存方式:例如 EHCache 等集成。

特点总结如下:

  • 通过少量的配置 annotation 注释即可使得既有代码支持缓存
  • 支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存
  • 支持 Spring Express Language,能使用对象的任何属性或者方法来定义缓存的 key 和 condition
  • 支持 AspectJ,并通过其实现任何方法的缓存支持
  • 支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性
本文将通过实际的例子来解析Spring Cache和自定义缓存以及第三方缓存组件的区别,最后将会详细的介绍Spring Cache相关注解。
 

二、Spring对Cache的支持有两种方法

1.基于注解的配置
2.基础XML配置
 

2.1、通过注解去使用到Cache

@Cacheable:使用这个注解的方法在执行后会缓存其返回结果。
@CacheEvict:使用这个注解的方法在其执行前或执行后移除Spring Cache中的某些元素。
@CachePut:与@Cacheable不同,它虽然也可以声明一个方法支持缓存,但它执行方法前是不会去检查缓存中是否存在之前执行过的结果,而是每次都执行该方法,并将执行结果放入指定缓存中。
---------------------------------------@Cacheable----------------------------
@Cacheable可以注解在方法上也可以注解在类上。当标记在方法上面时,表示该方法是可以缓存的;如果标记在类上面,则表示该类的所有方法都是可以缓存的。对于一个支持缓存的方法,在执行后,会将其返回结果进行缓存起来,以保证下次同样参数来执行该方法的时候可以从缓存中返回结果,而不需要再次执行此方法。Spring缓存方法的返回值是以键值对进行保存的,值就是返回结果,键的话Spring支持两种策略,一种是默认策略,一种是自定义策略。
注意:一个支持缓存的方法,在对象内部被调用是不会触发缓存功能的。
@Cacheable可以指定三个属性:value、key、condition。
----------------------value:指定Cache的名称
value值是必须指定的,其表示该方法缓存的返回结果是被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以使多个Cache(数组);
key属性是用来指定Spring缓存方法返回结果时所对应的key值的。该属性支持EL表达式。当我们没有指定key时,Spring会使用默认策略生成key。·
-----------key的自定义策略:
自定义策略是表示我们通过EL表达式来指定我们的key。这里EL表达式可以使用方法参数以及他们对应的属性。使用方法参数时,我们可以使用“#参数名”。
-------1.methodName  当前方法名    #root.methodName
-------2.method       当前方法  #root.method.name
-------3.target   当前被动用对象 #root.target
-------4.targetClass      当前被调用对象Class#root.targetCla
-------5.args    当前方法参数组成的数组 #root.args[0]
-------6.caches    当前被调用方法所使用的Cache #root.caches[0],name
当我们要使用root作为key时,可以不用写root直接@Cache(key="caches[1].name")。因为他默认是使用#root的
-----------------condition:指定发生条件
有时候可能不需要缓存一个方法的所有结果。通过condition可以设置一个条件,其条件值是使用SpringEL表达式来指定的。当为true时进行缓存处理;为false时不进行缓存处理,即每次调用该方法都会执行。
-----------------------------------------------------@CachePut------------------------------------------
与@Cacheable不同,它虽然也可以声明一个方法支持缓存,但它执行方法前是不会去检查缓存中是否存在之前执行过的结果,而是每次都执行该方法,并将执行结果放入指定缓存中。
-----------------------------------------------------@CacheEvict----------------------------------------
@CacheEvict标注在需要清楚缓存元素的方法和类上。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。value表示清除缓存作用在哪个Cache上;key是表示需要清除的是哪个key。
--------------allEntries是表示是否需要清除缓存中所有的元素。
--------------beforeInvocatio
清除操作默认是在方法成功执行之后触发的。使用beforeInvocation可以改变触发清除操作的时间,当我们设置为true时,Spring会在调用该方法之前进行缓存的清除。
 
 
1.在不使用第三方缓存组件的情况下,自定义缓存实现
场景:

通过图书ID查询图书信息的方法做缓存,以图书ID为 key,图书名称为 value,当以相同的图书ID查询图书信息的时候,直接从缓存中返回结果,则不经过数据库查询,反之则查询数据库更新缓存。当然还支持 reload 缓存

具体的实体类代码如下:

  1. package com.my.data.cache.dao;
  2. import java.io.Serializable;
  3. /**
  4. * 图书领域对象
  5. * @author wbw
  6. *
  7. */
  8. public class Book implements Serializable {
  9. /**
  10. * 序列化版本号
  11. */
  12. private static final long serialVersionUID = -2710076757833997658L;
  13. /**
  14. * 图书ID
  15. */
  16. private String bookId;
  17. /**
  18. * 图书名称
  19. */
  20. private String bookName;
  21. /**
  22. * @return the 图书ID
  23. */
  24. public String getBookId() {
  25. return bookId;
  26. }
  27. /**
  28. * @param 图书ID the bookId to set
  29. */
  30. public void setBookId(String bookId) {
  31. this.bookId = bookId;
  32. }
  33. /**
  34. * @return the 图书名称
  35. */
  36. public String getBookName() {
  37. return bookName;
  38. }
  39. /**
  40. * @param 图书名称 the bookName to set
  41. */
  42. public void setBookName(String bookName) {
  43. this.bookName = bookName;
  44. }
  45. }

然后定义缓存管理器,主要用于处理新增缓存对象、删除缓存对象、更新缓存对象、查询缓存对象、清空缓存等操作

具体代码如下:

  1. package com.my.cacheManage;
  2. import java.util.concurrent.ConcurrentHashMap;
  3. /**
  4. * 自定义缓存控制器 、回调接口、监听器
  5. * 缓存代码和业务逻辑耦合度高
  6. * 不灵活
  7. * 缓存存储写的很死,不能灵活的与第三方缓存插件相结合
  8. *
  9. * @author wangbowen
  10. *
  11. * @param <T>
  12. */
  13. public class CacheManagerHandler<T> {
  14. //ConcurrentHashMap jdk1.5 线程安全 分段锁
  15. private  ConcurrentHashMap<String,T> cache = new ConcurrentHashMap<String,T>();
  16. /**
  17. * 根据key获取缓存对象
  18. * @param key 缓存对象名
  19. * @return  缓存对象
  20. */
  21. public T getValue(Object key){
  22. return cache.get(key);
  23. }
  24. /**
  25. * 新增或更新
  26. * @param key
  27. * @param value
  28. */
  29. public void  put(String key,T value){
  30. cache.put(key, value);
  31. }
  32. /**
  33. * 新增缓存对象
  34. * @param key 缓存对象名称
  35. * @param value 缓存对象
  36. * @param time 缓存时间(单位:毫秒) -1表示时间无限制
  37. * @param callBack
  38. */
  39. public void  put(String key,T value,long time,CacheCallBack callBack){
  40. cache.put(key, value);
  41. if(time!=-1){
  42. //启动监听
  43. new CacheListener(key,time,callBack);
  44. }
  45. }
  46. /**
  47. * 根据key删除缓存中的一条记录
  48. * @param key
  49. */
  50. public void evictCache(String key){
  51. if(cache.containsKey(key)){
  52. cache.remove(key);
  53. }
  54. }
  55. /**
  56. * 获取缓存大小
  57. * @return
  58. */
  59. public int getCacheSize(){
  60. return  cache.size();
  61. }
  62. /**
  63. * 清空缓存
  64. */
  65. public void evictCache(){
  66. cache.clear();
  67. }
  68. }

定义图书服务接口

  1. package com.my.data.cache.service;
  2. import com.my.data.cache.domain.Book;
  3. /**
  4. * 图书服务接口
  5. * @author wbw
  6. *
  7. */
  8. public interface BookService {
  9. /**
  10. * 根据图形ID查询图书
  11. * @param bookId 图书ID
  12. * @return 图书信息
  13. */
  14. public Book findBookById(String bookId);
  15. }

图书服务接口实现类

  1. package com.my.service.impl;
  2. import com.my.cacheManage.CacheManagerHandler;
  3. import com.my.domain.Account;
  4. import com.my.service.MyAccountService;
  5. /**
  6. * 实现类
  7. * @author wangbowen
  8. *
  9. */
  10. public class BookServiceImpl implements BookService {
  11. /**
  12. * 缓存控制器
  13. */
  14. private CacheManagerHandler<Book> myCacheManager;
  15. /**
  16. * 初始化
  17. */
  18. public BookServiceImpl(){
  19. myCacheManager = new CacheManagerHandler<Book>();
  20. }
  21. @Override
  22. public Book getBookByID(String id) {
  23. Account result = null;
  24. if(id!=null){
  25. //先查询缓存中是否有,直接返回
  26. result = myCacheManager.getValue(id);
  27. if(result!=null){
  28. System.out.println("从缓存查询到:"+id);
  29. return result;
  30. }else{
  31. result = getFormDB(id);
  32. if(result!=null){//将数据查询出来的结果更新到缓存集合中
  33. myCacheManager.put(id, result);
  34. return result;
  35. }else{
  36. System.out.println("数据库为查询到"+id+"账户信息");
  37. }
  38. }
  39. }
  40. return null;
  41. }
  42. /**
  43. * 从数据库中查询
  44. * @param name
  45. * @return
  46. */
  47. private Book getFormDB(String id) {
  48. System.out.println("从数据库中查询:"+id);
  49. return new Book(id);
  50. }
  51. }

运行执行:

  1. package com.my.cache.test;
  2. import com.my.service.MyAccountService;
  3. import com.my.service.impl.MyAccountServiceImpl;
  4. /**
  5. * 测试
  6. * @author wbw
  7. *
  8. */
  9. public class MyCacheTest {
  10. public static void main(String[] args) {
  11. BookService s = new BookServiceImpl();
  12. s.getBookByid("1");// 第一次查询,应该是数据库查询
  13. s.getBookByid("1");// 第二次查询,应该直接从缓存返回
  14. }
  15. }

控制台输出信息:

  1. 从数据库中查询:1
  2. 从缓存查询到:1

虽然自定义缓存能实现缓存的基本功能,但是这种自定义缓存存在很大的缺点:

1.缓存代码和实际业务耦合度高,不便于后期修改。

2.不灵活,需要按照某种缓存规则进行缓存,不能根据不同的条件进行缓存

3.兼容性太差,不能与第三方缓存组件兼容。

Spring Cache基于注解的实现方式:

领域对象:

 

  1. package com.my.data.cache.domain;
  2. import java.io.Serializable;
  3. import javax.persistence.Column;
  4. import javax.persistence.Entity;
  5. import javax.persistence.GeneratedValue;
  6. import javax.persistence.GenerationType;
  7. import javax.persistence.Id;
  8. import javax.persistence.Table;
  9. @Entity
  10. @Table(name="book")
  11. public class Book implements Serializable {
  12. /**
  13. *
  14. */
  15. private static final long serialVersionUID = -6283522837937163003L;
  16. @Id
  17. @GeneratedValue(strategy = GenerationType.AUTO)
  18. @Column(name = "id", nullable = true)
  19. private Integer id;
  20. private String isbn;
  21. private String title;
  22. public Book(String isbn, String title) {
  23. this.isbn = isbn;
  24. this.title = title;
  25. }
  26. public Book() {
  27. }
  28. public Book(int id, String isbn, String title) {
  29. super();
  30. this.id = id;
  31. this.isbn = isbn;
  32. this.title = title;
  33. }
  34. public int getId() {
  35. return id;
  36. }
  37. public void setId(int id) {
  38. this.id = id;
  39. }
  40. public String getIsbn() {
  41. return isbn;
  42. }
  43. public void setIsbn(String isbn) {
  44. this.isbn = isbn;
  45. }
  46. public String getTitle() {
  47. return title;
  48. }
  49. public void setTitle(String title) {
  50. this.title = title;
  51. }
  52. @Override
  53. public String toString() {
  54. return "Book{" + "isbn='" + isbn + '\'' + ", title='" + title + '\'' + '}';
  55. }
  56. }

图书服务接口

  1. package com.my.data.cache.service;
  2. import java.util.List;
  3. import com.my.data.cache.domain.Book;
  4. public interface BookService {
  5. public Book findById(Integer bid);
  6. public List<Book> findBookAll();
  7. public void insertBook(Book book);
  8. public Book findByTitle(String title);
  9. public int countBook();
  10. public void modifyBook(Book book);
  11. public Book findByIsbn(String isbn);
  12. }

图书服务接口,这里 ORM框架使用的是Spring Data 通过基于注解的查询方式能更简便的与数据交互

  1. package com.my.data.cache.service.impl;
  2. import java.util.List;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cache.annotation.CacheEvict;
  7. import org.springframework.cache.annotation.Cacheable;
  8. import org.springframework.stereotype.Service;
  9. import org.springframework.transaction.annotation.Transactional;
  10. import com.my.data.cache.annotation.LogAnnotation;
  11. import com.my.data.cache.domain.Book;
  12. import com.my.data.cache.exception.MyException;
  13. import com.my.data.cache.repository.BookRepository;
  14. import com.my.data.cache.service.BookService;
  15. @Service
  16. @Transactional
  17. public class BookServiceImpl  implements BookService{
  18. private static final Logger log = LoggerFactory.getLogger(BookServiceImpl.class);
  19. @Autowired
  20. private BookRepository bookRepository;
  21. //将缓存保存进andCache,并使用参数中的bid加上一个字符串(这里使用方法名称)作为缓存的key
  22. @Cacheable(value="andCache",key="#bid+'findById'")
  23. @LogAnnotation(value="通过Id查询Book")
  24. public Book findById(Integer bid) {
  25. this.simulateSlowService();
  26. return bookRepository.findById(bid);
  27. }
  28. @Override
  29. public List<Book> findBookAll() {
  30. return bookRepository.findBookAll();
  31. }
  32. //将缓存保存进andCache,并当参数title的长度小于32时才保存进缓存,默认使用参数值及类型作为缓存的key
  33. @Cacheable(value="andCache",condition="#title.length >5")
  34. public Book findByTitle(String title){
  35. return null;
  36. }
  37. /**
  38. * 新增
  39. * @param book
  40. * @return
  41. */
  42. public void insertBook(Book book){
  43. bookRepository.save(book);
  44. }
  45. @Override
  46. public int countBook() {
  47. return bookRepository.countBook();
  48. }
  49. //清除掉指定key中的缓存
  50. @CacheEvict(value="andCache",key="#book.id + 'findById'")
  51. public void modifyBook(Book book) {
  52. log.info("清除指定缓存"+book.getId()+"findById");
  53. bookRepository.save(book);
  54. }
  55. //清除掉全部缓存
  56. @CacheEvict(value="andCache",allEntries=true,beforeInvocation=true)
  57. public void ReservedBook() {
  58. log.info("清除全部的缓存");
  59. }
  60. // Don't do this at home
  61. private void simulateSlowService() {
  62. try {
  63. long time = 5000L;
  64. Thread.sleep(time);
  65. } catch (InterruptedException e) {
  66. throw new MyException("程序出错", e);
  67. }
  68. }
  69. @Override
  70. public Book findByIsbn(String isbn) {
  71. return bookRepository.findByIsbn(isbn);
  72. }
  73. }

BookRepository接口

  1. package com.my.data.cache.repository;
  2. import java.util.List;
  3. import org.springframework.data.jpa.repository.Query;
  4. import org.springframework.data.repository.CrudRepository;
  5. import com.my.data.cache.dao.CommonRepository;
  6. import com.my.data.cache.domain.Book;
  7. /**
  8. * 接口
  9. * @author wbw
  10. *
  11. */
  12. public interface BookRepository extends CrudRepository<Book, Integer> {
  13. @Query("select b from Book b  where 1=1")
  14. public   List<Book> findBookAll();
  15. /**
  16. * 根据isbn查询
  17. * @param name
  18. * @return
  19. */
  20. @Query("select  b from Book b where b.id =?1")
  21. public  Book findById(Integer bid);
  22. /**
  23. * 统计size
  24. * @return
  25. */
  26. @Query("select count(*) from Book where 1=1 ")
  27. public int countBook();
  28. /**
  29. * 根据命名规范查询 findBy+属性
  30. * @param isbn
  31. * @return
  32. */
  33. public Book findByIsbn(String isbn);
  34. }

Controller 代码:

  1. package com.my.data.cache.controller;
  2. import java.util.List;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RequestMethod;
  9. import org.springframework.web.bind.annotation.ResponseBody;
  10. import org.springframework.web.bind.annotation.RestController;
  11. import com.my.data.cache.domain.Book;
  12. import com.my.data.cache.service.BookService;
  13. @RestController
  14. @RequestMapping("/book")
  15. public class BookController {
  16. private static final Logger log = LoggerFactory.getLogger(BookController.class);
  17. @Autowired
  18. private BookService bookService;
  19. @RequestMapping("/{id}")
  20. public @ResponseBody Book index(@PathVariable("id") Integer id){
  21. Book b = bookService.findById(id);
  22. log.info(b.getIsbn()+"------>"+b.getTitle());
  23. return b;
  24. }
  25. @RequestMapping(value = "/list", method = RequestMethod.GET)
  26. public  @ResponseBody List<Book> list(){
  27. List<Book> b = bookService.findBookAll();
  28. return  b;
  29. }
  30. @RequestMapping(value = "/add")
  31. public String  insertBook(){
  32. Book b = new Book();
  33. b.setId(4);
  34. b.setIsbn("1111");
  35. b.setTitle("相信自己");
  36. bookService.insertBook(b);
  37. return "success";
  38. }
  39. /**
  40. * 更新
  41. * @return
  42. */
  43. @RequestMapping(value = "/update")
  44. public String update(){
  45. Book b = new Book();
  46. b.setId(1);
  47. b.setIsbn("1");
  48. b.setTitle("爱的力量");
  49. bookService.modifyBook(b);
  50. return "success";
  51. }
  52. }

测试-------这里我们采用Spring Boot 启动服务的方式,

  1. package com.my.data.cache;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.boot.CommandLineRunner;
  4. import org.springframework.boot.SpringApplication;
  5. import org.springframework.boot.autoconfigure.SpringBootApplication;
  6. import org.springframework.cache.annotation.EnableCaching;
  7. import com.my.data.cache.domain.Book;
  8. import com.my.data.cache.service.BookService;
  9. /**
  10. *
  11. * 启动器
  12. *
  13. */
  14. @SpringBootApplication
  15. @EnableCaching//扫描cahce注解
  16. public class Application1  implements CommandLineRunner{
  17. @Autowired
  18. private BookService bookService;
  19. @Override
  20. public void run(String... args) throws Exception {
  21. Book b1 = bookService.findByIsbn("1");
  22. Book b2 = bookService.findByIsbn("2");
  23. Book b3 = bookService.findById(3);
  24. System.out.println(b1);
  25. System.out.println(b2);
  26. System.out.println(b3);
  27. }
  28. public static void main(String[] args) {
  29. SpringApplication.run(Application1.class,args);
  30. }
  31. }

第一次访问indexI()方法,可以从下面的控制台信息看出:发出了sql语句从数据库查询数据,然后将查询的数据缓存,下次有相同条件访问相同的请求则直接从缓存中取数据

  1. Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_ where book0_.isbn=?
  2. Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_ where book0_.isbn=?
  3. Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_ where book0_.id=?
  4. Book{isbn='1', title='爱的力量'}
  5. 2016-03-10 11:22:40.107  INFO 8132 --- [  restartedMain] com.my.data.cache.Application1           : Started Application1 in 42.661 seconds (JVM running for 46.34)

第二次访问indexI()方法,则直接从缓存中获取数据,不在查询数据库

  1. Book{isbn='1', title='爱的力量'}
  2. 2016-03-10 11:27:43.936  INFO 6436 --- [  restartedMain] com.my.data.cache.Application1           : Started Application1 in 19.363 seconds (JVM running for 20.063)

从上面Spring Cahce的示例代码可以看出,Spring Cache通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果,并没有太多的缓存业务逻辑代码。
Spring Cache 部分注解介绍:

  • @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
  • @CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
  • @CacheEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空,清除全部的缓存@CacheEvict(value="缓存名字",allEntries=true,beforeInvocation=true)
Spring Cache实现原理:
通过 Spring AOP动态代理技术
Spring Cache的扩展性:
在现实的业务中总是很复杂,当你的用户量上去或者性能跟不上,总需要进行扩展,这个时候你或许对其提供的内存缓存不满意了,因为其不支持高可用性,也不具备持久化数据能力,这个时候,你就需要自定义你的缓存方案了。
此部分引用别人的代码示例:

首先,我们需要提供一个 CacheManager 接口的实现,这个接口告诉 spring 有哪些 cache 实例,spring 会根据 cache 的名字查找 cache 的实例。另外还需要自己实现 Cache 接口,Cache 接口负责实际的缓存逻辑,例如增加键值对、存储、查询和清空等。

利用 Cache 接口,我们可以对接任何第三方的缓存系统,例如 EHCacheOSCache,甚至一些内存数据库例如 memcache 或者redis 等。下面我举一个简单的例子说明如何做。

  1. import java.util.Collection;
  2. import org.springframework.cache.support.AbstractCacheManager;
  3. public class MyCacheManager extends AbstractCacheManager {
  4. private Collection<? extends MyCache> caches;
  5. /**
  6. * Specify the collection of Cache instances to use for this CacheManager.
  7. */
  8. public void setCaches(Collection<? extends MyCache> caches) {
  9. this.caches = caches;
  10. }
  11. @Override
  12. protected Collection<? extends MyCache> loadCaches() {
  13. return this.caches;
  14. }
  15. }

上面的自定义的 CacheManager 实际继承了 spring 内置的 AbstractCacheManager,实际上仅仅管理 MyCache 类的实例。

下面是MyCache的定义:

  1. import java.util.HashMap;
  2. import java.util.Map;
  3. import org.springframework.cache.Cache;
  4. import org.springframework.cache.support.SimpleValueWrapper;
  5. public class MyCache implements Cache {
  6. private String name;
  7. private Map<String,Account> store = new HashMap<String,Account>();;
  8. public MyCache() {
  9. }
  10. public MyCache(String name) {
  11. this.name = name;
  12. }
  13. @Override
  14. public String getName() {
  15. return name;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. @Override
  21. public Object getNativeCache() {
  22. return store;
  23. }
  24. @Override
  25. public ValueWrapper get(Object key) {
  26. ValueWrapper result = null;
  27. Account thevalue = store.get(key);
  28. if(thevalue!=null) {
  29. thevalue.setPassword("from mycache:"+name);
  30. result = new SimpleValueWrapper(thevalue);
  31. }
  32. return result;
  33. }
  34. @Override
  35. public void put(Object key, Object value) {
  36. Account thevalue = (Account)value;
  37. store.put((String)key, thevalue);
  38. }
  39. @Override
  40. public void evict(Object key) {
  41. }
  42. @Override
  43. public void clear() {
  44. }
  45. }

上面的自定义缓存只实现了很简单的逻辑,主要看 get 和 put 方法,其中的 get 方法留了一个后门,即所有的从缓存查询返回的对象都将其 password 字段设置为一个特殊的值,这样我们等下就能演示“我们的缓存确实在起作用!”了。

这还不够,spring 还不知道我们写了这些东西,需要通过 spring*.xml 配置文件告诉它

  1. <cache:annotation-driven />
  2. <bean id="cacheManager" class="com.rollenholt.spring.cache.MyCacheManager">
  3. <property name="caches">
  4. <set>
  5. <bean
  6. class="com.rollenholt.spring.cache.MyCache"
  7. p:name="accountCache" />
  8. </set>
  9. </property>
  10. </bean>

测试:

  1. Account account = accountService.getAccountByName("someone");
  2. logger.info("passwd={}", account.getPassword());
  3. account = accountService.getAccountByName("someone");
  4. logger.info("passwd={}", account.getPassword());

Spring Cache的注意和限制

基于 proxy 的 spring aop 带来的内部调用问题

上面介绍过 spring cache 的原理,即它是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题.

如果对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效,我们来演示一下。

  1. public Account getAccountByName2(String accountName) {
  2. return this.getAccountByName(accountName);
  3. }
  4. @Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache
  5. public Account getAccountByName(String accountName) {
  6. // 方法内部实现不考虑缓存逻辑,直接实现业务
  7. return getFromDB(accountName);
  8. }

上面我们定义了一个新的方法 getAccountByName2,其自身调用了 getAccountByName 方法,这个时候,发生的是内部调用(this),所以没有走 proxy,导致 spring cache 失效

要避免这个问题,就是要避免对缓存方法的内部调用,或者避免使用基于 proxy 的 AOP 模式,可以使用基于 aspectJ 的 AOP 模式来解决这个问题。

@CacheEvict 的可靠性问题

我们看到,@CacheEvict 注释有一个属性 beforeInvocation,缺省为 false,即缺省情况下,都是在实际的方法执行完成后,才对缓存进行清空操作。期间如果执行方法出现异常,则会导致缓存清空不被执行。我们演示一下

  1. // 清空 accountCache 缓存
  2. @CacheEvict(value="accountCache",allEntries=true)
  3. public void reload() {
  4. throw new RuntimeException();
  5. }

测试:

  1. accountService.getAccountByName("someone");
  2. accountService.getAccountByName("someone");
  3. try {
  4. accountService.reload();
  5. } catch (Exception e) {
  6. //...
  7. }
  8. accountService.getAccountByName("someone");

注意上面的代码,我们在 reload 的时候抛出了运行期异常,这会导致清空缓存失败。上面的测试代码先查询了两次,然后 reload,然后再查询一次,结果应该是只有第一次查询走了数据库,其他两次查询都从缓存,第三次也走缓存因为 reload 失败了。

那么我们如何避免这个问题呢?我们可以用 @CacheEvict 注释提供的 beforeInvocation 属性,将其设置为 true,这样,在方法执行前我们的缓存就被清空了。可以确保缓存被清空。

Spring 3.1新特性之三:Spring对声明式缓存的支持的更多相关文章

  1. Spring 3.1新特性之三:Spring支持Servlet 3.0(待补充)

    高效并发是JDK 1.6的一个重要主题,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋(Adaptive Spinning).锁削除(Lock Elimin ...

  2. 【2.0新特性】Spring Boot 2.0新特性

    以Java 8 为基准 Spring Boot 2.0 要求Java 版本必须8以上, Java 6 和 7 不再支持. 内嵌容器包结构调整 为了支持reactive使用场景,内嵌的容器包结构被重构了 ...

  3. Spring Cloud Greenwich 新特性和F升级分享

    2019.01.23 期待已久的Spring Cloud Greenwich 发布了release版本,作为我们团队也第一时间把RC版本替换为release,以下为总结,希望对你使用Spring Cl ...

  4. 我这样回答了Spring 5的新特性,面试官对我刮目相看

    最近,有一个小伙伴拿到了自己满意的Offer,和他交谈的过程中得知他面试官问他关于Spring的问题比较多,其中最让面试官满意的就是自己回答关于Spring 5的知识点回答的不错. Spring5于2 ...

  5. 【Finchley】【新特性】Spring Cloud Finchley 新特性

    Finchley 正式版的发布貌似经历了相当长的时间,这次的重大发布主要带来了以下 4 项重大更新. 重大更新 1.新增 Spring Cloud Gateway 组件 Spring Cloud Ga ...

  6. Spring 3.1新特性之一:spring注解之@profile

    前言 由于在项目中使用Maven打包部署的时候,经常由于配置参数过多(比如Nginx服务器的信息.ZooKeeper的信息.数据库连接.Redis服务器地址等),导致实际现网的配置参数与测试服务器参数 ...

  7. Spring官方发布新成员:Spring GraphQL

    近日,在GraphQL Java诞生6周年的时候,Spring社区通过博客宣布正式创建全新项目:Spring GraphQL,同时还发布了这个新项目的里程碑1.0版本. 博客原文:https://sp ...

  8. Spring 事务配置管理,简单易懂,详细 [声明式]

    Spring 事务配置说明 Spring 如果没有特殊说明,一般指是跟数据存储有关的数据操作事务操作:对于数据持久操作的事务配置,一般有三个对象,数据源,事务管理器,以及事务代理机制: Spring ...

  9. Oracle 12C 新特性之 db默认字符集AL32UTF8、PDB支持不同字符集

    一. db默认字符集AL32UTF8Specify the database character set when you create the database. Starting from Ora ...

随机推荐

  1. java 常用设计模式(转载)

    http://www.cnblogs.com/hnrainll/archive/2011/12/29/2305582.html 设计模式:一个程序员对设计模式的理解:“不懂”为什么要把很简单的东西搞得 ...

  2. 认识XmlReader

    认识XmlReader   摘要 XmlReader类是组成.NET的关键技术之一,极大地方便了开发人员对Xml的操作.通过本文您将对XmlReader有一个很好的认识,并将其应用到实际开发中. 目录 ...

  3. FPGA学习记录 - Quartus II 未使用管脚设置为三态输入

    未使用管脚设置为三态输入 Assignments  -> Device 或双击器件

  4. Unix环境高级编程—进程关系

    终端登录 网络登录 进程组 getpgrp(void) setpgid(pid_t pid, pid_) 会话: 是一个或多个进程组的集合,通常由shell的管道将几个进程编成一组. setsid(v ...

  5. native2ascii转码工具的使用

    native2ascii转码工具是JDK自带的一种,方便我们将非unicode的编码文件转为unicode格式的文件,位置一般是位于JAVA_HOME/bin目录下. Why? 在做Java开发的时候 ...

  6. 九度OJ 1008:最短路径问题 (最短路)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:8064 解决:2685 题目描述: 给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费 ...

  7. 【足迹C++primer】35、特定容器算法

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/cutter_point/article/details/33732681 特定容器算法 lst.me ...

  8. Linux 设备和模块的分类

    概念:在Linux系统中,所有设备都被映射成 [设备文件] 来处理,设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作. 一.设备类型 整理自:(相当不错,建议有时间看下原文) <第一章 ...

  9. 3D焦点图插件

    在线演示 本地下载

  10. 20145239杜文超 《Java程序设计》第1周学习总结

    20145239<Java程序设计>第1周学习总结 教材学习内容总结 第一周. 通过教材简单的了解了java的历史.因为之前看过视频,所以有一个大致明了的认识. 识记了Java三大平台:J ...