CacheLoader和装饰器模式

CacheLoader

CacheLoader 是 Google Guava 库中的一个类,用于定义如何加载缓存中的值。它通常与 LoadingCache 一起使用,以便在缓存中不存在某个键时自动加载相应的值。以下是 CacheLoader 的基本使用方法:

  1. 引入依赖 :首先,你需要在项目中引入 Guava 库的依赖。对于 Maven 项目,可以在 pom.xml 中添加以下依赖:

    xml 复制代码
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>31.0.1-jre</version>
    </dependency>
  2. 创建 CacheLoader :实现 CacheLoaderload 方法,该方法定义了如何加载缓存中的值。

    java 复制代码
    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) {
            // 创建 CacheLoader
            CacheLoader<String, String> loader = new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    // 定义如何加载缓存中的值
                    return "Value for " + key;
                }
            };
    
            // 创建 LoadingCache
            LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                    .expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存过期时间
                    .build(loader);
    
            // 使用缓存
            try {
                System.out.println(cache.get("key1")); // 输出: Value for key1
                System.out.println(cache.get("key2")); // 输出: Value for key2
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
  3. 配置缓存 :在创建 LoadingCache 时,可以使用 CacheBuilder 来配置缓存的各种属性,例如过期时间、最大缓存大小等。

    java 复制代码
    LoadingCache<String, String> cache = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存过期时间
            .maximumSize(100) // 设置缓存的最大大小
            .build(loader);
  4. 使用缓存 :通过 cache.get(key) 方法来获取缓存中的值。如果缓存中不存在该键,则会调用 CacheLoaderload 方法来加载值。

    java 复制代码
    try {
        String value = cache.get("key1");
        System.out.println(value); // 输出: Value for key1
    } catch (Exception e) {
        e.printStackTrace();
    }

通过 CacheLoaderLoadingCache,可以实现缓存的自动加载和管理。

在 Google Guava 的缓存库中,cache.refreshcache.invalidate 是两个常用的方法,用于管理缓存中的数据。它们的作用和使用场景有所不同:

1. cache.refresh

cache.refresh 方法用于异步地重新加载缓存中的值,而不会立即删除旧值。它会触发 CacheLoaderload 方法来重新加载数据,但在新值加载完成之前,旧值仍然可以被访问。

使用场景
  • 当你希望在后台异步更新缓存中的值,而不影响当前的读取操作时,可以使用 cache.refresh
  • 适用于需要定期刷新缓存数据的场景。
示例代码
java 复制代码
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>() {
            @Override
            public 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 方法用于立即删除缓存中的指定键及其对应的值。下次访问该键时,会触发 CacheLoaderload 方法来重新加载数据。

使用场景
  • 当你知道缓存中的数据已经过期或不再有效时,可以使用 cache.invalidate 来删除缓存中的数据。
  • 适用于需要手动控制缓存失效的场景。
示例代码
java 复制代码
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>() {
            @Override
            public 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 方法会在缓存中没有找到对应键的值时调用 CacheLoaderload 方法来加载数据。

LoadingCache 的工作原理

  1. 缓存命中 :如果缓存中存在对应键的值,cache.get 方法会直接返回该值。
  2. 缓存未命中 :如果缓存中不存在对应键的值,cache.get 方法会调用 CacheLoaderload 方法来加载数据,并将加载的数据存入缓存,然后返回该值。

示例代码

以下是一个完整的示例,展示了 LoadingCache 如何在缓存未命中时调用 CacheLoaderload 方法:

java 复制代码
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>() {
            @Override
            public 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);
    }
}

详细解释

  1. 创建 CacheLoader

    java 复制代码
    CacheLoader<String, String> loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) throws Exception {
            // 从数据库加载数据
            return loadFromDatabase(key);
        }
    };

    这里我们定义了一个 CacheLoader,它的 load 方法会在缓存未命中时被调用,从数据库加载数据。

  2. 创建 LoadingCache

    java 复制代码
    cache = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build(loader);

    使用 CacheBuilder 创建一个 LoadingCache,并设置缓存的过期时间为 10 分钟。

  3. 使用 cache.get 方法

    java 复制代码
    System.out.println(cache.get("key1")); // 输出: Value for key1

    初次加载时,缓存未命中,cache.get 方法会调用 CacheLoaderload 方法,从数据库加载数据并存入缓存。

  4. 使缓存失效

    java 复制代码
    cache.invalidate("key1");

    使缓存中的键 key1 失效,下次访问时会重新加载数据。

  5. 重新加载数据

    java 复制代码
    System.out.println(cache.get("key1")); // 输出: New Value for key1

    缓存失效后,再次访问时,cache.get 方法会再次调用 CacheLoaderload 方法,从数据库加载最新的数据。

总结

在使用 Google Guava 的 LoadingCache 时,cache.get 方法会在缓存未命中时调用 CacheLoaderload 方法来加载数据,并将加载的数据存入缓存。这样可以确保在缓存中找不到数据时,能够自动从数据源加载最新的数据。

装饰器模式

装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地向对象添加行为,而无需修改对象的类。装饰器模式通过创建一个装饰器类来包装原始类,从而在保持类接口不变的情况下扩展对象的功能。

主要角色

  1. Component:定义一个对象接口,可以给这些对象动态地添加职责。
  2. ConcreteComponent :实现 Component 接口的具体对象。
  3. Decorator :实现 Component 接口,并持有一个 Component 对象的引用。
  4. ConcreteDecorator :具体的装饰器类,扩展 Decorator 类,向 Component 添加职责。

示例代码

以下是一个使用装饰器模式的示例,展示了如何动态地向对象添加行为。假设我们有一个 Coffee 接口和一个 SimpleCoffee 类,我们希望通过装饰器模式向 SimpleCoffee 添加不同的装饰(如牛奶、糖等)。

Java 示例
java 复制代码
// Component
interface Coffee {
    String getDescription();
    double getCost();
}

// ConcreteComponent
class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Simple Coffee";
    }

    @Override
    public double getCost() {
        return 5.0;
    }
}

// Decorator
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost();
    }
}

// ConcreteDecorator
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", Milk";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 1.5;
    }
}

// ConcreteDecorator
class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", Sugar";
    }

    @Override
    public 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

详细解释

  1. Component 接口

    java 复制代码
    interface Coffee {
        String getDescription();
        double getCost();
    }

    定义了 Coffee 接口,包含两个方法:getDescriptiongetCost

  2. ConcreteComponent 类

    java 复制代码
    class SimpleCoffee implements Coffee {
        @Override
        public String getDescription() {
            return "Simple Coffee";
        }
    
        @Override
        public double getCost() {
            return 5.0;
        }
    }

    实现了 Coffee 接口的具体类 SimpleCoffee

  3. Decorator 抽象类

    java 复制代码
    abstract class CoffeeDecorator implements Coffee {
        protected Coffee decoratedCoffee;
    
        public CoffeeDecorator(Coffee coffee) {
            this.decoratedCoffee = coffee;
        }
    
        @Override
        public String getDescription() {
            return decoratedCoffee.getDescription();
        }
    
        @Override
        public double getCost() {
            return decoratedCoffee.getCost();
        }
    }

    实现了 Coffee 接口的抽象类 CoffeeDecorator,并持有一个 Coffee 对象的引用。

  4. ConcreteDecorator 类

    java 复制代码
    class MilkDecorator extends CoffeeDecorator {
        public MilkDecorator(Coffee coffee) {
            super(coffee);
        }
    
        @Override
        public String getDescription() {
            return decoratedCoffee.getDescription() + ", Milk";
        }
    
        @Override
        public double getCost() {
            return decoratedCoffee.getCost() + 1.5;
        }
    }
    
    class SugarDecorator extends CoffeeDecorator {
        public SugarDecorator(Coffee coffee) {
            super(coffee);
        }
    
        @Override
        public String getDescription() {
            return decoratedCoffee.getDescription() + ", Sugar";
        }
    
        @Override
        public double getCost() {
            return decoratedCoffee.getCost() + 0.5;
        }
    }

    实现了具体的装饰器类 MilkDecoratorSugarDecorator,分别向 Coffee 对象添加牛奶和糖的功能。

  5. 使用示例

    java 复制代码
    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 对象,并通过装饰器动态地向其添加牛奶和糖的功能。

总结

装饰器模式通过创建装饰器类来包装原始类,从而在保持类接口不变的情况下动态地向对象添加行为。它提供了一种灵活的方式来扩展对象的功能,而无需修改原始类的代码。

相关推荐
陌上 烟雨齐11 分钟前
Kafka数据生产和发送
java·分布式·kafka
Jinkxs19 分钟前
高级15-Java构建工具:Maven vs Gradle深度对比
java·开发语言·maven
有梦想的攻城狮20 分钟前
spring中的ApplicationRunner接口详解
java·后端·spring·runner·application
程序视点21 分钟前
设计模式之原型模式!附Java代码示例!
java·后端·设计模式
振鹏Dong2 小时前
微服务架构及常见微服务技术栈
java·后端
丶小鱼丶2 小时前
二叉树算法之【中序遍历】
java·算法
摇滚侠3 小时前
Oracle 关闭 impdp任务
java
编程爱好者熊浪3 小时前
RedisBloom使用
java
苇柠4 小时前
Spring框架基础(1)
java·后端·spring
yics.4 小时前
数据结构——栈和队列
java·数据结构