在 Spring 3.1 中使用 @Cacheable 实现缓存

在软件开发中使用缓存已经有一个非常久的历史了。缓存是一种很好的设计思想,一旦你用了他,你将会发现他确实很有用。Spring3.1版本的核心对缓 存做了实现。在Java推出Annotation特性之前,实现缓存的一个难点在于它与业务逻辑代码的耦合性太强。
       然而,Spring3.1中使用@Cacheable 和@CacheEvict实现缓存在某种程度上解决了这个问题,基本思想是在方法加上@Cacheable注解,这个方法的返回值
将具有缓存特性。
       @Cacheable注解可以用在方法或者类级别。当他应用于方法级别的时候,就是如上所说的缓存返回值了。当应用在类级别的时候,这个类的所有方法的
返回值都将被缓存。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
@Cacheable (value =  "employee" )
public class EmployeeDAO {
 
   public Person findEmployee(String firstName, String surname,  int age) {
 
     return new Person(firstName, surname, age);
   }
 
   public Person findAnotherEmployee(String firstName, String surname,  int age) {
 
     return new Person(firstName, surname, age);
   }
}

       @Cacheable注解有三个参数,value是必须的,还有key和condition。第一个参数,也就是value指明了缓存将被存到什么地方

?
1
2
3
4
5
@Cacheable (value =  "employee" )
  public Person findEmployee(String firstName, String surname,  int age) {
 
    return new Person(firstName, surname, age);
  }

       任何存储在缓存中的数据为了高速访问都需要一个key。Spring默认使用被@Cacheable注解的方法的签名来作为key,当然你可以重写key,自定义key可以使用SpEL表达式。

?
1
2
3
4
<span style= "font-size:14px;" > @Cacheable (value =  "employee" , key =  "#surname" )</span>    public Person findEmployeeBySurname(String firstName, String surname,  int age) {
 
     return new Person(firstName, surname, age);
   }

       在findEmployeeBySurname()的注解中"#surname"是一个SpEL表达式,他将使用findEmployeeBySurname()方法中的surname参数作为key。


       @Cacheable的最后一个参数是condition(可选),同样的,也是引用一个SpEL表达式。但是这个参数将指明方法的返回结果是否被缓存。

?
1
2
3
4
5
@Cacheable (value =  "employee" , condition =  "#age < 25" )
  public Person findEmployeeByAge(String firstName, String surname,  int age) {
 
    return new Person(firstName, surname, age);
  }
上面的例子中,只有年龄小于25的时候才被缓存。

在快速看完了如何使用缓存后,我们接下来看看缓存带来的效果。
?
1
2
3
4
5
6
7
8
@Test
   public void testCache() {
 
     Person employee1 = instance.findEmployee( "John" "Smith" 33 );
     Person employee2 = instance.findEmployee( "John" "Smith" 33 );
 
     assertEquals(employee1, employee2);
   }
       上面的例子很简单,第一次调用findEmployee,findEmployee方法将被执行,Spring将他的返回值一个person对象存入缓存。第二次调用findEmployee的时候findEmployee将不被执行,Spring直接将缓存中的数据作为返回值返回。所以employee1 和employee2引用了同样的对象。

       而下面的例子中,我们将年龄小于25作为缓存条件,就将得到不同的结果。
?
1
2
3
4
5
6
7
8
@Test
  public void testCacheWithAgeAsCondition() {
 
    Person employee1 = instance.findEmployeeByAge( "John" "Smith" 33 );
    Person employee2 = instance.findEmployeeByAge( "John" "Smith" 33 );
 
    assertEquals(employee1, employee2);
  }

       下面的例子我们在findEmployeeBySurname的方法的注解中自定义了key,我们使用了自定义的key生成方式,以确保不同的surname将会指向不同的人。看下面的程序:

?
1
2
3
4
5
6
7
8
@Test
  public void testCacheOnSurnameAsKey() {
 
    Person employee1 = instance.findEmployeeBySurname( "John" "Smith" 22 );
    Person employee2 = instance.findEmployeeBySurname( "Jack" "Smith" 55 );
 
    assertEquals(employee1, employee2);
  }
       我们想找到两个不同的人,但是两个人的surname是相同的,你将发现两次调用返回了相同的结果,这不是Spring的问题,而是我们的cache key的生成方式有问题。所以在我们定义key的时候要小心注意key的生成策略,避免造成这种问题。

        最后总结一下流程,当执行到一个被@Cacheable注解的方法时,Spring首先检查condition条件是否满足,如果不满足,执行方法,返回;如果满足,在value所命名的缓存空间中查找使用key存储的对象,如果找到,将找到的结果返回,如果没有找到执行方法,将方法的返回值以key-对象的方式存入value缓存中,然后方法返回。

上文仅仅是@Cacheable的使用方法,但是我们怎么使用@CacheEvict注解来清除缓存呢?另外,还有一个问题,如何选择一个缓存的实现,并配置Spring的缓存呢?欲知后事如何,且听下回分解。




缓存注解有以下三个:

@Cacheable      @CacheEvict     @CachePut

 

@Cacheable(value=”accountCache”),这个注释的意思是,当调用这个方法的时候,会从一个名叫 accountCache 的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。这里的缓存中的 key 就是参数 userName,value 就是 Account 对象。“accountCache”缓存是在 spring*.xml 中定义的名称。

示例:

 

Java代码   收藏代码
  1. @Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache   
  2. public Account getAccountByName(String userName) {  
  3.      // 方法内部实现不考虑缓存逻辑,直接实现业务  
  4.      System.out.println("real query account."+userName);   
  5.      return getFromDB(userName);   
  6. }   

 

 

@CacheEvict 注释来标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。注意其中一个 @CacheEvict(value=”accountCache”,key=”#account.getName()”),其中的 Key 是用来指定缓存的 key 的,这里因为我们保存的时候用的是 account 对象的 name 字段,所以这里还需要从参数 account 对象中获取 name 的值来作为 key,前面的 # 号代表这是一个 SpEL 表达式,此表达式可以遍历方法的参数对象,具体语法可以参考 Spring 的相关文档手册。

示例:

 

Java代码   收藏代码
  1. @CacheEvict(value="accountCache",key="#account.getName()")// 清空accountCache 缓存    
  2. public void updateAccount(Account account) {  
  3.      updateDB(account);   
  4. }   
  5.     
  6. @CacheEvict(value="accountCache",allEntries=true)// 清空accountCache 缓存  
  7. public void reload() {  
  8.      reloadAll()  
  9. }  
  10.   
  11. @Cacheable(value="accountCache",condition="#userName.length() <=4")// 缓存名叫 accountCache   
  12. public Account getAccountByName(String userName) {   
  13.  // 方法内部实现不考虑缓存逻辑,直接实现业务  
  14.  return getFromDB(userName);   
  15. }  

 

@CachePut 注释,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。

示例:

 

Java代码   收藏代码
  1. @CachePut(value="accountCache",key="#account.getName()")// 更新accountCache 缓存  
  2. public Account updateAccount(Account account) {   
  3.    return updateDB(account);   
  4. }   

 

@Cacheable、@CachePut、@CacheEvict 注释介绍

通过上面的例子,我们可以看到 spring cache 主要使用两个注释标签,即 @Cacheable、@CachePut 和 @CacheEvict,我们总结一下其作用和配置方法。

表 1. @Cacheable 作用和配置方法
@Cacheable 的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@Cacheable 主要的参数
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
@Cacheable(value=”mycache”) 或者 
@Cacheable(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
表 2. @CachePut 作用和配置方法
@CachePut 的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
@CachePut 主要的参数
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
@Cacheable(value=”mycache”) 或者 
@Cacheable(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
表 3. @CacheEvict 作用和配置方法
@CachEvict 的作用主要针对方法配置,能够根据一定的条件对缓存进行清空
@CacheEvict 主要的参数
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
@CachEvict(value=”mycache”) 或者 
@CachEvict(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:
@CachEvict(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存 例如:
@CachEvict(value=”testcache”,
condition=”#userName.length()>2”)
allEntries 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 例如:
@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 例如:
@CachEvict(value=”testcache”,beforeInvocation=true)

<think>首先,用户的问题是:他想了解在Java抽象类中使用@Cacheable注解进行缓存的可行性及方法。问题中提到参考站内引用:[1]描述了Spring缓存抽象的注释,如@Cacheable、@CacheEvict等;[2]则讨论了解决缓存问题的潜在方法,如使用分布式锁。回顾系统级指令:-所有行内数学表达式使用$...$格式,但这不是数学问题,所以可能不需要。-独立公式使用$$...$$,但同样不适用。-LaTeX语法正确,但重点是Java代码。-使用中文回答。-生成相关问题。-回答中引用的段落末尾添加引用标识,如[^1]。用户的问题是Java相关的,所以我应该用中文回答。关键是回答结构和内容:结构清晰,帮助用户逐步解决问题,保证回答真实可靠。我的回答结构:1.**理解问题**:在抽象类中使用@Cacheable的可行性。2.**可行性分析**:-@Cacheable可以在方法上使用。-抽象类可以包含方法,包括抽象和具体方法。-Spring支持在类的方法上使用注解,包括抽象类的具体方法。-但抽象类的抽象方法不能被实例化,所以@Cacheable可能只适用于具体方法。3.**方法**:-示例:在抽象类中定义一个具体方法并添加@Cacheable。-说明如何配置Spring缓存。-潜在问题:如果方法被覆盖等。4.**参考引用**:-引用[1]说明@Cacheable可以触发缓存保存。-引用[2]是关于分布式锁的,可能不直接相关,但可以作为附加提到如果有并发问题。5.**生成相关问题**:在回答末尾生成2-3个相关问题。关键点:-在Spring中,@Cacheable可以在任何Springbean的方法上使用。抽象类可以被Spring管理如果它们是bean。-抽象类本身不能实例化,但可以被具体子类扩展。-@Cacheable可以应用于抽象类中的具体方法。如果方法被覆盖,缓存行为可能取决于方法的实现。-可行性:是可行的,但需注意上下文。示例方法:-在抽象类中定义一个方法,添加@Cacheable。-确保Spring配置了缓存支持(如使用@EnableCaching)。-代码示例使用Java。生成相关问题:1.Java抽象类中使用@Cacheable有哪些常见问题?2.如何缓存抽象方法的结果?3.Spring缓存如何处理继承?回答结构:-介绍。-可行性:是可行的。-方法:详细步骤和示例。-注意事项。-生成相关问题。</think> ### 在Java抽象类中使用@Cacheable注解的可行性及方法 在Java中,Spring框架的缓存抽象(如@Cacheable注解)可以用于提升应用性能,通过缓存方法调用的结果减少数据库访问。以下我将基于Spring的设计原理,逐步解答在**抽象类**中使用@Cacheable的可行性、具体方法、以及注意事项。参考了Spring官方文档和常见实践[^1]。 #### 1. **可行性分析** @Cacheable注解在抽象类中使用是**可行的**,但需满足以下条件: - **具体方法支持**:@Cacheable必须作用于抽象类的**具体方法**(即非抽象方法),因为只有具体方法才能在运行时被Spring AOP拦截并缓存结果。抽象方法(abstract method)不能被实例化,因此无法直接应用缓存[^1]。 - **Spring Bean管理**:抽象类必须被配置为Spring Bean或其子类是Bean。Spring会自动代理Bean,处理注解的逻辑。如果抽象类只是一个普通Java类(未纳入Spring容器),注解无效。 - **AOP代理机制**:Spring使用AOP代理来实现缓存拦截。抽象类的具体方法可以被代理,但需注意继承和覆盖可能影响行为(例如,子类覆盖方法时缓存可能失效)。 - **潜在限制**: - 如果方法涉及并发访问,可能需要额外处理(如引用[^2]建议的分布式锁),但这不影响可行性。 - 在Java 8+中,默认方法(default method)也可以使用@Cacheable,但抽象类不支持接口默认方法机制。 总的来说,如果抽象类中的方法是具体且被Spring管理,@Cacheable完全可用,这在Spring Boot或Spring应用中常见。 #### 2. **具体方法** 以下是在抽象类中使用@Cacheable的完整步骤和示例。假设你已配置Spring缓存(如通过@EnableCaching)。重点在于:注解必须应用于**具体方法**。 **步骤:** 1. **配置Spring缓存支持**:在Spring Boot启动类或配置类上添加@EnableCaching,以启用缓存功能。 ```java @SpringBootApplication @EnableCaching public class MyApp { public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } } ``` 2. **定义抽象类和添加@Cacheable**:在抽象类中,添加一个具体方法并注解@Cacheable。指定缓存名(如"myCache")和可选的键参数。 ```java import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service // 确保抽象类或其子类被Spring管理 public abstract class AbstractCacheService { // 具体方法使用@Cacheable缓存结果 @Cacheable(cacheNames = "userCache", key = "#id") // #id表示使用参数作为缓存键 public User getUserById(Long id) { // 模拟数据库查询 System.out.println("Executing database query for id: " + id); return new User(id, "User_" + id); } // 抽象方法不能使用@Cacheable(会编译错误) public abstract void otherMethod(); // 此方法无法缓存 } ``` 3. **子类实现抽象类**:抽象类需由具体子类扩展,并注册为Bean。 ```java @Service public class UserService extends AbstractCacheService { @Override public void otherMethod() { // 实现抽象方法 } } ``` 4. **调用和测试**:在其他类(如Controller)中注入UserService并调用getUserById方法。第一次调用会执行方法并缓存结果;后续相同id的调用直接返回缓存值。 - **控制台输出示例**: ``` First call: Executing database query for id: 1 Second call: (no output, result cached) ``` **关键说明**: - **缓存行为**:@Cacheable会在方法执行前检查缓存,如果缓存存在直接返回结果(避免真实执行)。适用于读多写少的场景。 - **键设计**:使用SpEL(如`#id`)确保唯一键,避免误缓存。 - **集成其他注解**:如果需要清除缓存,可在其他方法添加@CacheEvict。 #### 3. **注意事项** - **方法覆盖问题**:如果子类覆盖了@Cacheable方法,缓存逻辑可能失效(取决于代理类型)。Spring默认使用基于接口的代理,对于类代理需设置`@EnableCaching(proxyTargetClass = true)`。 - **性能考虑**:高并发下缓存可能导致竞争(如缓存击穿),可参考引用[^2]的分布式锁方案缓解。 - **测试建议**:在单元测试中模拟缓存行为(如使用Mockito),确保缓存正确触发。 - **框架兼容**:Spring 3.1+ 支持,但建议使用Spring Boot 2.x以上版本。 #### 相关问题 1. 在抽象类中使用@Cacheable时,如何处理子类方法覆盖导致的缓存失效? 2. Spring缓存如何与其他注解(如@CacheEvict)在抽象类中组合使用3. 如果抽象类的方法需要分布式锁解决并发问题,如何实现? 希望这个回答对您有帮助!如果需要更多代码示例或深入讨论,请提供更多上下文。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值