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

总结

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

相关推荐
yuanbenshidiaos32 分钟前
c++---------数据类型
java·jvm·c++
向宇it36 分钟前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
Lojarro1 小时前
【Spring】Spring框架之-AOP
java·mysql·spring
莫名其妙小饼干1 小时前
网上球鞋竞拍系统|Java|SSM|VUE| 前后端分离
java·开发语言·maven·mssql
isolusion1 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp2 小时前
Spring-AOP
java·后端·spring·spring-aop
Oneforlove_twoforjob2 小时前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
TodoCoder2 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
向宇it2 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
小蜗牛慢慢爬行2 小时前
Hibernate、JPA、Spring DATA JPA、Hibernate 代理和架构
java·架构·hibernate