zoukankan      html  css  js  c++  java
  • Spring 3.1新特性之三:Spring对声明式缓存的支持

    一、概述:

    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 缓存

    具体的实体类代码如下:

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

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

    具体代码如下:

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

    定义图书服务接口

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

    图书服务接口实现类

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

    运行执行:

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

    控制台输出信息:

    [html] view plain copy
     
    1. 从数据库中查询:1  
    2. 从缓存查询到:1  

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

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

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

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

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

    领域对象:

     

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

    图书服务接口

    [java] view plain copy
     
    1. package com.my.data.cache.service;  
    2.   
    3. import java.util.List;  
    4.   
    5. import com.my.data.cache.domain.Book;  
    6.   
    7. public interface BookService {  
    8.   
    9.     public Book findById(Integer bid);  
    10.   
    11.     public List<Book> findBookAll();  
    12.   
    13.     public void insertBook(Book book);  
    14.   
    15.     public Book findByTitle(String title);  
    16.       
    17.     public int countBook();  
    18.       
    19.     public void modifyBook(Book book);  
    20.       
    21.      public Book findByIsbn(String isbn);  
    22.       
    23.       
    24. }  

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

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

    BookRepository接口

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

    Controller 代码:

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


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

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

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

    [html] view plain copy
     
    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()方法,则直接从缓存中获取数据,不在查询数据库

    [java] view plain copy
     
    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 等。下面我举一个简单的例子说明如何做。

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

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

    下面是MyCache的定义:

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

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

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

    [html] view plain copy
     
    1. <cache:annotation-driven />   
    2.   
    3.  <bean id="cacheManager" class="com.rollenholt.spring.cache.MyCacheManager">  
    4.      <property name="caches">   
    5.        <set>   
    6.          <bean   
    7.            class="com.rollenholt.spring.cache.MyCache"  
    8.            p:name="accountCache" />   
    9.        </set>   
    10.      </property>   
    11.    </bean>   
    测试:
    [html] view plain copy
     
    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 都会失效,我们来演示一下。


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

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

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

    @CacheEvict 的可靠性问题

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

    [java] view plain copy
     
    1. // 清空 accountCache 缓存  
    2.  @CacheEvict(value="accountCache",allEntries=true)  
    3.  public void reload() {   
    4.    throw new RuntimeException();   
    5.  }  
    6.    
    测试:
    [java] view plain copy
     
    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,这样,在方法执行前我们的缓存就被清空了。可以确保缓存被清空。

  • 相关阅读:
    wpf 不规则窗体
    wpf treeview使用expanded事件出错的问题
    获取文件图标
    C#操作快捷方式总结
    mysql 更改存储引擎,更改自增列计数值,更改默认字符集
    zend framework集成smarty
    文本文件数据导入mysql注意事项
    MYSQL 外键
    uchome 日志发布函数blog_post()
    mysql order by null
  • 原文地址:https://www.cnblogs.com/duanxz/p/4893219.html
Copyright © 2011-2022 走看看