Spring 3.1 中使用 @Cacheable

本文介绍Spring3.1中@Cacheable注解的使用方法,包括缓存位置、缓存键生成及缓存条件等特性,并探讨如何通过SpEL表达式自定义这些属性。


在 Spring 3.1 中使用 @Cacheable 实现缓存软件开发中使用缓存已经有一个非常久的历史了。缓存是一种很好的设计思想,一旦你用了他,你将会发现他确实很有用。Spring3.1版本的核心对缓存做了实现。在Java推出Annotation特性之前,实现缓存的一个难点在于它与业务逻辑代码的耦合性太强。

然而,Spring3.1中使用@Cacheable 和@CacheEvict实现缓存在某种程度上解决了这个问题,基本思想是在方法加上@Cacheable注解,这个方法的返回值将具有缓存特性。

@Cacheable注解可以用在方法或者类级别。当他应用于方法级别的时候,就是如上所说的缓存返回值了。当应用在类级别的时候,这个类的所有方法的返回值都将被缓存。

@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指明了缓存将被存到什么地方。

@Cacheable(value = "employee")
 public Person findEmployee(String firstName, String surname, int age) {

   return new Person(firstName, surname, age);
 }

上面的代码保证findEmployee的返回值Person对象将被存储在"employee"中。

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

@Cacheable(value = "employee", key = "#surname")
   public Person findEmployeeBySurname(String firstName, String surname, int age) {

    return new Person(firstName, surname, age);
  }

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

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

@Cacheable(value = "employee", condition = "#age < 25")
 public Person findEmployeeByAge(String firstName, String surname, int age) {

   return new Person(firstName, surname, age);
 }

上面的例子中,只有年龄小于25的时候才被缓存。

在快速看完了如何使用缓存后,我们接下来看看缓存带来的效果。

<A href="http://my.oschina.net/test45" target=_blank rel=nofollow>@Test</A> 
  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作为缓存条件,就将得到不同的结果。

<A href="http://my.oschina.net/test45" target=_blank rel=nofollow>@Test</A> 
 public void testCacheWithAgeAsCondition() {

   Person employee1 = instance.findEmployeeByAge("John", "Smith", 33);
   Person employee2 = instance.findEmployeeByAge("John", "Smith", 33);

   assertEquals(employee1, employee2);
 }

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

<A href="http://my.oschina.net/test45" target=_blank rel=nofollow>@Test</A> 
 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的缓存呢?欲知后事如何,且听下回分解。

<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、付费专栏及课程。

余额充值