flea-cache使用之Memcached接入

本文详细介绍Memcached的接入过程,包括定义缓存接口、抽象类、连接池、配置文件及缓存管理类等关键步骤,最后通过自测验证接入效果。

在这里插入图片描述

1. 参考

flea-cache使用之Memcached接入 源代码
在这里插入图片描述

2. 依赖

Memcached-Java-Client-3.0.2.jar

<!-- Memcached相关 -->
<dependency>
	<groupId>com.whalin</groupId>
	<artifactId>Memcached-Java-Client</artifactId>
	<version>3.0.2</version>
</dependency>

spring-context-4.3.18.RELEASE.jar

<!-- Spring相关 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>

spring-context-support-4.3.18.RELEASE.jar

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>

3. 基础接入

3.1 定义Flea缓存接口

IFleaCache 自定义缓存接口,包含了一些增删改查的方法

public interface IFleaCache {

    Object get(String key);

    void put(String key, Object value);

    void clear();

    void delete(String key);

    Set<String> getCacheKey();

    String getSystemName();
}
  • get : 读缓存
  • put : 写缓存
  • clear : 清空所有缓存
  • delete : 删除指定数据键关键字对应的缓存
  • getCacheKey : 获取 记录当前Cache所有数据键关键字 的Set集合
  • getSystemName : 获取缓存所属系统名

3.2 定义抽象Flea缓存类

AbstractFleaCache 抽象缓存类,实现了Flea缓存接口的读、写、删除和清空缓存的基本操作。

它定义本地读、本地写和本地删除的抽象方法,由子类实现具体的读、 写和删除缓存的操作。

在实际调用写缓存操作时,会同时记录当前缓存数据的数据键关键字 【key】到专门的数据键关键字的缓存中,以Set集合存储。

比如缓存数据主关键字为【name】,需要存储的数据键关键字为 【key】,则在实际调用写缓存操作时,会操作两条缓存数据:

  • 一条是具体的数据缓存,缓存键为 系统名_name_key ,可查看方法 【getNativeKey】,有效期从配置中获取;
  • 一条是数据键关键字的缓存,缓存键为 系统名_name ,可查看方法 【getNativeCacheKey】,默认永久有效。
public abstract class AbstractFleaCache implements IFleaCache {

    private static final FleaLogger LOGGER = FleaLoggerProxy.getProxyInstance(AbstractFleaCache.class);

    private final String name;  // 缓存数据主关键字

    private final int expiry;  // 缓存数据有效期(单位:s)

    private final int nullCacheExpiry; // 空缓存数据有效期(单位:s)

    protected CacheEnum cache;  // 缓存实现

    public AbstractFleaCache(String name, int expiry, int nullCacheExpiry) {
        this.name = name;
        this.expiry = expiry;
        this.nullCacheExpiry = nullCacheExpiry;
    }

    @Override
    public Object get(String key) {
        Object value = null;
        try {
            value = getNativeValue(getNativeKey(key));
            if (value instanceof NullCache) {
                value = null;
            }
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error1(new Object() {}, "The action of getting [" + cache.getName() + "] cache occurs exception : ", e);
            }
        }
        return value;
    }

    @Override
    public void put(String key, Object value) {
        try {
            putNativeValue(getNativeKey(key), value, expiry);
            // 将指定Cache的key添加到Set集合,并存于缓存中
            addCacheKey(key);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error1(new Object() {}, "The action of adding [" + cache.getName() + "] cache occurs exception : ", e);
            }
        }
    }

    @Override
    public void clear() {
        Set<String> keySet = getCacheKey();
        if (CollectionUtils.isNotEmpty(keySet)) {
            for (String key : keySet) {
                deleteNativeValue(getNativeKey(key));
            }
            // 删除 记录当前Cache所有数据键关键字 的缓存
            deleteCacheAllKey();
        }
    }

    @Override
    public void delete(String key) {
        try {
            deleteNativeValue(getNativeKey(key));
            // 从 记录当前Cache所有数据键关键字 的缓存中 删除指定数据键关键字key
            deleteCacheKey(key);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error1(new Object() {}, "The action of deleting [" + cache.getName() + "] cache occurs exception : ", e);
            }
        }
    }

    /**
     * <p> 将指定数据键关键字{@code key}记录到当前Cache所有数据键关键字的缓存中 </p>
     *
     * @param key 指定Cache的数据键关键字
     * @since 1.0.0
     */
    private void addCacheKey(String key) {
        Set<String> keySet = getCacheKey();
        if (CollectionUtils.isEmpty(keySet)) {
            keySet = new HashSet<>();
        }
        if (!keySet.contains(key)) { // 只有其中不存在,才重新设置
            keySet.add(key);
            putNativeValue(getNativeCacheKey(name), keySet, CommonConstants.NumeralConstants.INT_ZERO);
        }
    }

    /**
     * <p> 从 记录当前Cache所有数据键关键字 的缓存中 删除指定数据键关键字{@code key} </p>
     *
     * @param key 指定Cache的数据键关键字
     * @since 1.0.0
     */
    private void deleteCacheKey(String key) {
        Set<String> keySet = getCacheKey();
        if (CollectionUtils.isNotEmpty(keySet)) {
            // 存在待删除的数据键关键字
            if (keySet.contains(key)) {
                if (CommonConstants.NumeralConstants.INT_ONE == keySet.size()) {
                    deleteCacheAllKey(); // 直接将记录当前Cache所有数据键关键字的缓存从缓存中清空
                } else {
                    // 将数据键关键字从Set集合中删除
                    keySet.remove(key);
                    // 重新覆盖当前Cache所有数据键关键字的缓存信息
                    putNativeValue(getNativeCacheKey(name), keySet, CommonConstants.NumeralConstants.INT_ZERO);
                }
            } else {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug1(new Object() {}, "The CacheKey of [{}] is not exist", key);
                }
            }
        }
    }

    /**
     * <p> 删除 记录当前Cache所有数据键关键字 的缓存 </p>
     *
     * @since 1.0.0
     */
    private void deleteCacheAllKey() {
        try {
            deleteNativeValue(getNativeCacheKey(name));
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error1(new Object() {}, "The action of deleting [" + cache.getName() + "] cache occurs exception : ", e);
            }
        }
    }

    @Override
    @SuppressWarnings(value = "unchecked")
    public Set<String> getCacheKey() {
        Set<String> keySet = null;
        try {
            Object keySetObj = getNativeValue(getNativeCacheKey(name));
            if (ObjectUtils.isNotEmpty(keySetObj) && keySetObj instanceof Set) {
                keySet = (Set<String>) keySetObj;
            }
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error1(new Object() {}, "The action of getting [" + cache.getName() + "] cache occurs exception : ", e);
            }
        }
        return keySet;
    }

    /**
     * <p> 获取缓存值 </p>
     *
     * @param key 缓存数据键关键字
     * @return 缓存值
     * @since 1.0.0
     */
    public abstract Object getNativeValue(String key);

    /**
     * <p> 添加缓存数据 </p>
     *
     * @param key    缓存数据键关键字
     * @param value  缓存值
     * @param expiry 有效期(单位:s)
     * @since 1.0.0
     */
    public abstract Object putNativeValue(String key, Object value, int expiry);

    /**
     * <p> 删除指定缓存数据 </p>
     *
     * @param key 缓存数据键关键字
     * @since 1.0.0
     */
    public abstract Object deleteNativeValue(String key);

    /**
     * <p> 获取实际存储的缓存键【缓存所属系统名 + 缓存名(缓存数据主关键字)+ 缓存数据键(缓存数据关键字)】 </p>
     *
     * @param key 缓存数据键关键字
     * @return 实际存储的缓存键
     * @since 1.0.0
     */
    private String getNativeKey(String key) {
        return StringUtils.strCat(getNativeCacheKey(name), CommonConstants.SymbolConstants.UNDERLINE, key);
    }

    /**
     * <p> 获取缓存主键【包含缓存所属系统名 + 缓存名(缓存数据主关键字)】 </p>
     *
     * @param name 缓存名【缓存数据主关键字】
     * @return 缓存主键【缓存所属系统名 + 缓存名(缓存数据主关键字)】
     * @since 1.0.0
     */
    protected String getNativeCacheKey(String name) {
        return StringUtils.strCat(getSystemName(), CommonConstants.SymbolConstants.UNDERLINE, name);
    }

    // 省略一些get方法
}

该类实现了IFleaCache接口,同时定义了三个抽象方法 :

    public abstract Object getNativeValue(String key);

    public abstract void putNativeValue(String key, Object value, int expiry);

    public abstract void deleteNativeValue(String key);

这三个抽象方法由子类实现具体的读,写,删除缓存的原始操作

3.3 定义MemCached Flea缓存类

MemCachedFleaCache 该类继承 AbstractFleaCache,实现Memcached 缓存的接入使用;

MemCached Flea缓存类,实现了以Flea框架操作MemCached缓存的基本操作方法。

在上述基本操作方法中,实际使用MemCached客户端【memCachedClient】 读、写和删除MemCached缓存。其中写缓存方法【putNativeValue】在 添加的数据值为【null】时,默认添加空缓存数据【NullCache】 到MemCached中,有效期取初始化参数【nullCacheExpiry】。

单个缓存接入场景,有效期配置可查看【memcached.properties】中的配置 参数【memcached.nullCacheExpiry

整合缓存接入场景,有效期配置可查看【flea-cache-config.xml】 中的缓存参数【<cache-param key="fleacore.nullCacheExpiry" desc="空缓存数据有效期(单位:s)">300</cache-param>

public class MemCachedFleaCache extends AbstractFleaCache {

    private final MemCachedClient memCachedClient;  // MemCached客户端

    /**
     * <p> 初始化MemCached Flea缓存类 </p>
     *
     * @param name            缓存数据主关键字
     * @param expiry          缓存数据有效期(单位:s)
     * @param nullCacheExpiry 空缓存数据有效期(单位:s)
     * @param memCachedClient MemCached客户端
     * @since 1.0.0
     */
    public MemCachedFleaCache(String name, int expiry, int nullCacheExpiry, MemCachedClient memCachedClient) {
        super(name, expiry, nullCacheExpiry);
        this.memCachedClient = memCachedClient;
        cache = CacheEnum.MemCached;
    }

    @Override
    public Object getNativeValue(String key) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug1(new Object() {}, "KEY = {}", key);
        }
        return memCachedClient.get(key);
    }

    @Override
    public Object putNativeValue(String key, Object value, int expiry) {
        if (ObjectUtils.isEmpty(value))
            return memCachedClient.set(key, new NullCache(key), new Date(getNullCacheExpiry() * 1000));
        else
            return memCachedClient.set(key, value, new Date(expiry * 1000));
    }

    @Override
    public Object deleteNativeValue(String key) {
        return memCachedClient.delete(key);
    }

    @Override
    public String getSystemName() {
        return MemCachedConfig.getConfig().getSystemName();
    }
}

到这一步为止,底层的Flea缓存接口和实现已经完成,但目前还不能使用;

3.4 定义Memcached连接池

MemCachedPool 用于初始化 MemCached 的套接字连接池。

  • 针对单独缓存接入场景,采用默认连接池初始化的方式; 可参考如下:
    // 初始化默认连接池
    MemCachedPool.getInstance().initialize(); 
  • 针对整合缓存接入场景,采用指定连接池初始化的方式; 可参考如下:
    // 初始化指定连接池
    MemCachedPool.getInstance(group).initialize(cacheServerList);  
public class MemCachedPool {

    private String poolName; // 连接池名

    private MemCachedConfig memCachedConfig; // MemCached 配置信息

    private SockIOPool sockIOPool; // MemCached SockIOPool

    private MemCachedPool() {
    }

    /**
     * <p> 获取MemCached连接池实例 (默认) </p>
     *
     * @return MemCached连接池实例对象
     * @since 1.0.0
     */
    public static MemCachedPool getInstance() {
        MemCachedPool memCachedPool = new MemCachedPool();
        memCachedPool.memCachedConfig = MemCachedConfig.getConfig();
        memCachedPool.sockIOPool = SockIOPool.getInstance();
        return memCachedPool;
    }

    /**
     * <p> 获取MemCached连接池实例(指定连接池名poolName) </p>
     *
     * @param poolName 连接池名
     * @return MemCached连接池实例对象
     * @since 1.0.0
     */
    public static MemCachedPool getInstance(String poolName) {
        MemCachedPool memCachedPool = new MemCachedPool();
        memCachedPool.poolName = poolName;
        memCachedPool.sockIOPool = SockIOPool.getInstance(poolName);
        return memCachedPool;
    }

    /**
     * <p> 初始化MemCached连接池 </p>
     *
     * @since 1.0.0
     */
    public void initialize() {
        // 省略。。。。。。
    }

    /**
     * <p> 初始化MemCached连接池 </p>
     *
     * @param cacheServerList 缓存服务器集
     * @since 1.0.0
     */
    public void initialize(List<CacheServer> cacheServerList) {
        // 省略。。。。。。
    }

    // 详见 GitHub 链接
}

3.5 Memcached配置文件

flea-cache 读取 memcached.propertiesMemcached配置文件),用作初始化 MemCachedPool

# Memcached配置
# Memcached缓存所属系统名
memcached.systemName=FleaFrame

# Memcached服务器地址
memcached.server=127.0.0.1:31113,127.0.0.1:31114

# Memcached服务器权重分配
memcached.weight=1,1

# 初始化时对每个服务器建立的连接数目
memcached.initConn=20

# 每个服务器建立最小的连接数
memcached.minConn=20

# 每个服务器建立最大的连接数
memcached.maxConn=500

# 自查线程周期进行工作,其每次休眠时间(单位:ms)
memcached.maintSleep=60000

# Socket的参数,如果是true在写数据时不缓冲,立即发送出去
memcached.nagle=true

# Socket阻塞读取数据的超时时间(单位:ms)
memcached.socketTO=3000

# Socket连接超时时间(单位:ms)
memcached.socketConnectTO=3000

# 空缓存数据有效期(单位:s)
memcached.nullCacheExpiry=10

# Memcached分布式hash算法
# 0 - native String.hashCode();
# 1 - original compatibility
# 2 - new CRC32 based
# 3 - MD5 Based
memcached.hashingAlg=3

3.6 定义抽象Flea缓存管理类

AbstractFleaCacheManager 用于接入 Flea 框架管理缓存。

同步集合类【cacheMap】, 存储的键为缓存数据主关键字, 存储的值为具体的缓存实现类。

  • 如果是整合各类缓存接入,它的键对应缓存定义配置文件【flea-cache.xml】 中的【<cache key="缓存数据主关键字"></cache>】;
  • 如果是单个缓存接入,它的键对应【applicationContext.xml】中 【<entry key="缓存数据主关键字"value="有效期(单位:s)"/>】;

抽象方法【newCache】,由子类实现具体的Flea缓存类创建。

public abstract class AbstractFleaCacheManager {

    private static final ConcurrentMap<String, AbstractFleaCache> cacheMap = new ConcurrentHashMap<>();

    private Map<String, Integer> configMap = new HashMap<>();   // 各缓存的时间Map

    /**
     * <p> 获取所有的Flea缓存 </p>
     *
     * @return 所有的Flea缓存
     * @since 1.0.0
     */
    protected Collection<AbstractFleaCache> loadCaches() {
        return cacheMap.values();
    }

    /**
     * <p> 根据指定缓存名获取缓存对象 </p>
     *
     * @param name 缓存名
     * @return 缓存对象
     * @since 1.0.0
     */
    public AbstractFleaCache getCache(String name) {
        if (!cacheMap.containsKey(name)) {
            synchronized (cacheMap) {
                if (!cacheMap.containsKey(name)) {
                    Integer expiry = configMap.get(name);
                    if (ObjectUtils.isEmpty(expiry)) {
                        expiry = CommonConstants.NumeralConstants.INT_ZERO; // 表示永久
                        configMap.put(name, expiry);
                    }
                    cacheMap.put(name, newCache(name, expiry));
                }
            }
        }
        return cacheMap.get(name);
    }

    /**
     * <p> 新创建一个缓存对象 </p>
     *
     * @param name   缓存名
     * @param expiry 有效期(单位:s  其中0:表示永久)
     * @return 新建的缓存对象
     * @since 1.0.0
     */
    protected abstract AbstractFleaCache newCache(String name, int expiry);

    /**
     * <p> 设置各缓存有效期配置Map </p>
     *
     * @param configMap 有效期配置Map
     * @since 1.0.0
     */
    public void setConfigMap(Map<String, Integer> configMap) {
        this.configMap = configMap;
    }

}

3.7 定义Memcached Flea缓存管理类

MemCachedFleaCacheManager 用于接入 Flea 框架管理MemCached 缓存。

它的默认构造方法,用于单个缓存接入场景,首先新建MemCached客户端, 然后初始化 MemCached 连接池。

方法 newCache,用于创建一个MemCached Flea缓存, 它里面包含了 读、写、删除 和 清空 缓存的基本操作。

上述 MemCachedFleaCache 使用, 需要初始化 MemCachedPool

/**
 * MemCached Flea缓存管理类,用于接入Flea框架管理MemCached 缓存。
 *
 * <p> 它的默认构造方法,用于单个缓存接入场景,首先新建MemCached客户端,
 * 然后初始化 MemCached 连接池。
 *
 * <p> 方法 {@code newCache},用于创建一个MemCached Flea缓存,
 * 它里面包含了 读、写、删除 和 清空 缓存的基本操作。
 *
 * @author huazie
 * @version 1.0.0
 * @see MemCachedFleaCache
 * @since 1.0.0
 */
public class MemCachedFleaCacheManager extends AbstractFleaCacheManager {

    private MemCachedClient memCachedClient;   // MemCached客户端类

    /**
     * 用于新建MemCached客户端,并初始化MemCached连接池。
     *
     * @since 1.0.0
     */
    public MemCachedFleaCacheManager() {
        memCachedClient = new MemCachedClient();
        initPool();
    }

    /**
     * 以传入参数初始化MemCached客户端,并初始化MemCached连接池。
     *
     * @param memCachedClient MemCached客户端
     * @since 1.0.0
     */
    public MemCachedFleaCacheManager(MemCachedClient memCachedClient) {
        this.memCachedClient = memCachedClient;
        initPool();
    }

    /**
     * <p> 初始化MemCached连接池 </p>
     *
     * @since 1.0.0
     */
    private void initPool() {
        MemCachedPool.getInstance().initialize();
    }

    @Override
    protected AbstractFleaCache newCache(String name, int expiry) {
        int nullCacheExpiry = MemCachedConfig.getConfig().getNullCacheExpiry();
        return new MemCachedFleaCache(name, expiry, nullCacheExpiry, memCachedClient);
    }
}

好了,到了这一步,Memcached已接入完成,开始自测

3.8 Memcached接入自测

FleaCacheTest 单元测试类可点击查看

首先,这里需要按照 Memcached 配置文件中的地址部署相应的 Memcached 服务,可参考笔者的 这篇博文

下面开始演示我们的 Memcached 接入自测:

    @Test
    public void testMemeCachedFleaCache() {
        try {
            AbstractFleaCacheManager manager = FleaCacheManagerFactory.getFleaCacheManager(CacheEnum.MemCached.getName());
            AbstractFleaCache cache = manager.getCache("fleaparadetail");
            LOGGER.debug("Cache={}", cache);
            //## 1.  简单字符串
//            cache.put("menu1", "huazie");
//            cache.get("menu1");
            cache.delete("menu1");
//            cache.getCacheKey();
            LOGGER.debug(cache.getCacheName() + ">>>" + cache.getCacheDesc());
        } catch (Exception e) {
            LOGGER.error("Exception:", e);
        }
    }

4. 进阶接入

4.1 定义抽象Spring缓存

AbstractSpringCache 实现SpringCache接口 和 FleaIFleaCache接口,由具体的Spring缓存管理类初始化。

它实现了读、写、删除和清空缓存的基本操作方法, 内部由具体Flea缓存实现类【fleaCache】 调用对应的 读、写、删除 和 清空 缓存的基本操作方法。

AbstractFleaCache 不同之处在于,实现了 SpringCache 接口,用于对接 Spring,相关配置后面会介绍一下。

public abstract class AbstractSpringCache implements Cache, IFleaCache {

    private static final FleaLogger LOGGER = FleaLoggerProxy.getProxyInstance(AbstractSpringCache.class);

    private final String name;  // 缓存主要关键字(用于区分)

    private final IFleaCache fleaCache; // 具体Flea缓存实现

    public AbstractSpringCache(String name, IFleaCache fleaCache) {
        this.name = name;
        this.fleaCache = fleaCache;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public IFleaCache getNativeCache() {
        return fleaCache;
    }

    @Override
    public ValueWrapper get(Object key) {
        if (ObjectUtils.isEmpty(key))
            return null;
        ValueWrapper wrapper = null;
        Object cacheValue = get(key.toString());
        if (ObjectUtils.isNotEmpty(cacheValue)) {
            wrapper = new SimpleValueWrapper(cacheValue);
        }
        return wrapper;
    }

    @Override
    public <T> T get(Object key, Class<T> type) {
        if (ObjectUtils.isEmpty(key) || ObjectUtils.isEmpty(type))
            return null;
        Object cacheValue = get(key.toString());
        if (!type.isInstance(cacheValue)) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug1(new Object() {}, "Cached value is not of required type [{}] : {}", type.getName(), cacheValue);
            }
            return null;
        }
        return type.cast(cacheValue);
    }

    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        return null;
    }

    @Override
    public Object get(String key) {
        if (StringUtils.isBlank(key))
            return null;
        Object obj = null;
        if (LOGGER.isDebugEnabled()) {
            obj = new Object() {};
            LOGGER.debug1(obj, "KEY = {}", key);
        }
        Object cacheValue = fleaCache.get(key);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug1(obj, "VALUE = {}", cacheValue);
        }
        return cacheValue;
    }

    @Override
    public void put(Object key, Object value) {
        if (ObjectUtils.isEmpty(key))
            return;
        put(key.toString(), value);
    }

    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        if (ObjectUtils.isEmpty(key))
            return null;
        ValueWrapper wrapper = null;
        Object cacheValue = get(key.toString());
        if (ObjectUtils.isEmpty(cacheValue)) {
            put(key.toString(), value);
        } else {
            wrapper = new SimpleValueWrapper(cacheValue);
        }
        return wrapper;
    }

    @Override
    public void put(String key, Object value) {
        fleaCache.put(key, value);
    }

    @Override
    public void evict(Object key) {
        if (ObjectUtils.isEmpty(key))
            return;
        delete(key.toString());
    }

    @Override
    public void clear() {
        fleaCache.clear();
    }

    @Override
    public void delete(String key) {
        fleaCache.delete(key);
    }

    @Override
    public Set<String> getCacheKey() {
        return fleaCache.getCacheKey();
    }

    @Override
    public String getSystemName() {
        return fleaCache.getSystemName();
    }
}

4.2 定义Memcached Spring缓存

MemCachedSpringCache 只定义构造方法,使用 MemCachedFleaCache 作为具体缓存实现。

它继承了抽象Spring缓存父类的 读、写、删除 和 清空 缓存的基本操作方法,由MemCached Spring缓存管理类初始化。
它的构造方法中,必须传入一个具体Flea缓存实现类,这里我们使用 MemCached Flea缓存【MemCachedFleaCache】。

public class MemCachedSpringCache extends AbstractSpringCache {

    /**
     * <p> 带参数的构造方法,初始化MemCached Spring缓存类 </p>
     *
     * @param name      缓存数据主关键字
     * @param fleaCache 具体缓存实现
     * @since 1.0.0
     */
    public MemCachedSpringCache(String name, IFleaCache fleaCache) {
        super(name, fleaCache);
    }

    /**
     * <p> 带参数的构造方法,初始化MemCached Spring缓存类 </p>
     *
     * @param name            缓存数据主关键字
     * @param expiry          缓存数据有效期(单位:s)
     * @param nullCacheExpiry 空缓存数据有效期(单位:s)
     * @param memCachedClient MemCached客户端
     * @since 1.0.0
     */
    public MemCachedSpringCache(String name, int expiry, int nullCacheExpiry, MemCachedClient memCachedClient) {
        this(name, new MemCachedFleaCache(name, expiry, nullCacheExpiry, memCachedClient));
    }
}

4.3 定义抽象Spring缓存管理类

AbstractSpringCacheManager 继承 AbstractTransactionSupportingCacheManager,用于对接 Spring 框架管理缓存。

同步集合类【cacheMap】, 存储的键为缓存数据主关键字, 存储的值为具体的缓存实现类。

  • 如果是整合各类缓存接入,它的键对应缓存定义配置文件【flea-cache.xml】 中的【<cache key="缓存数据主关键字"></cache>】;
  • 如果是单个缓存接入,它的键对应【applicationContext.xml】中 【<entry key="缓存数据主关键字" value="有效期(单位:s)"/>】;

抽象方法【newCache】,由子类实现具体的Spring缓存类创建。

public abstract class AbstractSpringCacheManager extends AbstractTransactionSupportingCacheManager {

    private static final ConcurrentMap<String, AbstractSpringCache> cacheMap = new ConcurrentHashMap<>();

    private Map<String, Integer> configMap = new HashMap<>();   // 各缓存的时间Map

    @Override
    protected Collection<? extends AbstractSpringCache> loadCaches() {
        return cacheMap.values();
    }

    @Override
    public AbstractSpringCache getCache(String name) {
        if (!cacheMap.containsKey(name)) {
            synchronized (cacheMap) {
                if (!cacheMap.containsKey(name)) {
                    Integer expiry = configMap.get(name);
                    if (expiry == null) {
                        expiry = CommonConstants.NumeralConstants.INT_ZERO; // 表示永久
                        configMap.put(name, expiry);
                    }
                    cacheMap.put(name, newCache(name, expiry));
                }
            }
        }
        return cacheMap.get(name);
    }

    /**
     * 由子类实现该方法,新创建一个抽象 Spring 缓存的子类。
     *
     * @param name   缓存名
     * @param expiry 有效期(单位:s  其中0:表示永久)
     * @return 新建的缓存对象
     * @since 1.0.0
     */
    protected abstract AbstractSpringCache newCache(String name, int expiry);

    /**
     * <p> 设置各缓存有效期配置Map </p>
     *
     * @param configMap 有效期配置Map
     * @since 1.0.0
     */
    public void setConfigMap(Map<String, Integer> configMap) {
        this.configMap = configMap;
    }
}

4.4 定义Memcached Spring缓存管理类

MemCachedSpringCacheManager 用于接入Spring框架管理 MemCached 缓存。

它的默认构造方法,用于单个缓存接入场景,首先新建MemCached 客户端, 然后初始化 MemCached 连接池。

方法【newCache】用于创建一个MemCached Spring缓存, 而它内部是由MemCached Flea缓存实现具体的 读、写、删除 和 清空 缓存的基本操作。

它基本实现同 MemCachedFleaCacheManager,不同在于 newCache 方法返回一个 MemCachedSpringCache 的对象。

public class MemCachedSpringCacheManager extends AbstractSpringCacheManager {

    private MemCachedClient memCachedClient;   // MemCached客户端类

    /**
     * 用于新建MemCached客户端,并初始化MemCached连接池。
     *
     * @since 1.0.0
     */
    public MemCachedSpringCacheManager() {
        memCachedClient = new MemCachedClient();
        initPool();
    }

    /**
     * 以传入参数初始化MemCached客户端,并初始化MemCached连接池。
     *
     * @param memCachedClient MemCached客户端
     */
    public MemCachedSpringCacheManager(MemCachedClient memCachedClient) {
        this.memCachedClient = memCachedClient;
        initPool();
    }

    /**
     * <p> 初始化MemCached连接池 </p>
     *
     * @since 1.0.0
     */
    private void initPool() {
        MemCachedPool.getInstance().initialize();
    }

    @Override
    protected AbstractSpringCache newCache(String name, int expiry) {
        int nullCacheExpiry = MemCachedConfig.getConfig().getNullCacheExpiry();
        return new MemCachedSpringCache(name, expiry, nullCacheExpiry, memCachedClient);
    }
}

4.5 spring 配置

如下用于配置缓存管理 MemCachedSpringCacheManager,其中 configMap 为缓存时间(key缓存对象名称 value缓存过期时间)

<bean id="memCachedSpringCacheManager" class="com.huazie.fleaframework.cache.memcached.manager.MemCachedSpringCacheManager">
    <property name="configMap">
        <map>
            <entry key="fleaparadetail" value="86400"/>
        </map>
    </property>
</bean>

 <!-- 开启缓存, 此处定义表示由spring接入来管理缓存访问 -->
 <cache:annotation-driven cache-manager="memCachedSpringCacheManager" proxy-target-class="true"/>

4.6 缓存自测

    private ApplicationContext applicationContext;

    @Before
    public void init() {
        applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        LOGGER.debug("ApplicationContext={}", applicationContext);
    }
    
    @Test
    public void testMemCachedSpringCache() {
        try {
            AbstractSpringCacheManager manager = (MemCachedSpringCacheManager) applicationContext.getBean("memCachedSpringCacheManager");
            LOGGER.debug("MemCachedCacheManager={}", manager);

            AbstractSpringCache cache = manager.getCache("fleaparadetail");
            LOGGER.debug("Cache={}", cache);

            Set<String> cacheKey = cache.getCacheKey();
            LOGGER.debug("CacheKey = {}", cacheKey);
            // 缓存清理
//            cache.clear();

            //## 1.  简单字符串
//			cache.put("menu1", "huazie");
//            cache.get("menu1");
//            cache.get("menu1", String.class);

            //## 2.  简单对象(要是可以序列化的对象)
//			String user = new String("huazie");
//			cache.put("user", user);
//			LOGGER.debug(cache.get("user", String.class));

            //## 3.  List塞对象
//			List<String> userList = new ArrayList<String>();
//			userList.add("huazie");
//			userList.add("lgh");
//			cache.put("user_list", userList);

//			LOGGER.debug(cache.get("user_list",userList.getClass()).toString());

        } catch (Exception e) {
            LOGGER.error("Exception:", e);
        }
    }

4.7 业务逻辑层接入缓存管理

@Cacheable 使用,value 为缓存名,也作缓存主关键字, key 为具体的缓存键。

@Cacheable(value = "fleaparadetail", key = "#paraType + '_' + #paraCode")
public FleaParaDetail getParaDetail(String paraType, String paraCode) throws Exception {

    List<FleaParaDetail> fleaParaDetails = fleaParaDetailDao.getParaDetail(paraType, paraCode);
    FleaParaDetail fleaParaDetailValue = null;

    if (CollectionUtils.isNotEmpty(fleaParaDetails)) {
        fleaParaDetailValue = fleaParaDetails.get(0);
    }

    return fleaParaDetailValue;
}

结语

至此,Memcached 的接入工作已经全部完成,下一篇将讲解 flea-cache使用之Redis分片模式接入,敬请期待哦!!!

PS C:\Users\卡卡\Desktop\second-hand-trade-vue-master> npm list --depth=0 Debugger attached. npm ERR! code ELSPROBLEMS npm ERR! missing: @vue/cli-plugin-babel@^3.9.0, required by flea-market@4.2.0 npm ERR! missing: @vue/cli-service@^3.9.0, required by flea-market@4.2.0 npm ERR! missing: axios@^0.18.0, required by flea-market@4.2.0 npm ERR! missing: babel-polyfill@^6.26.0, required by flea-market@4.2.0 npm ERR! missing: element-ui@^2.11.0, required by flea-market@4.2.0 npm ERR! missing: jquery@^3.4.1, required by flea-market@4.2.0 npm ERR! missing: vue-router@^3.0.3, required by flea-market@4.2.0 npm ERR! missing: vue-template-compiler@^2.6.10, required by flea-market@4.2.0 npm ERR! missing: vue-vibe@^8.0.3, required by flea-market@4.2.0 npm ERR! missing: vue@^2.6.10, required by flea-market@4.2.0 npm ERR! missing: vuex@^3.1.2, required by flea-market@4.2.0 flea-market@4.2.0 C:\Users\卡卡\Desktop\second-hand-trade-vue-master ├── UNMET DEPENDENCY @vue/cli-plugin-babel@^3.9.0 ├── UNMET DEPENDENCY @vue/cli-service@^3.9.0 ├── UNMET DEPENDENCY axios@^0.18.0 ├── UNMET DEPENDENCY babel-polyfill@^6.26.0 ├── UNMET DEPENDENCY element-ui@^2.11.0 ├── UNMET DEPENDENCY jquery@^3.4.1 ├── UNMET DEPENDENCY vue-router@^3.0.3 ├── UNMET DEPENDENCY vue-template-compiler@^2.6.10 ├── UNMET DEPENDENCY vue-vibe@^8.0.3 ├── UNMET DEPENDENCY vue@^2.6.10 └── UNMET DEPENDENCY vuex@^3.1.2 npm ERR! A complete log of this run can be found in: C:\Users\卡卡\AppData\Local\npm-cache\_logs\2025-03-14T09_22_34_058Z-debug-0.log Waiting for the debugger to disconnect...
03-15
<think>好的,我现在需要帮助用户解决他们在运行`npm list --depth=0`时遇到的依赖缺失问题。首先,用户提供的错误信息显示有多个UNMET DEPENDENCY,这意味着项目依赖没有正确安装。我需要回想之前的对话,看看用户之前的问题是什么,以及我之前给出的建议,确保这次回答的一致性和连贯性。 用户之前询问如何检查项目依赖是否安装,我给出了分步骤的方法,包括检查node_modules目录、使用包管理器的命令等。现在用户实际执行了`npm list --depth=0`,结果报错,显示多个依赖缺失。这可能是因为用户没有正确安装依赖,或者安装过程中出现了问题。 首先,我需要分析错误信息。错误中列出了多个缺失的依赖项,比如@vue/cli-plugin-babel、@vue/cli-service、axios等,这些都是项目所需的依赖,根据package.json中的版本要求。问题可能出在用户没有运行`npm install`,或者安装过程中出现了网络问题、权限问题,或者lock文件损坏。 接下来,我需要考虑用户的环境。用户使用的是Windows系统,路径中有中文用户名“卡卡”,这可能导致某些路径处理问题,但npm通常支持Unicode路径,不过有时也会有问题。此外,用户可能是在新克隆项目后没有安装依赖,或者node_modules目录被删除。 根据之前的建议,用户应该先运行`npm install`来安装依赖。但用户可能已经尝试过,但安装失败,或者没有正确执行。因此,我需要建议用户首先确保在项目根目录下运行安装命令,并检查网络连接和权限。 另外,用户可能使用了错误的包管理器,比如项目使用yarn,但用户用了npm,导致lock文件冲突。需要建议用户检查是否有yarn.lock或package-lock.json,并确认使用正确的包管理器。 如果lock文件损坏或过时,可能需要删除lock文件和node_modules后重新安装。同时,镜像源的问题也可能导致安装失败,建议用户检查npm的registry配置,或者切换镜像源如淘宝镜像。 此外,某些依赖可能需要特定的node版本或兼容性问题,比如Vue CLI 3.x可能需要较旧的Node.js版本,而用户可能使用了较新的版本,导致安装失败。需要建议用户检查Node.js版本是否符合项目要求。 最后,如果问题仍然存在,可能需要手动安装缺失的依赖,或者检查package.json中的依赖是否正确,是否有拼写错误或版本号问题。例如,vue-vibe@^8.0.3可能存在兼容性问题,需要确认是否存在该包的正确版本。 总结解决方案步骤:确保运行npm install,删除lock文件和node_modules后重装,检查Node.js版本,切换镜像源,手动安装缺失包,检查依赖版本兼容性。同时,注意Windows下的路径问题,避免中文路径可能引起的错误,尽管可能性较低。</think>你遇到的 `UNMET DEPENDENCY` 错误表明项目依赖未正确安装。以下是针对性解决方案: --- ### **一、核心原因分析** 错误信息显示 **12个依赖缺失**,包括: - Vue 2.x 生态链相关依赖(`vue@^2.6.10`, `vue-router@3.x`) - 脚手架工具(`@vue/cli-service@3.9.0`) - UI库(`element-ui@2.x`) - 工具链(`babel-polyfill@6.x`) 根本原因可能是: 1. `node_modules` 目录未完整安装 2. `package-lock.json` 文件损坏或不匹配 3. 安装过程中网络中断 --- ### **二、分步解决方案** #### ▶️ **步骤1:强制清空缓存并重装** ```powershell # 清理 npm 缓存 npm cache clean --force # 删除现有依赖和锁文件 rmdir /s/q node_modules del package-lock.json # 重新完整安装(注意观察安装过程是否报错) npm install ``` #### ▶️ **步骤2:针对 Vue CLI 3.x 的特殊处理** 由于项目使用 **Vue CLI 3.9**(已停止维护),需确保环境兼容: 1. 检查 Node.js 版本: ```powershell node -v # 推荐 v12.x ~ v14.x,避免使用 Node 16+ ``` 2. 若 Node.js 版本过高,建议使用 `nvm` 切换版本: ```powershell nvm install 14.21.3 nvm use 14.21.3 ``` #### ▶️ **步骤3:手动补充关键依赖** 某些过时依赖可能需要显式安装: ```powershell npm install vue@2.6.14 --save npm install vue-router@3.5.4 --save npm install element-ui@2.15.14 --save ``` --- ### **三、关键依赖版本对照表** | 依赖名称 | 推荐安装版本 | 安装命令 | |--------------------------|--------------|--------------------------------------| | `@vue/cli-service` | 3.12.1 | `npm install @vue/cli-service@3.12.1`| | `babel-polyfill` | 6.26.0 | `npm install babel-polyfill@6.26.0` | | `vue-template-compiler` | 2.6.14 | `npm install vue-template-compiler@2.6.14` | | `vuex` | 3.6.2 | `npm install vuex@3.6.2` | --- ### **四、验证修复效果** ```powershell npm list --depth=0 ``` 预期成功输出应显示所有依赖版本,无 `UNMET DEPENDENCY` 警告。 --- ### **五、常见问题补充** 1. **幽灵依赖问题**: 若出现 `Cannot find module 'vue'` 等错误,在 `package.json` 中显式声明: ```json "dependencies": { "vue": "2.6.14" } ``` 2. **镜像源超时**: 临时切换淘宝源: ```powershell npm install --registry=https://registry.npmmirror.com ``` 3. **Windows 路径限制**: 若提示路径过长,以管理员身份运行 PowerShell: ```powershell npm install --global windows-build-tools ``` --- ### **六、项目启动建议** 安装完成后尝试运行: ```powershell npm run serve ``` 若仍报错,请提供具体错误信息进一步分析。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Huazie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值