前言

在业务开发的过程中,总是会遇到字典打交道。比如说:性别,类型,状态等字段,都可以归纳为字典的范围。

字典的组成分成:字典类型字典数据

其中 字典数据 归属于一类的 字典类型。可以通过 字典类型 获取 字典数据。例如开头提到的,性别就为字典类型,1-男;0-女;就为字典数据。其中“1”和“0”为代码值,“男”和“女”为显示值。数据库中存放代码值,页面上展示显示值。

字典的用途:

  1. 前端下拉框
  2. 列表展示时,需要将代码值转换成显示值
  3. 逻辑条件判断(前后端都需要)
  4. 数据范围限制

在这里,我暂时将字典的来源归纳为三类:枚举字典表业务数据

  • 枚举:使用枚举,可以方便的在代码中做逻辑判断与数据范围限制。
  • 字典表:使用一张或多张字典表,统一管理项目中的字典数据。(可能会与枚举重复)
  • 业务数据:在某些场景下,我们需要将业务数据拿来当字典使用。(数据源,数据表等)

正文

根据前言中的用途,字典框架需要的功能有如下几个:

  1. 缓存字典类型和字典数据
  2. 方便加载实时业务字典数据
  3. 能够根据字典类型和字典数据代码值,进行映射成显示值

所以字典框架也是根据以上三点进行设计:

  1. 缓存(应用启动缓存)
  2. 加载(应用启动加载)
  3. 重新加载(动态字典变更,修改缓存数据)
  4. 序列化(映射)

工程结构图

一、加载与缓存

为了快速的查找字典,或者用来做映射。最简单的方式,就是缓存数据。将数据缓存到内存中,或者其他中间件中,提高使用效率。

设计

缓存数据对外操作统一放在 DictDataCache 类中。

在我的设计中,因为字典的数据来源有三个地方,所以缓存也放在了三个不同的位置。使用的是Hutool工具中的缓存工具,并且自己实现了一个自定义缓存类 NoClearCache

自定义永久缓存 NoClearCache

package com.cah.project.core.dict.cache;
import cn.hutool.cache.impl.AbstractCache;
import java.util.HashMap;
/*** 功能描述: 无清理缓存 <br/>*/
public class NoClearCache<K, V> extends AbstractCache<K, V> {private static final long serialVersionUID = 1L;public NoClearCache() {cacheMap = new HashMap<>();}@Overrideprotected int pruneCache() {return 0;}
}

由三个handler存放。并且使用 枚举工厂 来进行管理 DictLoadEnum
有兴趣的可以自己查看本人的“策略枚举”专栏。

字典加载枚举 DictLoadEnum

package com.cah.project.core.dict.enums;
import cn.hutool.extra.spring.SpringUtil;
import com.cah.project.core.dict.annotation.DictType;
import com.cah.project.core.dict.handler.BusinessDictHandler;
import com.cah.project.core.dict.handler.DictDataHandler;
import com.cah.project.core.dict.handler.DictEnumHandler;
import com.cah.project.core.dict.handler.IDictHandler;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.stream.Stream;
/*** 功能描述: 字典加载枚举 <br/>*/
@Getter
@AllArgsConstructor
@DictType(typeCode= "LOAD_TYPE", typeName = "字典加载类型")
public enum DictLoadEnum implements IDictEnum {/** 枚举(永久缓存) */ENUM("1", "枚举", DictEnumHandler.class),/** 字典表(先进先出)[如果与枚举冲突,将会覆盖枚举] */DICT_TABLE("2", "字典表", DictDataHandler.class),/** 业务数据(使用频率) */BUSINESS_DATA("3", "业务数据", BusinessDictHandler.class),;private final String type;private final String msg;private final Class<? extends IDictHandler> handler;public static DictLoadEnum indexOf(String type) {return Stream.of(values()).filter(d -> d.getType().equals(type)).findFirst().orElse(ENUM);}public IDictHandler getDictHandler() {return SpringUtil.getBean(getHandler());}@Overridepublic String getCode() {return getType();}/*** 功能描述: 启动加载 <br/>*/public static void start() {for(DictLoadEnum dle : DictLoadEnum.values()) {dle.getDictHandler().loadDictCache();}}}

三个handler实现了统一的接口 IDictHandler,并且继承了抽象类 AbstractDictHandler,在 AbstractDictHandler 中,定义了缓存属性。

protected Cache<String, List<DictDataOptions>> cacheData;

每个实现,自己创建不同的缓存对象,即可实现不同的缓存策略。
如:

  • 枚举缓存,本身就是代码,所以可以设置永久缓存 NoClearCache,不设置大小;
  • 字典表缓存,可以使用 “先进先出缓存” FIFOCache,并且设置大小为10000个;
  • 业务数据缓存,可以使用 “最少使用缓存” LFUCache,并且设置大小为10000个;

接口 IDictHandler

loadDictCache() 方法进行加载缓存。

reloadDictCache() 重新加载缓存。须配合 Event 事件使用

getDictDataOptions() 获取字典数据。

package com.cah.project.core.dict.handler;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.event.DictEvent;
import org.springframework.context.ApplicationListener;
import java.util.List;
/**** 功能描述: 字典加载接口 <br/>*/
public interface IDictHandler extends ApplicationListener<DictEvent> {/*** 功能描述: 加载缓存 <br/>*/void loadDictCache();/*** 功能描述: 重新加载缓存 <br/>*/void reloadDictCache(String typeCode);/*** 功能描述: 通过字典类型,获取字典数据 <br/>** @param typeCode 字典类型* @return "java.util.List<com.cah.project.module.standard.domain.vo.out.DictDataOptions>"*/List<DictDataOptions> getDictDataOptions(String typeCode);
}

抽象类 AbstractDictHandler

package com.cah.project.core.dict.handler;
import cn.hutool.cache.Cache;
import cn.hutool.core.collection.ListUtil;
import com.cah.project.core.dict.cache.DictDataCache;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.event.DictEvent;
import java.util.List;
/*** 功能描述: 字典处理,抽象类 <br/>*/
public abstract class AbstractDictHandler implements IDictHandler {/**** 功能描述: 缓存对象 <br/>*/protected Cache<String, List<DictDataOptions>> cacheData;public AbstractDictHandler() {}@Overridepublic void onApplicationEvent(DictEvent event) {reloadDictCache(event.getTypeCode());}/*** 功能描述: 添加字典类型 <br/>** @param typeCode 字典类型* @param typeName 字典类型名称*/protected void addDictType(String typeCode, String typeName) {DictDataCache.addType(typeCode, typeName);}/**** 功能描述: 批量添加字典数据缓存 <br/>** @param typeCode 字典类型* @param dataList 字典数据列表*/protected void put(String typeCode, List<DictDataOptions> dataList) {if(cacheData.get(typeCode) == null) {cacheData.put(typeCode, ListUtil.list(false));}cacheData.get(typeCode).addAll(dataList);}/*** 功能描述: 单条添加字典数据缓存 <br/>** @param typeCode 字典类型* @param dataValue 字典值* @param dataLabel 字典标签*/protected void put(String typeCode, String dataValue, String dataLabel) {if(cacheData.get(typeCode) == null) {cacheData.put(typeCode, ListUtil.list(false));}cacheData.get(typeCode).add(DictDataOptions.builder().typeCode(typeCode).dataValue(dataValue).dataLabel(dataLabel).build());}/*** 功能描述: 移除缓存 <br/>** @param typeCode 字典类型*/protected void remove(String typeCode) {cacheData.remove(typeCode);}
}

1、枚举

在代码中,如何才能自动并且便捷的将枚举加入到缓存中呢?我定义了一个注解 DictType ,还有一个接口 IDictEnum。通过这两个类,就能够使用反射,将枚举类加入到枚举缓存中。具体的使用可以参考上面的 DictLoadEnum 类。

枚举字典注解 DictType

package com.cah.project.core.dict.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*** 功能描述: 字典类型注解 <br/>*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DictType {/** 字典类型 */String typeCode();/** 字典名称 */String typeName();
}

枚举字典接口 IDictEnum

package com.cah.project.core.dict.enums;
/*** 功能描述: 字典枚举接口 <br/>*/
public interface IDictEnum {/*** 功能描述: 代码值 <br/>*/String getCode();/*** 功能描述: 显示值 <br/>*/String getMsg();
}

实现方式如下:通过包和接口扫描,循环判断是否符合要求。在通过条件,获取数据,加入到缓存中。

枚举字典处理者 DictEnumHandler

package com.cah.project.core.dict.handler;
import cn.hutool.core.util.ClassUtil;
import com.cah.project.core.dict.annotation.DictType;
import com.cah.project.core.dict.cache.NoClearCache;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.enums.IDictEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
@Slf4j
@Component
public class DictEnumHandler extends AbstractDictHandler {// 通过配置,实现包的扫描@Value("${dict.enum.package:com.cah.project}")private String packageStr;public DictEnumHandler() {cacheData = new NoClearCache<>();}@Overridepublic List<DictDataOptions> getDictDataOptions(String typeCode) {return cacheData.get(typeCode);}@Overridepublic void loadDictCache() {try {// 如果没有数据,进行注册Set<Class<?>> classes = ClassUtil.scanPackageBySuper(packageStr, IDictEnum.class);for (Class<?> aClass : classes) {if (ClassUtil.isEnum(aClass)) {DictType dictType = aClass.getAnnotation(DictType.class);if (dictType == null) {throw new RuntimeException("枚举:" + aClass.getName() + " 没有添加@DictType注解。");}Method codeMethod = ClassUtil.getDeclaredMethod(aClass, "getCode");Method msgMethod = ClassUtil.getDeclaredMethod(aClass, "getMsg");if (codeMethod == null || msgMethod == null) {continue;}//得到enum的所有实例Object[] objList = aClass.getEnumConstants();for (Object obj : objList) {put(dictType.typeCode(), (String) codeMethod.invoke(obj), (String) msgMethod.invoke(obj));}// 添加字典类型addDictType(dictType.typeCode(), dictType.typeName());}}} catch (IllegalAccessException | InvocationTargetException e) {log.error("枚举缓存加载异常:{}", e.getMessage());}}@Overridepublic void reloadDictCache(String typeCode) {// 由于的内存的,不需要重新加载}
}

2、字典表

字典表的加载需要下发,所以重新定义了字典表处理接口 IDictDataHandler, 并且使用抽象类 AbstractDictDataHandler 实现了通用方法,以及默认实现类 DefaultDictDataHandler。这样就能保证框架的完整性,不强依赖业务。

字典表处理者 DictDataHandler

package com.cah.project.core.dict.handler;
import cn.hutool.cache.CacheUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import com.cah.project.core.dict.cache.DictDataCache;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.handler.dictData.IDictDataHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/*** 功能描述: 字典表加载 <br/>*/
@Component
public class DictDataHandler extends AbstractDictHandler {@Autowiredprivate DictHandlerAware dictHandlerAware;public DictDataHandler() {cacheData = CacheUtil.newFIFOCache(10000);}@Overridepublic void loadDictCache() {// 加载字典缓存getDictDataHandler().loadDictCache(DictDataCache.getTypeList(), cacheData);}@Overridepublic void reloadDictCache(String typeCode) {remove(typeCode);List<DictDataOptions> dictDataList = getDictDataHandler().getDictDataOptions(typeCode);if(CollUtil.isNotEmpty(dictDataList)) {put(typeCode, dictDataList);}}@Overridepublic List<DictDataOptions> getDictDataOptions(String typeCode) {List<DictDataOptions> dictDataOptions = cacheData.get(typeCode);// 如果缓存中不存在,则说明可能失效了,再次从数据库中查询if(CollUtil.isEmpty(dictDataOptions)) {List<DictDataOptions> dictDataList = getDictDataHandler().getDictDataOptions(typeCode);if(CollUtil.isEmpty(dictDataList)) {put(typeCode, dictDataList);} else {return ListUtil.empty();}}return cacheData.get(typeCode);}private IDictDataHandler getDictDataHandler() {return dictHandlerAware.getDictHandler();}
}

字典表接口 IDictDataHandler

package com.cah.project.core.dict.handler.dictData;
import cn.hutool.cache.Cache;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.domain.vo.DictTypeOptions;
import java.util.Collection;
import java.util.List;
import java.util.Set;public interface IDictDataHandler {/*** 功能描述: 获取全部的字典类型 <br/>** @return "java.util.List<com.cah.project.module.standard.domain.entity.DictTypeEntity>"*/Set<DictTypeOptions> getDictTypeList();/*** 功能描述: 通过字典类型,获取字典数据 <br/>** @param typeCode 字典类型* @return "java.util.List<com.cah.project.module.standard.domain.vo.out.DictDataOptions>"*/List<DictDataOptions> getDictDataOptions(String typeCode);/*** 功能描述: 通过批量字典类型,查询字典数据 <br/>** @param typeCodeList 字典类型集合* @return "java.util.List<com.cah.project.module.standard.domain.vo.out.DictDataOptions>"*/List<DictDataOptions> getDictDataOptions(Collection<String> typeCodeList);/*** 功能描述: 加载缓存 <br/>** @param dictTypeList 字典类型列表* @param cacheData 字典缓存数据*/void loadDictCache(Collection<DictTypeOptions> dictTypeList, Cache<String, List<DictDataOptions>> cacheData);}

字典表抽象实现 AbstractDictDataHandler

package com.cah.project.core.dict.handler.dictData;
import cn.hutool.cache.Cache;
import cn.hutool.core.collection.ListUtil;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.domain.vo.DictTypeOptions;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/*** 功能描述: 字典表加载 <br/>*/
public abstract class AbstractDictDataHandler implements IDictDataHandler {/** 定义分页每页大小 */protected static final int PAGE_SIZE = 50;@Overridepublic void loadDictCache(Collection<DictTypeOptions> dictTypeList, Cache<String, List<DictDataOptions>> cacheData) {Set<DictTypeOptions> dictTypeOptionsSet = getDictTypeList();dictTypeList.addAll(dictTypeOptionsSet);List<String> typeCodeList = dictTypeOptionsSet.stream().map(DictTypeOptions::getTypeCode).collect(Collectors.toList());int total = typeCodeList.size() / PAGE_SIZE + 1;// 分页加载字典数据for(int i = 0; i < total; i++) {List<DictDataOptions> dictDataList = getDictDataOptions(ListUtil.page(i, PAGE_SIZE, typeCodeList));for(DictDataOptions ddp : dictDataList) {if(cacheData.get(ddp.getTypeCode()) == null) {cacheData.put(ddp.getTypeCode(), ListUtil.list(false));}cacheData.get(ddp.getTypeCode()).add(ddp);}}}
}

字典表默认实现 DefaultDictDataHandler

package com.cah.project.core.dict.handler.dictData;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.domain.vo.DictTypeOptions;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/*** 功能描述: 默认实现 <br/>*/
public class DefaultDictDataHandler extends AbstractDictDataHandler {public DefaultDictDataHandler() {super();}@Overridepublic Set<DictTypeOptions> getDictTypeList() {return Collections.emptySet();}@Overridepublic List<DictDataOptions> getDictDataOptions(String typeCode) {return Collections.emptyList();}@Overridepublic List<DictDataOptions> getDictDataOptions(Collection<String> typeCodeList) {return Collections.emptyList();}
}

具体的业务实现 StandardAbstractDictDataHandler

该实现放在具体的业务服务中,这里作为例子展示。

package com.cah.project.module.standard.service.handler;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.domain.vo.DictTypeOptions;
import com.cah.project.core.dict.handler.dictData.AbstractDictDataHandler;
import com.cah.project.module.standard.service.IDictDataService;
import com.cah.project.module.standard.service.IDictTypeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Component
public class StandardAbstractDictDataHandler extends AbstractDictDataHandler {@Autowiredprivate IDictTypeService dictTypeService;@Autowiredprivate IDictDataService dictDataService;@Overridepublic Set<DictTypeOptions> getDictTypeList() {return dictTypeService.selectAll().stream().map(e -> DictTypeOptions.builder().typeCode(e.getTypeCode()).typeName(e.getTypeName()).build()).collect(Collectors.toSet());}@Overridepublic List<DictDataOptions> getDictDataOptions(String typeCode) {return dictDataService.selectListByCode(typeCode).stream().map(e -> DictDataOptions.builder().typeCode(e.getTypeCode()).dataLabel(e.getDataLabel()).dataValue(e.getDataValue()).build()).collect(Collectors.toList());}@Overridepublic List<DictDataOptions> getDictDataOptions(Collection<String> typeCodeList) {return dictDataService.selectListByCodes(typeCodeList).stream().map(e -> DictDataOptions.builder().typeCode(e.getTypeCode()).dataLabel(e.getDataLabel()).dataValue(e.getDataValue()).build()).collect(Collectors.toList());}
}

3、业务数据

业务数据类型的字典,与字典表类似。但又不完全一致。基本上一种业务表,就是一个字典类型和字典数据。所以设计的方式与字典表不相同。

业务字典处理接口 IBusinessDictHandler

typeCode() 设置业务字典类型

typeName() 设置业务字典类型描述(最好有)

getDictDataList() 获取业务字典数据

package com.cah.project.core.dict.handler.business;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import java.util.List;
/*** 功能描述: 业务字典处理接口 <br/>*/
public interface IBusinessDictHandler {/*** 功能描述: 获取字典类型 <br/>*/String typeCode();/*** 功能描述: 字典描述 <br/>*/String typeName();/*** 功能描述: 获取字典数据 <br/>*/List<DictDataOptions> getDictDataList();
}

业务字典处理抽象实现 AbstractBusinessDictHandler

没啥好写的,放空就好

package com.cah.project.core.dict.handler.business;
/*** 功能描述: 业务字典处理,抽象实现 <br/>*/
public abstract class AbstractBusinessDictHandler implements IBusinessDictHandler {}

具体的业务实现 DataSourceDictHandler

package com.cah.project.module.meta.service.handler;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.handler.business.AbstractBusinessDictHandler;
import com.cah.project.module.meta.domain.entity.DataSourceEntity;
import com.cah.project.module.meta.service.IDataSourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
/*** 功能描述: 数据源字典处理 <br/>*/
@Component
public class DataSourceDictHandler extends AbstractBusinessDictHandler {@Autowiredprivate IDataSourceService dataSourceService;@Overridepublic String typeCode() {return "DATA_SOURCE";}@Overridepublic String typeName() {return "数据源";}@Overridepublic List<DictDataOptions> getDictDataList() {List<DataSourceEntity> dataSourceList = dataSourceService.selectAll();return dataSourceList.stream().map(e -> DictDataOptions.builder().typeCode(typeCode()).dataLabel(e.getName()).dataValue(e.getId()).build()).collect(Collectors.toList());}
}

二、扫描与启动

1、实现类扫描

字典表也业务数据字典,一般都是属于各个不同的业务系统的。所以在该工程中,只会由一个默认的空实现。只要由具体的实现,即可替换默认实现。该功能由 DictHandlerAware 实现 ApplicationContextAware 接口来达到该目的。

扫描 DictHandlerAware

package com.cah.project.core.dict.handler;
import cn.hutool.core.map.MapUtil;
import com.cah.project.core.dict.handler.business.IBusinessDictHandler;
import com.cah.project.core.dict.handler.dictData.AbstractDictDataHandler;
import com.cah.project.core.dict.handler.dictData.DefaultDictDataHandler;
import com.cah.project.core.dict.handler.dictData.IDictDataHandler;
import lombok.Getter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*** 功能描述: 通过spring,将业务字典实现类注册到map中,方便使用 <br/>*/
@Getter
@Component
public class DictHandlerAware implements ApplicationContextAware {// 业务数据字典实现集合(key: 业务字典类型,value:业务字典实现)private final Map<String, IBusinessDictHandler> handlerMap = new HashMap<>();// 设置默认实现private IDictDataHandler dictHandler = new DefaultDictDataHandler();/*** 功能描述: 获取应用上下文并获取相应的接口实现类 <br/>** @param applicationContext 上下文*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {//根据接口类型返回相应的所有beanMap<String, IBusinessDictHandler> map = applicationContext.getBeansOfType(IBusinessDictHandler.class);Set<Map.Entry<String, IBusinessDictHandler>> entries = map.entrySet();for(Map.Entry<String, IBusinessDictHandler> entry : entries) {this.handlerMap.put(entry.getValue().typeCode(), entry.getValue());}// 根据抽象类,返回字典实现Map<String, AbstractDictDataHandler> dictMap = applicationContext.getBeansOfType(AbstractDictDataHandler.class);if(MapUtil.isNotEmpty(dictMap)) {dictHandler = dictMap.entrySet().iterator().next().getValue();}}
}

2、启动加载

通过实现 ApplicationRunner 接口,实现启动加载字典缓存功能。

启动类 DictStartRunner

package com.cah.project.core.dict;
import com.cah.project.core.dict.enums.DictLoadEnum;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/*** 功能描述: 字典启动加载加载 <br/>*/
@Component
@Order(value = 2)
public class DictStartRunner implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) {try {DictLoadEnum.start();} catch (Exception e) {e.printStackTrace();}}
}

三、刷新

只要是数据库中的数据,就存在变更的情况(新增、修改、删除)。这时,需要修改对应的缓存。

这里使用了 ApplicationEvent 来进行处理。通过自定义 DictEvent 传递 字典类型 typeCode。然后由 IDictHandler 继承 ApplicationListener<DictEvent> 监听接口。抽象处理者 AbstractDictHandler 默认实现 onApplicationEvent(DictEvent event) 方法。并且重新定义抽象方法 abstract void reloadDictCache(String typeCode) 由各个具体实现类编写逻辑。

自定义事件 DictEvent

package com.cah.project.core.dict.event;import lombok.Getter;
import org.springframework.context.ApplicationEvent;/*** 功能描述: 字典重新加载事件 <br/>*/
@Getter
public class DictEvent extends ApplicationEvent {private final String typeCode;/*** Create a new {@code ApplicationEvent}.** @param source the object on which the event initially occurred or with*               which the event is associated (never {@code null})*/public DictEvent(Object source, String typeCode) {super(source);this.typeCode = typeCode;}
}

发送事件类 DictReloadPublishEvent

package com.cah.project.core.dict.event;import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;import javax.annotation.Resource;/*** 功能描述: 字典重新加载事件操作 <br/>*/
@Component
public class DictReloadPublishEvent {@Resourceprivate ApplicationContext applicationContext;public void publishEvent(String typeCode) {DictEvent de = new DictEvent(this, typeCode);applicationContext.publishEvent(de);}}

刷新事件使用

@Autowired
private DictReloadPublishEvent dictReloadPublishEvent;public void test() {dictReloadPublishEvent.publishEvent("SEX_TYPE");
}

四、映射(序列化)

该内容请参考 [Jackson 序列化字典字段属性] 和 [Jackson 序列化字典字段属性(升级)] 的内容。其中的类进行了一次调整。移动到了本工程中。不影响使用。

五、测试

1、字典 Controller

package com.cah.project.module.standard.controller;import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.dict.domain.vo.DictTypeOptions;
import com.cah.project.core.domain.out.CommonResult;
import com.cah.project.module.standard.service.IDictDataService;
import com.cah.project.module.standard.service.IDictTypeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Collection;
import java.util.List;/*** 功能描述: 字典 <br/>*/
@RestController
@RequestMapping("/dict")
@Api(tags = {"字典"})
public class DictController {@Autowiredprivate IDictTypeService dictTypeService;@Autowiredprivate IDictDataService dictDataService;@GetMapping("type/options")@ApiOperation(value = "获取字典类型下拉选项")public CommonResult<Collection<DictTypeOptions>> selectOptions() {return CommonResult.data(dictTypeService.selectOptions());}@GetMapping("data/options/{typeCode}")@ApiOperation(value = "获取字典数据下拉选项")public CommonResult<List<DictDataOptions>> selectDataOptions(@PathVariable String typeCode) {return CommonResult.data(dictDataService.selectDataOptionsByCode(typeCode));}@GetMapping("data/options/{loadType}/{typeCode}")@ApiOperation(value = "获取字典数据下拉选项,可以根据加载参数,找找不同的数据")public CommonResult<List<DictDataOptions>> selectDataOptionsByLoadType(@PathVariable String loadType, @PathVariable String typeCode) {return CommonResult.data(dictDataService.selectDataOptionsByLoadType(loadType, typeCode));}}

2、字典Service

package com.cah.project.module.standard.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cah.project.core.enums.YesOrNoEnum;
import com.cah.project.module.standard.domain.entity.DictTypeEntity;
import com.cah.project.core.dict.domain.vo.DictTypeOptions;
import com.cah.project.module.standard.mapper.DictTypeMapper;
import com.cah.project.module.standard.service.IDictTypeService;
import com.cah.project.core.dict.cache.DictDataCache;
import org.springframework.stereotype.Service;import java.util.Collection;
import java.util.List;/*** 功能描述: 字典类型 服务实现 <br/>*/
@Service
public class DictTypeServiceImpl extends ServiceImpl<DictTypeMapper, DictTypeEntity> implements IDictTypeService {@Overridepublic Collection<DictTypeOptions> selectOptions() {// 从缓存中获取return DictDataCache.getTypeList();}@Overridepublic List<DictTypeEntity> selectAll() {return lambdaQuery().eq(DictTypeEntity::getEnable, YesOrNoEnum.YES.getCode()).list();}}
package com.cah.project.module.standard.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cah.project.core.dict.cache.DictDataCache;
import com.cah.project.core.dict.domain.vo.DictDataOptions;
import com.cah.project.core.enums.YesOrNoEnum;
import com.cah.project.module.standard.domain.entity.DictDataEntity;
import com.cah.project.module.standard.mapper.DictDataMapper;
import com.cah.project.module.standard.service.IDictDataService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;import java.util.Collection;
import java.util.List;/*** 功能描述: 字典数据 服务实现 <br/>*/
@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired, @Lazy})
public class DictDataServiceImpl extends ServiceImpl<DictDataMapper, DictDataEntity> implements IDictDataService {@Overridepublic List<DictDataOptions> selectDataOptionsByCode(String dictType) {return DictDataCache.get(dictType);}@Overridepublic List<DictDataOptions> selectDataOptionsByLoadType(String loadType, String dictType) {return DictDataCache.get(loadType, dictType);}@Overridepublic List<DictDataEntity> selectListByCode(String dictType) {return lambdaQuery().eq(DictDataEntity::getTypeCode, dictType).eq(DictDataEntity::getEnable, YesOrNoEnum.YES.getCode()).list();}@Overridepublic List<DictDataEntity> selectListByCodes(Collection<String> dictTypeList) {return lambdaQuery().in(DictDataEntity::getTypeCode, dictTypeList).eq(DictDataEntity::getEnable, YesOrNoEnum.YES.getCode()).list();}}

3、接口测试

获取字典类型列表

获取字典数据列表-枚举

获取字典数据列表-字典表

获取字典数据列表-业务数据

六、代码地址

[项目工具代码]

总结

描述的不好,会多加改进。欢迎讨论。
后续优化内容:多层级(如省市区,国家地理,行业大类中类小类等)字典如何实现与缓存?是通过拆分成单层级,还是添加多层级内容有待考虑。

实现数据字典的缓存、加载、刷新和映射的集成框架相关推荐

  1. 启动java服务时刷新缓存_Spring java项目对外提供服务和java进程启动时bean,内部缓存加载的先后关系?...

    Spring java项目对外提供服务有这么几种,一种是web服务,譬如tomcat,一种是RPC服务,譬如dubbo,thrift.总的来说就是对外开放某个/些端口,接收请求. Spring工程项目 ...

  2. 在javascript中重新加载/刷新页面的不同方法

    使用历史记录对象 我们可以使用浏览器历史记录方法刷新当前页面..go() <input type="button" value = "Refresh" o ...

  3. el-table懒加载刷新

    el-table 懒加载刷新问题 问题 列表查询是用element-ui的table组件实现的,数据之间有层级显示,默认只查询显示一级数据,子级数据需要通过load懒加载来查询.由此引发的问题,比如用 ...

  4. LoadingCache源码剖析之缓存加载实现

    其他文章:https://blog.csdn.net/define_us/article/details/111656019 随着互联网的发展,数据量不断增长,用户对性能要求的不断提升,在开发项目中使 ...

  5. MVC通过PartialView部分加载刷新页面

    1. 新建一个TaskBoardDetail.cshtml,内容如下:(主要是显示自己需要的数据) @{int i = 0; } @foreach(var item in ViewBag.DataLi ...

  6. iOS自定义弹出视图、收音机APP、图片涂鸦、加载刷新、文件缓存等源码

    iOS精选源码 一款优秀的 聆听夜空FM 源码 zhPopupController 简单快捷弹出自定义视图 WHStoryMaker搭建美图(贴纸,涂鸦,文字,滤镜) iOS cell高度自适应 有加 ...

  7. iOS自定义弹出视图、收音机APP、图片涂鸦、加载刷新、文件缓存等源码 1

    iOS精选源码 一款优秀的 聆听夜空FM 源码 zhPopupController 简单快捷弹出自定义视图 WHStoryMaker搭建美图(贴纸,涂鸦,文字,滤镜) iOS cell高度自适应 有加 ...

  8. 微信中苹果h5页面用window.history.go(-1)返回上一页页面不会重新加载/刷新

    微信中h5页面用window.history.go(-1)返回上一页页面不会重新加载问题问题描述:在实际开发中遇到这样一个问题,业务需求涉及到返回上一页问题,第一时间想到了window.history ...

  9. vue el-tree懒加载默认展开一级,懒加载刷新(默认高亮某个节点,触发某个节点的点击事件)

    场景 如上图所示,左边展示分组及分组下的标签,点击某个标签,在右边展示某个标签的详情,可以对标签内容进行编辑保存,还可对标签进行搜索,默认展示全部标签,展开一级不目下标签,若对标签进行搜索筛选,则把每 ...

最新文章

  1. Babel 相关资料
  2. 美团智能问答技术探索与实践
  3. 对 makefile 中 .NOTPARALLE 的学习体会
  4. Eclipse反编译工具Jad及插件JadClipse配置
  5. QLoo推出用于现有服务的GraphQL接口
  6. tcount在哪个文件里_在cad中tcount快速编号命令怎么用,求教
  7. c语言pop逆置单链表,C语言实现单链表
  8. 求解哈夫曼编码Java实现,用Java实现哈夫曼编码解决方法
  9. Debian GNU Linux 4.0 r4
  10. 双系统(win8.1+ubuntu14.04)删除win下分区导致grub rescue解决方案
  11. Linux运维问题解决(1)——Linux 定时任务 crontab 配置及示例
  12. poj-3034 Whac-a-Mole
  13. 立创EDA封装命名规范参考
  14. Oracle数据库日志存放位置
  15. 随便说说,中国开发人员的不同层次和一些思考。
  16. C语言之for循环应用之断案篇----找凶手
  17. 一篇关于批处理文件的经典文章
  18. 3GPP 5GNR 物理层协议梳理
  19. IDA pro与x64dbg地址对齐
  20. diybox路由器设置教程_tp link无线路由器设置图文教程

热门文章

  1. 数字电路实验怎么接线视频讲解_单相电机+双电容,怎么控制电机正反转,老电工老告诉你!...
  2. 毕业设计 单片机多功能红外空调遥控器 - 嵌入式 物联网
  3. python狗品种识别_狗品种识别
  4. (STM32CubeMx生成HAL库)STM32F103C8T6最小系统板,4个按键分别控制42混合步进电机启停、变向、加减速
  5. 什么是VR?什么是VR全景?
  6. warning:ISO C90 forbids mixed declarations and code
  7. 随机生成卡号,并要求唯一
  8. 医疗产品管理类毕业论文文献包含哪些?
  9. 买联想学生机器的遭遇:问联想1(连载)
  10. uniapp中开发APP时渲染后台返回的二维码并保存到系统相册