如何在Spring Boot中配置多个缓存管理器

x33g5p2x  于2022-09-17 转载在 Spring  
字(11.8k)|赞(0)|评价(0)|浏览(1235)

在这篇Spring Boot文章中,我们将了解如何在Spring Boot中配置多个缓存管理器应用程序。
广告

Spring Boot中的多个缓存管理器

在标准的Spring Boot应用程序中,我们可能只需要一个缓存提供者,但是有很多用例,你想配置多个缓存提供者,并喜欢根据你的要求使用这些缓存。本文将介绍Spring Boot缓存API以及在Spring Boot中*配置多个缓存管理器的能力。 *有几种方法可以做到这一点,正确的方法取决于你打算如何使用缓存。本文作为一个指南,选择最适合你的要求的方法。在这篇文章中,我们将使用以下两种缓存API进行配置。

  1. Ehcache
  2. Caffeine Cache

本文假设你有Spring Boot和Spring缓存API的相关知识。

1. 应用程序的设置

让我们从设置应用程序开始。我们将使用Spring自动配置来为我们完成繁重的工作。你可以使用Spring Initializr生成应用程序的结构,也可以使用IDE生成应用程序的结构。

下面是我们的pom.xml的样子

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.3.4.RELEASE</version>
      <relativePath />
      <!-- lookup parent from repository -->
   </parent>
   <groupId>com.javadevjournal</groupId>
   <artifactId>multiple-cache-manager</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>multiple-cache-manager</name>
   <description>Multiple Cache manager with spring boot</description>
   <properties>
      <java.version>11</java.version>
      <ehcache-version>3.9.0</ehcache-version>
      <caffeine-version>2.8.6</caffeine-version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-cache</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>com.github.ben-manes.caffeine</groupId>
         <artifactId>caffeine</artifactId>
         <version>${caffeine-version}</version>
      </dependency>
      <dependency>
         <groupId>org.ehcache</groupId>
         <artifactId>ehcache</artifactId>
         <version>${ehcache-version}</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
         <exclusions>
            <exclusion>
               <groupId>org.junit.vintage</groupId>
               <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
         </exclusions>
      </dependency>
   </dependencies>
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
</project>

我们在这个例子中使用Caffeine缓存和默认的ConcurrentHashMap缓存。

2. Spring缓存

Spring提供了一个强大而灵活的缓存抽象。缓存抽象使得在Spring应用程序中实现缓存变得很容易。我们可以使用@EnableCachingAnnotation启用缓存API。如果我们没有明确指定任何其他的缓存,Spring将回到ConcurrentHashMap作为底层缓存。

@Configuration
@EnableCaching
public class MultiCacheManagerConfig{
    //to enable caching for our application
}

如前所述,在Spring Boot中,有很多方法可以启用和配置多个缓存管理器。让我们看看这些选项。

3. 通过CacheConfigurerSupport配置多个缓存管理器

如果你的应用程序在大多数情况下使用一个缓存提供者,而只想在特定情况下使用另一个缓存管理器,使用CacheConfigurerSupport配置多个缓存管理器将为你提供更大的灵活性。

  1. 我们可以用这种方法定义一个默认的缓存管理器。
  2. 你可以继续使用缓存注释,不做任何改变。
  3. 对于特定的用例,我们可以通过cacheManager@CacheConfig@Cacheable注释。

让我们看看如何配置它。

package com.javadevjournal.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class MultiCacheManagerConfig extends CachingConfigurerSupport {

    public String[] cacheNames = {
        "products"
    };

    /**
     * We are using CachingConfigurerSupport to define out main caching
     * provider. In our case it's Caffeine cache. This will be the default cache provider
     * for our application. If we don't provide explicit cache manager, Spring Boot
     * will pick this as default cache provider.
     * @return
     */
    @Override
    @Bean // good to have but not strictly necessary
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCacheNames(Arrays.asList(
            "customers",
            "products"
        ));
        cacheManager.setCaffeine(caffeineCacheBuilder());
        return cacheManager;
    }

    Caffeine < Object, Object > caffeineCacheBuilder() {
        return Caffeine.newBuilder()
            .initialCapacity(100)
            .maximumSize(500)
            .expireAfterAccess(10, TimeUnit.MINUTES)
            .weakKeys()
            .recordStats();
    }

    /**
     * Second cache provider which can work as fallback or will be used when invoked explicitly in the
     * code base.
     */
    @Bean
    CacheManager alternateCacheManager() {
        return new ConcurrentMapCacheManager(cacheNames);
    }
}

使用Caffeine缓存,我们要配置2个缓存(客户和产品),而使用默认缓存;我们要设置产品缓存。以下是你如何在你的应用程序中使用这些缓存管理器的方法

//Class levels
@CacheConfig(cacheManager = "alternateCacheManager")
public class DefaultProductService {
    
}

//method levels 
@Cacheable(cacheNames = "products", cacheManager = "alternateCacheManager")
@Override
public Product getProductByCode(String code) {

}

@Cacheable(cacheNames = "products")
@Override
public Product getProductByBrand(String brand) {
    
}

4. 使用@Primary的多个缓存管理器

如果我们不想使用CacheConfigurerSupport,我们可以使用@Primary annotation标记一个Bean作为主要的。如果我们没有用@CacheConfig@Cacheable注解来指定cacheManager,Spring会自动挑选主豆。

@Configuration
@EnableCaching
public class MultiCacheManagerConfig {

    public String[] cacheNames = {
        "products"
    };


    @Bean
    @Primary
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCacheNames(Arrays.asList(
            "customers",
            "products"
        ));
        cacheManager.setCaffeine(caffeineCacheBuilder());
        return cacheManager;
    }

    Caffeine < Object, Object > caffeineCacheBuilder() {
        return Caffeine.newBuilder()
            .initialCapacity(100)
            .maximumSize(500)
            .expireAfterAccess(10, TimeUnit.MINUTES)
            .weakKeys()
            .recordStats();
    }

    @Bean
    CacheManager alternateCacheManager() {
        return new ConcurrentMapCacheManager(cacheNames);
    }
}

5. 使用CacheResolver

CacheResolver提供了更细化的控制。你应该考虑使用CacheResolver。

  1. 如果你需要根据具体情况选择缓存管理器。
  2. 你需要在运行时根据请求的类型来选择缓存管理器。

CacheResolverJSR-107比较吻合。作为第一步,我们需要通过扩展CacheResolver来创建我们自定义的CacheResolver

package com.javadevjournal.caching;

import com.javadevjournal.service.impl.DefaultCustomerService;
import com.javadevjournal.service.impl.DefaultProductService;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheResolver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

public class CustomCacheResolver implements CacheResolver {

    private final CacheManager cacheManager;
    private final CacheManager alternateCacheManager;

    public CustomCacheResolver(final CacheManager cacheManager, CacheManager alternateCacheManager) {
        this.cacheManager = cacheManager;
        this.alternateCacheManager = cacheManager;
    }

    @Override
    public Collection << ? extends Cache > resolveCaches(CacheOperationInvocationContext << ? > context) {
        Collection < String > cacheNames = getCacheNames(context);
        if (cacheNames == null) {
            return Collections.emptyList();
        }
        Collection < Cache > result = new ArrayList < > (cacheNames.size());
        if (context.getTarget() instanceof DefaultProductService) {
            for (String cacheName: cacheNames) {
                Cache cache = cacheManager.getCache(cacheName);
                if (cache == null) {
                    throw new IllegalArgumentException("Cannot find cache named '" +
                        cacheName + "' for " + context.getOperation());
                }
                result.add(cache);
            }
        }
        if (context.getTarget() instanceof DefaultCustomerService) {
            for (String cacheName: cacheNames) {
                Cache cache = alternateCacheManager.getCache(cacheName);
                if (cache == null) {
                    throw new IllegalArgumentException("Cannot find cache named '" +
                        cacheName + "' for " + context.getOperation());
                }
                result.add(cache);
            }
        }
        return result;
    }

    protected Collection < String > getCacheNames(CacheOperationInvocationContext << ? > context) {
        return context.getOperation().getCacheNames();
    }
}

下一步是将我们的自定义CacheResolver定义为一个bean。我们正在使用我们的Config类来扩展CachingConfigurerSupport

@Configuration
@EnableCaching
public class MultiCacheManagerConfig extends CachingConfigurerSupport {
    ....

    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new CustomCacheResolver(cacheManager(), alternateCacheManager());
    }
}

为了使用自定义的CacheResolver,我们可以用@Cacheable或其他缓存注解来传递它。

@Cacheable(cacheNames = "products", cacheResolver = "cacheResolver")
@Override
public Product getProductByBrand(String brand) {
    
}

6. 测试应用程序

为了检查多个缓存管理器是否按预期工作并返回缓存实例,让我们创建一个简单的控制器和服务类来看看工作流程的运行情况。

6.1. 产品控制器

我们的产品控制器将有1个方法,它将使用DefaultProductService来获取产品数据。DefaultProductService服务将使用备用的缓存管理器来处理缓存。

@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    ProductService productService;

    @Autowired
    CacheManager alternateCacheManager;

    @GetMapping("/product/{code}")
    public Product getProductByCode(@PathVariable(value = "code") String code) {
        Product product = productService.getProductByCode(code);
        alternateCacheManager.getCacheNames(); // this is only for demo purpose, don't use this in real life application
        return product;
    }
}
6.2. 默认产品服务
@Service("productService")
public class DefaultProductService implements ProductService {

    private static final Logger LOG = LoggerFactory.getLogger(DefaultProductService.class);

    @Cacheable(cacheNames = "products", cacheManager = "alternateCacheManager")
    @Override
    public Product getProductByCode(String code) {
        LOG.info("Get product by code {} ", code);
        Product product = new Product();
        product.setCode(code);
        product.setBrand("Sony");
        product.setDescription("Sony new camera");
        product.setName("Sony Alpha A7S");
        return product;
    }
}
6.3. 客户控制器

客户控制器将调用DefaultCustomerService,它将回复defaulr CacheManager来处理缓存。

@RestController
@RequestMapping("/customers")
public class CustomerController {

    @Autowired
    CustomerService customerService;

    @Autowired
    CacheManager cacheManager;

    @GetMapping("/customer/{id}")
    public Customer getCustomerByID(@PathVariable(value = "id") Long id) {
        Customer customer = customerService.getCustomer(id);
        cacheManager.getCacheNames();
        return customer;
    }
}
6.4. 默认产品服务
@Service
public class DefaultCustomerService implements CustomerService {

    private static final Logger LOG = LoggerFactory.getLogger(DefaultCustomerService.class);

    @Cacheable(cacheNames = "customers")
    @Override
    public Customer getCustomer(Long id) {
        LOG.info("Fetching customer information for Id {} ", id);
        Customer customer = new Customer();
        customer.setEmail("[email protected]");
        customer.setFirstName("Javadev");
        customer.setLastName("Journal");
        customer.setId(id);
        return customer;
    }
}
6.5. 运行和检查缓存

运行应用程序并点击以下网址。

  1. http://localhost:8080/products/product/1
  2. http://localhost:8080/products/product/2
  3. http://localhost:8080/products/product/1
  4. http://localhost:8080/customers/customer/1
  5. http://localhost:8080/customers/customer/2
  6. http://localhost:8080/customers/customer/1

对于#1,#2,#4和#5,你会看到日志语句,而对于其他的,将没有日志语句,因为数据将从缓存中提供。这就是你的日志可能的样子。

2020-10-21 16:57:48.611  INFO 99215 --- [nio-8080-exec-1] c.j.service.impl.DefaultProductService   : Get product by code 1 
2020-10-21 16:57:53.314  INFO 99215 --- [nio-8080-exec-2] c.j.service.impl.DefaultProductService   : Get product by code 2 
2020-10-21 16:58:46.810  INFO 99215 --- [nio-8080-exec-6] c.j.service.impl.DefaultCustomerService  : Fetching customer information for Id 1 
2020-10-21 16:58:56.264  INFO 99215 --- [nio-8080-exec-7] c.j.service.impl.DefaultCustomerService  : Fetching customer information for Id 2

为了更好地理解,这里有一些屏幕截图

我们将Caffeine缓存配置为同时处理产品和客户缓存,但在这个例子中我们只使用了客户缓存。

摘要

在这篇文章中,我们看到了如何使用Spring缓存在Spring Boot中配置多个缓存管理器。我们看到了在Spring中处理多个缓存管理器的下列选项。

  1. 通过CacheConfigurerSupport进行配置。
  2. 使用@Primary注解。
  3. 创建自定义的CacheResolver

一如既往,本文的源代码可以在GitHub上找到。

相关文章