CacheLoader和装饰器模式
CacheLoader
CacheLoader 是 Google Guava 库中的一个类,用于定义如何加载缓存中的值。它通常与 LoadingCache 一起使用,以便在缓存中不存在某个键时自动加载相应的值。以下是 CacheLoader 的基本使用方法:
-
引入依赖:首先,你需要在项目中引入 Guava 库的依赖。对于 Maven 项目,可以在
pom.xml中添加以下依赖:<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.0.1-jre</version> </dependency> -
创建 CacheLoader:实现
CacheLoader的load方法,该方法定义了如何加载缓存中的值。import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.CacheBuilder;import java.util.concurrent.TimeUnit;public class CacheLoaderExample {public static void main(String[] args) {// 创建 CacheLoaderCacheLoader<String, String> loader = new CacheLoader<String, String>() {@Overridepublic String load(String key) throws Exception {// 定义如何加载缓存中的值return "Value for " + key;}};// 创建 LoadingCacheLoadingCache<String, String> cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存过期时间.build(loader);// 使用缓存try {System.out.println(cache.get("key1")); // 输出: Value for key1System.out.println(cache.get("key2")); // 输出: Value for key2} catch (Exception e) {e.printStackTrace();}} } -
配置缓存:在创建
LoadingCache时,可以使用CacheBuilder来配置缓存的各种属性,例如过期时间、最大缓存大小等。LoadingCache<String, String> cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存过期时间.maximumSize(100) // 设置缓存的最大大小.build(loader); -
使用缓存:通过
cache.get(key)方法来获取缓存中的值。如果缓存中不存在该键,则会调用CacheLoader的load方法来加载值。try {String value = cache.get("key1");System.out.println(value); // 输出: Value for key1 } catch (Exception e) {e.printStackTrace(); }
通过 CacheLoader 和 LoadingCache,可以实现缓存的自动加载和管理。
在 Google Guava 的缓存库中,cache.refresh 和 cache.invalidate 是两个常用的方法,用于管理缓存中的数据。它们的作用和使用场景有所不同:
1. cache.refresh
cache.refresh 方法用于异步地重新加载缓存中的值,而不会立即删除旧值。它会触发 CacheLoader 的 load 方法来重新加载数据,但在新值加载完成之前,旧值仍然可以被访问。
使用场景
- 当你希望在后台异步更新缓存中的值,而不影响当前的读取操作时,可以使用
cache.refresh。 - 适用于需要定期刷新缓存数据的场景。
示例代码
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;import java.util.concurrent.TimeUnit;public class CacheRefreshExample {private static LoadingCache<String, String> cache;static {CacheLoader<String, String> loader = new CacheLoader<String, String>() {@Overridepublic String load(String key) throws Exception {// 从数据库加载数据return loadFromDatabase(key);}};cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(loader);}public static void main(String[] args) {// 初次加载System.out.println(cache.getUnchecked("key1"));// 刷新缓存cache.refresh("key1");// 等待刷新完成try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// 重新加载System.out.println(cache.getUnchecked("key1"));}private static String loadFromDatabase(String key) {// 模拟从数据库加载数据return "Value for " + key;}
}
2. cache.invalidate
cache.invalidate 方法用于立即删除缓存中的指定键及其对应的值。下次访问该键时,会触发 CacheLoader 的 load 方法来重新加载数据。
使用场景
- 当你知道缓存中的数据已经过期或不再有效时,可以使用
cache.invalidate来删除缓存中的数据。 - 适用于需要手动控制缓存失效的场景。
示例代码
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;import java.util.concurrent.TimeUnit;public class CacheInvalidateExample {private static LoadingCache<String, String> cache;static {CacheLoader<String, String> loader = new CacheLoader<String, String>() {@Overridepublic String load(String key) throws Exception {// 从数据库加载数据return loadFromDatabase(key);}};cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(loader);}public static void main(String[] args) {// 初次加载System.out.println(cache.getUnchecked("key1"));// 使缓存失效cache.invalidate("key1");// 重新加载System.out.println(cache.getUnchecked("key1"));}private static String loadFromDatabase(String key) {// 模拟从数据库加载数据return "Value for " + key;}
}
总结
cache.refresh:用于异步地重新加载缓存中的值,不会立即删除旧值,适用于需要定期刷新缓存数据的场景。cache.invalidate:用于立即删除缓存中的指定键及其对应的值,适用于需要手动控制缓存失效的场景。
在使用 Google Guava 的 LoadingCache 时,cache.get 方法会在缓存中没有找到对应键的值时调用 CacheLoader 的 load 方法来加载数据。
LoadingCache 的工作原理
- 缓存命中:如果缓存中存在对应键的值,
cache.get方法会直接返回该值。 - 缓存未命中:如果缓存中不存在对应键的值,
cache.get方法会调用CacheLoader的load方法来加载数据,并将加载的数据存入缓存,然后返回该值。
示例代码
以下是一个完整的示例,展示了 LoadingCache 如何在缓存未命中时调用 CacheLoader 的 load 方法:
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;public class LoadingCacheExample {private static LoadingCache<String, String> cache;static {CacheLoader<String, String> loader = new CacheLoader<String, String>() {@Overridepublic String load(String key) throws Exception {// 从数据库加载数据return loadFromDatabase(key);}};cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(loader);}public static void main(String[] args) {try {// 初次加载,缓存未命中,调用 load 方法System.out.println(cache.get("key1")); // 输出: Value for key1// 再次加载,缓存命中,不调用 load 方法System.out.println(cache.get("key1")); // 输出: Value for key1// 模拟数据库更新updateDatabase("key1", "New Value for key1");// 使缓存失效cache.invalidate("key1");// 重新加载,缓存未命中,调用 load 方法System.out.println(cache.get("key1")); // 输出: New Value for key1} catch (ExecutionException e) {e.printStackTrace();}}private static String loadFromDatabase(String key) {// 模拟从数据库加载数据return "Value for " + key;}private static void updateDatabase(String key, String newValue) {// 模拟更新数据库System.out.println("Database updated: " + key + " -> " + newValue);}
}
详细解释
-
创建
CacheLoader:CacheLoader<String, String> loader = new CacheLoader<String, String>() {@Overridepublic String load(String key) throws Exception {// 从数据库加载数据return loadFromDatabase(key);} };这里我们定义了一个
CacheLoader,它的load方法会在缓存未命中时被调用,从数据库加载数据。 -
创建
LoadingCache:cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(loader);使用
CacheBuilder创建一个LoadingCache,并设置缓存的过期时间为 10 分钟。 -
使用
cache.get方法:System.out.println(cache.get("key1")); // 输出: Value for key1初次加载时,缓存未命中,
cache.get方法会调用CacheLoader的load方法,从数据库加载数据并存入缓存。 -
使缓存失效:
cache.invalidate("key1");使缓存中的键
key1失效,下次访问时会重新加载数据。 -
重新加载数据:
System.out.println(cache.get("key1")); // 输出: New Value for key1缓存失效后,再次访问时,
cache.get方法会再次调用CacheLoader的load方法,从数据库加载最新的数据。
总结
在使用 Google Guava 的 LoadingCache 时,cache.get 方法会在缓存未命中时调用 CacheLoader 的 load 方法来加载数据,并将加载的数据存入缓存。这样可以确保在缓存中找不到数据时,能够自动从数据源加载最新的数据。
装饰器模式
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地向对象添加行为,而无需修改对象的类。装饰器模式通过创建一个装饰器类来包装原始类,从而在保持类接口不变的情况下扩展对象的功能。
主要角色
- Component:定义一个对象接口,可以给这些对象动态地添加职责。
- ConcreteComponent:实现
Component接口的具体对象。 - Decorator:实现
Component接口,并持有一个Component对象的引用。 - ConcreteDecorator:具体的装饰器类,扩展
Decorator类,向Component添加职责。
示例代码
以下是一个使用装饰器模式的示例,展示了如何动态地向对象添加行为。假设我们有一个 Coffee 接口和一个 SimpleCoffee 类,我们希望通过装饰器模式向 SimpleCoffee 添加不同的装饰(如牛奶、糖等)。
Java 示例
// Component
interface Coffee {String getDescription();double getCost();
}// ConcreteComponent
class SimpleCoffee implements Coffee {@Overridepublic String getDescription() {return "Simple Coffee";}@Overridepublic double getCost() {return 5.0;}
}// Decorator
abstract class CoffeeDecorator implements Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee coffee) {this.decoratedCoffee = coffee;}@Overridepublic String getDescription() {return decoratedCoffee.getDescription();}@Overridepublic double getCost() {return decoratedCoffee.getCost();}
}// ConcreteDecorator
class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + ", Milk";}@Overridepublic double getCost() {return decoratedCoffee.getCost() + 1.5;}
}// ConcreteDecorator
class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + ", Sugar";}@Overridepublic double getCost() {return decoratedCoffee.getCost() + 0.5;}
}// 使用示例
public class DecoratorPatternExample {public static void main(String[] args) {Coffee coffee = new SimpleCoffee();System.out.println(coffee.getDescription() + " $" + coffee.getCost());coffee = new MilkDecorator(coffee);System.out.println(coffee.getDescription() + " $" + coffee.getCost());coffee = new SugarDecorator(coffee);System.out.println(coffee.getDescription() + " $" + coffee.getCost());}
}
输出
Simple Coffee $5.0
Simple Coffee, Milk $6.5
Simple Coffee, Milk, Sugar $7.0
详细解释
-
Component 接口:
interface Coffee {String getDescription();double getCost(); }定义了
Coffee接口,包含两个方法:getDescription和getCost。 -
ConcreteComponent 类:
class SimpleCoffee implements Coffee {@Overridepublic String getDescription() {return "Simple Coffee";}@Overridepublic double getCost() {return 5.0;} }实现了
Coffee接口的具体类SimpleCoffee。 -
Decorator 抽象类:
abstract class CoffeeDecorator implements Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee coffee) {this.decoratedCoffee = coffee;}@Overridepublic String getDescription() {return decoratedCoffee.getDescription();}@Overridepublic double getCost() {return decoratedCoffee.getCost();} }实现了
Coffee接口的抽象类CoffeeDecorator,并持有一个Coffee对象的引用。 -
ConcreteDecorator 类:
class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + ", Milk";}@Overridepublic double getCost() {return decoratedCoffee.getCost() + 1.5;} }class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + ", Sugar";}@Overridepublic double getCost() {return decoratedCoffee.getCost() + 0.5;} }实现了具体的装饰器类
MilkDecorator和SugarDecorator,分别向Coffee对象添加牛奶和糖的功能。 -
使用示例:
public class DecoratorPatternExample {public static void main(String[] args) {Coffee coffee = new SimpleCoffee();System.out.println(coffee.getDescription() + " $" + coffee.getCost());coffee = new MilkDecorator(coffee);System.out.println(coffee.getDescription() + " $" + coffee.getCost());coffee = new SugarDecorator(coffee);System.out.println(coffee.getDescription() + " $" + coffee.getCost());} }创建了一个
SimpleCoffee对象,并通过装饰器动态地向其添加牛奶和糖的功能。
总结
装饰器模式通过创建装饰器类来包装原始类,从而在保持类接口不变的情况下动态地向对象添加行为。它提供了一种灵活的方式来扩展对象的功能,而无需修改原始类的代码。
