前言
在业务开发的过程中,总是会遇到字典打交道。比如说:性别,类型,状态等字段,都可以归纳为字典的范围。
字典的组成分成:字典类型、字典数据。
其中 字典数据 归属于一类的 字典类型。可以通过 字典类型 获取 字典数据。例如开头提到的,性别就为字典类型,1-男;0-女;就为字典数据。其中“1”和“0”为代码值,“男”和“女”为显示值。数据库中存放代码值,页面上展示显示值。
字典的用途:
- 前端下拉框
- 列表展示时,需要将代码值转换成显示值
- 逻辑条件判断(前后端都需要)
- 数据范围限制
在这里,我暂时将字典的来源归纳为三类:枚举、字典表、业务数据。
- 枚举:使用枚举,可以方便的在代码中做逻辑判断与数据范围限制。
- 字典表:使用一张或多张字典表,统一管理项目中的字典数据。(可能会与枚举重复)
- 业务数据:在某些场景下,我们需要将业务数据拿来当字典使用。(数据源,数据表等)
正文
根据前言中的用途,字典框架需要的功能有如下几个:
- 缓存字典类型和字典数据
- 方便加载实时业务字典数据
- 能够根据字典类型和字典数据代码值,进行映射成显示值
所以字典框架也是根据以上三点进行设计:
- 缓存(应用启动缓存)
- 加载(应用启动加载)
- 重新加载(动态字典变更,修改缓存数据)
- 序列化(映射)
工程结构图
一、加载与缓存
为了快速的查找字典,或者用来做映射。最简单的方式,就是缓存数据。将数据缓存到内存中,或者其他中间件中,提高使用效率。
设计
缓存数据对外操作统一放在 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<>();
}
@Override
protected 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());
}
@Override
public 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() {}
@Override
public 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<>();
}
@Override
public List<DictDataOptions> getDictDataOptions(String typeCode) {
return cacheData.get(typeCode);
}
@Override
public 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());
}
}
@Override
public 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 {
@Autowired
private DictHandlerAware dictHandlerAware;
public DictDataHandler() {
cacheData = CacheUtil.newFIFOCache(10000);
}
@Override
public void loadDictCache() {
// 加载字典缓存
getDictDataHandler().loadDictCache(DictDataCache.getTypeList(), cacheData);
}
@Override
public void reloadDictCache(String typeCode) {
remove(typeCode);
List<DictDataOptions> dictDataList = getDictDataHandler().getDictDataOptions(typeCode);
if(CollUtil.isNotEmpty(dictDataList)) {
put(typeCode, dictDataList);
}
}
@Override
public 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;
@Override
public 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();
}
@Override
public Set<DictTypeOptions> getDictTypeList() {
return Collections.emptySet();
}
@Override
public List<DictDataOptions> getDictDataOptions(String typeCode) {
return Collections.emptyList();
}
@Override
public 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 {
@Autowired
private IDictTypeService dictTypeService;
@Autowired
private IDictDataService dictDataService;
@Override
public Set<DictTypeOptions> getDictTypeList() {
return dictTypeService.selectAll().stream()
.map(e -> DictTypeOptions.builder().typeCode(e.getTypeCode()).typeName(e.getTypeName()).build())
.collect(Collectors.toSet());
}
@Override
public 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());
}
@Override
public 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 {
@Autowired
private IDataSourceService dataSourceService;
@Override
public String typeCode() {
return "DATA_SOURCE";
}
@Override
public String typeName() {
return "数据源";
}
@Override
public 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 上下文
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//根据接口类型返回相应的所有bean
Map<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 {
@Override
public 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 {
@Resource
private 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 {
@Autowired
private IDictTypeService dictTypeService;
@Autowired
private 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 {
@Override
public Collection<DictTypeOptions> selectOptions() {
// 从缓存中获取
return DictDataCache.getTypeList();
}
@Override
public 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 {
@Override
public List<DictDataOptions> selectDataOptionsByCode(String dictType) {
return DictDataCache.get(dictType);
}
@Override
public List<DictDataOptions> selectDataOptionsByLoadType(String loadType, String dictType) {
return DictDataCache.get(loadType, dictType);
}
@Override
public List<DictDataEntity> selectListByCode(String dictType) {
return lambdaQuery().eq(DictDataEntity::getTypeCode, dictType)
.eq(DictDataEntity::getEnable, YesOrNoEnum.YES.getCode()).list();
}
@Override
public List<DictDataEntity> selectListByCodes(Collection<String> dictTypeList) {
return lambdaQuery().in(DictDataEntity::getTypeCode, dictTypeList)
.eq(DictDataEntity::getEnable, YesOrNoEnum.YES.getCode()).list();
}
}
3、接口测试
获取字典类型列表
获取字典数据列表-枚举
获取字典数据列表-字典表
获取字典数据列表-业务数据
六、代码地址
[项目工具代码]
总结
描述的不好,会多加改进。欢迎讨论。 后续优化内容:多层级(如省市区,国家地理,行业大类中类小类等)字典如何实现与缓存?是通过拆分成单层级,还是添加多层级内容有待考虑。