Spring 的緩存帝國,得益於這 5 個註解!
你好,我是猿 java
在微服務,分佈式的大環境下,緩存絕對是提升系統性能的關鍵手段,Spring 作爲 Java 生態中最流行的企業級應用框架,它是如何實現緩存的呢?這篇文章,我們將深入探討 Spring 中 5 個核心的緩存註解。
1. 什麼是緩存?
緩存(Cache)是一種存儲機制,旨在臨時存儲數據副本,以便快速訪問。緩存一般位於應用程序與數據源(如數據庫)之間,能夠顯著降低數據訪問延遲和減輕數據源的壓力。
2. 緩存的類型
緩存一般可以分爲下面 4 種類型:
-
本地緩存:存在於應用程序本地內存中,例如使用
ConcurrentHashMap
、Guava Cache 等。 -
分佈式緩存:跨多個應用實例共享的緩存,例如 Redis、Memcached、EhCache 的分佈式配置等。
-
持久化緩存:將緩存數據持久化到磁盤,以應對應用重啓後的數據恢復。
-
非持久化緩存:緩存數據存儲於內存,應用重啓後數據丟失。
3. Spring 緩存
Spring 從 4.0 版本起開始引入了 Cache 模塊,並提供了一套統一的緩存 API,隱藏了底層緩存實現的複雜性。開發者只需通過配置和註解即可實現緩存功能,支持多種緩存實現,如 EhCache、Redis、Caffeine 等。
Spring 緩存模塊的核心組件包括:
-
CacheManager:管理多個 Cache 實例,根據需要選擇合適的 Cache。
-
Cache:具體的緩存操作接口,定義了基本的緩存操作方法,如
get
、put
、evict
等。 -
CacheResolver:根據方法信息動態解析需要使用的 Cache。
-
KeyGenerator:生成緩存鍵的策略。
通過合理配置和使用,Spring 緩存抽象能夠靈活地滿足各種應用場景的需求。
4. Spring 緩存註解詳解
Spring 緩存註解主要有以下 5 個:
-
@Cacheable
-
@CachePut
-
@CacheEvict
-
@Caching
-
@CacheConfig
下面我們將逐一對這些註解進行分析。
4.1 @Cacheable
@Cacheable
註解用於方法級別,表示方法執行的結果可以被緩存。當方法被調用時,Spring 會先檢查緩存中是否存在對應的鍵值對,如果存在,則直接返回緩存中的結果;如果不存在,則執行方法,並將結果存入緩存。
使用示例:
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
// 模擬數據庫訪問
simulateSlowService();
returnnew User(id, "John Doe");
}
private void simulateSlowService() {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
thrownew IllegalStateException(e);
}
}
}
在上述示例中,getUserById
方法被@Cacheable
註解修飾,指定使用users
緩存,並以方法參數id
作爲緩存鍵。首次調用該方法時,緩存中不存在對應的用戶信息,方法會被執行並將結果存入緩存。後續相同的調用將直接從緩存中獲取結果,避免了重複的業務邏輯執行。
關鍵屬性:
-
value
/cacheNames
:指定緩存的名稱,可以有多個,表示多個緩存同時生效。 -
key
:指定緩存的鍵,支持 SpEL 表達式,默認基於方法參數生成。 -
condition
:緩存條件,符合條件的情況下才進行緩存。 -
unless
:否決緩存條件,符合條件的情況下不緩存。 -
keyGenerator
:自定義鍵生成策略。 -
cacheManager
:指定使用的緩存管理器。 -
cacheResolver
:指定緩存解析器。
4.2 @CachePut
@CachePut
註解同樣用於方法級別,但與@Cacheable
不同,它總是執行方法,並將結果存入緩存。@CachePut
適用於需要更新緩存但不影響方法執行結果的場景。
使用示例:
@Service
public class UserService {
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
// 模擬更新數據庫
return user;
}
}
在上述示例中,updateUser
方法被@CachePut
註解修飾,每次調用該方法時,都會執行方法邏輯(更新操作),並將返回的User
對象更新到users
緩存中。這樣可以確保緩存中的數據與數據庫中的數據保持一致。
關鍵屬性:
與@Cacheable
相同,@CachePut
也支持value
、cacheNames
、key
等屬性,用於指定緩存名稱、鍵及其他配置。
4.3 @CacheEvict
@CacheEvict
註解用於方法級別,表示在方法執行後,清除指定緩存中的一個或多個條目。它常用於刪除操作,以確保緩存中的數據與數據源保持一致。
使用示例:
@Service
public class UserService {
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
// 模擬刪除數據庫
}
}
在上述示例中,deleteUser
方法被@CacheEvict
註解修飾,指定從users
緩存中移除鍵爲id
的條目。這樣,在用戶被刪除後,相應的緩存數據也被清除,防止緩存中的數據不一致。
關鍵屬性:
-
value
/cacheNames
:指定緩存的名稱。 -
key
:指定要清除的緩存鍵。 -
allEntries
:指定是否清除緩存中的所有條目,默認爲false
。 -
beforeInvocation
:指定清除緩存的時機,默認爲方法執行成功後。 -
cacheManager
:指定使用的緩存管理器。 -
cacheResolver
:指定緩存解析器。
4.4 @Caching
@Caching
註解用於組合多個緩存註解,使得在一個方法上可以執行多個緩存操作。它適用於需要同時執行多個緩存行爲的複雜場景。
使用示例:
@Service
public class UserService {
@Caching(
put = { @CachePut(value = "users", key = "#user.id"),
@CachePut(value = "username", key = "#user.username") },
evict = { @CacheEvict(value = "userCache", allEntries = true) }
)
public User addUser(User user) {
// 模擬添加用戶到數據庫
return user;
}
}
在上述示例中,addUser
方法通過@Caching
註解同時執行了兩個@CachePut
操作,將用戶信息存入不同的緩存中,並且執行了一個@CacheEvict
操作,清除userCache
中的所有條目。
關鍵屬性:
@Caching
主要包含以下屬性:
-
cacheable
:@Cacheable
註解數組。 -
put
:@CachePut
註解數組。 -
evict
:@CacheEvict
註解數組。
通過組合不同類型的緩存註解,@Caching
提供了更靈活的緩存操作能力。
4.5 @CacheConfig
@CacheConfig
註解用於類級別,爲該類中的所有緩存註解提供公共配置。例如,可以指定統一的緩存名稱、緩存管理器等,減少重複配置的工作量。
使用示例:
@Service
@CacheConfig(cacheNames = "users", cacheManager = "cacheManager")
publicclass UserService {
@Cacheable(key = "#id")
public User getUserById(Long id) {
// 模擬數據庫訪問
returnnew User(id, "John Doe");
}
@CachePut(key = "#user.id")
public User updateUser(User user) {
// 模擬更新數據庫
return user;
}
@CacheEvict(key = "#id")
public void deleteUser(Long id) {
// 模擬刪除數據庫
}
}
在上述示例中,@CacheConfig
註解指定了默認的緩存名稱和緩存管理器,使得類中的所有緩存註解無需重複指定這些屬性,只需關注特定的鍵或其他配置。
關鍵屬性:
-
cacheNames
/value
:指定默認的緩存名稱。 -
cacheManager
:指定默認的緩存管理器。 -
cacheResolver
:指定默認的緩存解析器。 -
keyGenerator
:指定默認的鍵生成策略。
@CacheConfig
通過提供類級別的緩存配置,簡化了屬性的配置和維護,提高了代碼的可讀性和可維護性。
5. 緩存框架
要使 Spring 的緩存註解生效,必須配置一個緩存管理器(CacheManager
)和相應的緩存提供者。Spring 支持多種緩存實現,常見的包括 EhCache、Redis、Caffeine 等。下面,我們介紹這 3 種常用緩存提供者的配置方法。
5.1 EhCache
EhCache 是一款常用的開源緩存庫,支持本地內存和磁盤存儲,配置靈活,適用於單機應用。
依賴配置(Maven):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
配置示例:
創建一個 EhCache 配置文件ehcache.xml
:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="false"/>
<cache
maxEntriesLocalHeap="500"
timeToLiveSeconds="3600"
eternal="false"
overflowToDisk="false"/>
</ehcache>
Spring 配置:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
factoryBean.setShared(true);
return factoryBean;
}
@Bean
public CacheManager cacheManager(EhCacheManagerFactoryBean factoryBean) {
returnnew EhCacheCacheManager(factoryBean.getObject());
}
}
5.2 Redis
Redis 是一種高性能的 NoSQL 緩存數據庫,支持分佈式部署,適用於大規模應用場景。
依賴配置(Maven):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置示例(application.properties):
spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=yourpassword
Spring 配置:
Spring Boot 會自動配置RedisCacheManager
,無需額外配置。如果需要自定義配置,可以如下:
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
// 配置默認緩存過期時間等
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
5.3 Caffeine
Caffeine 是一個高性能的本地緩存庫,具有豐富的緩存策略和高併發性能。
依賴配置(Maven):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.6</version>
</dependency>
Spring 配置:
@Configuration
@EnableCaching
public class CaffeineCacheConfig {
@Bean
public Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder()
.expireAfterWrite(60, TimeUnit.MINUTES)
.maximumSize(1000);
}
@Bean
public CacheManager cacheManager(Caffeine<Object, Object> caffeine) {
CaffeineCacheManager manager = new CaffeineCacheManager("users");
manager.setCaffeine(caffeine);
return manager;
}
}
6. 案例分析
下面我們通過一個簡單的 CRUD 應用,演示如何在 Spring Boot 項目中集成和使用緩存註解。
6.1 項目介紹
構建一個用戶管理系統,包含用戶的增刪改查功能。通過緩存優化其中的讀取操作,以提升系統性能。
6.2 環境搭建
技術棧:
-
Spring Boot:快速構建項目基礎。
-
Spring Data JPA:數據訪問層。
-
H2 數據庫:內存數據庫,方便演示。
-
Spring Cache:緩存抽象。
-
EhCache:作爲緩存實現。
依賴配置(Maven):
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot Cache Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- EhCache -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
<!-- Lombok(可選,用於簡化代碼) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
6.3 緩存配置
創建ehcache.xml
文件:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="false"/>
<cache
maxEntriesLocalHeap="500"
timeToLiveSeconds="3600"
eternal="false"
overflowToDisk="false"/>
</ehcache>
配置類:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
factoryBean.setShared(true);
return factoryBean;
}
@Bean
public CacheManager cacheManager(net.sf.ehcache.CacheManager cm) {
returnnew EhCacheCacheManager(cm);
}
}
6.4 實體和倉庫
用戶實體類:
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
private Long id;
private String username;
}
用戶倉庫接口:
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
6.5 服務層與緩存註解應用
@Service
@CacheConfig(cacheNames = "users")
public class UserService {
@Autowired
private UserRepository userRepository;
@Cacheable(key = "#id")
public User getUserById(Long id) {
simulateSlowService();
return userRepository.findById(id).orElse(null);
}
@Cacheable(key = "#username")
public User getUserByUsername(String username) {
simulateSlowService();
return userRepository.findByUsername(username);
}
@CachePut(key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
@CacheEvict(key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
private void simulateSlowService() {
try {
Thread.sleep(2000L); // 模擬耗時操作
} catch (InterruptedException e) {
thrownew IllegalStateException(e);
}
}
}
在上述示例中:
-
getUserById
和getUserByUsername
方法被@Cacheable
註解修飾,表示查詢用戶時會先從緩存中查找,若緩存不存在則執行數據庫查詢並將結果緩存在users
緩存中。 -
updateUser
方法被@CachePut
註解修飾,表示更新用戶信息時,會將更新後的用戶對象寫入緩存。 -
deleteUser
方法被@CacheEvict
註解修飾,表示刪除用戶時,會從緩存中移除對應的用戶信息。
6.6 控制層
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
@GetMapping("/username/{username}")
public ResponseEntity<User> getUserByUsername(@PathVariable String username) {
User user = userService.getUserByUsername(username);
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<User> addUser(@RequestBody User user) {
User savedUser = userService.updateUser(user);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
6.7 測試緩存效果
-
啓動應用程序。
-
調用
GET /api/users/{id}
接口:
-
首次調用會觸發數據庫查詢並緩存結果。
-
第二次調用相同的接口,將直接從緩存中獲取用戶信息,響應速度更快。
- 調用
POST /api/users
接口更新用戶:
- 更新操作會通過
@CachePut
註解將新的用戶信息更新到緩存中。
- 調用
DELETE /api/users/{id}
接口刪除用戶:
- 刪除操作會通過
@CacheEvict
註解從緩存中移除用戶信息。
通過上述步驟,可以驗證緩存的實際效果,發現讀取操作的響應時間明顯降低。
7. 增強功能
7.1 自定義緩存鍵生成策略
默認情況下,Spring 根據方法的參數生成緩存鍵。對於複雜的業務場景,可能需要自定義緩存鍵生成策略。
自定義 KeyGenerator:
@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName() + "_" + Arrays.stream(params)
.map(Object::toString)
.collect(Collectors.joining("_"));
}
}
使用自定義 KeyGenerator:
@Cacheable(cacheNames = "users", keyGenerator = "customKeyGenerator")
public User getUser(Long id, String type) {
// 方法實現
}
7.2 緩存條件與排除
通過condition
和unless
屬性,可以控制是否進行緩存操作。
-
condition
:在滿足條件時才進行緩存。 -
unless
:在滿足條件時不進行緩存。
示例:
@Cacheable(value = "users", key = "#id", condition = "#id > 10", unless = "#result.username == 'admin'")
public User getUserById(Long id) {
// 方法實現
}
在上述示例中:
-
只有當
id > 10
時,方法執行結果纔會被緩存。 -
即使滿足
condition
條件,如果result.username == 'admin'
,則不緩存結果。
7.3 緩存同步與異步
在分佈式系統中,緩存的一致性和同步性是至關重要的。Spring Cache 本身不直接提供同步機制,但可以通過結合其他工具實現。
方案:
-
使用消息隊列(如 Kafka、RabbitMQ)同步緩存更新。
-
利用分佈式鎖(如 Redis 的 RedLock)防止緩存擊穿和緩存穿透。
-
實現基於事件驅動的緩存更新策略。
7.4 緩存與事務的結合
在涉及事務的操作中,緩存的更新需要與事務保持一致性。
方案:
-
緩存更新操作應在事務提交後執行,確保數據的一致性。
-
使用
@CacheEvict
的beforeInvocation
屬性控制緩存清除的時機。
示例:
@CacheEvict(value = "users", key = "#id", beforeInvocation = false)
@Transactional
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
在上述示例中,緩存清除操作將在事務提交後執行,確保數據成功刪除後再清除緩存。
8. 總結
本文,我們分析了緩存技術,它在提升應用性能、降低數據庫壓力、改善用戶體驗方面發揮着重要作用。
另外,我們重點分析了 Spring 中 5 個核心的緩存註解以及示例分析,Spring 通過提供全面的緩存抽象和簡潔的緩存註解,使得開發者能夠輕鬆地集成和管理緩存機制。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/19u0Las7ego4G5c4uMrP2A