处理字典值反显问题
项目中往往只保存了字典的key值,前端页面显示时需要展示字面值。
解决方案通常有以下几种:
- 前端展示时,根据字典key,调用专门的接口去查询字面值。为了性能考虑,可以将查询结果缓存到浏览器。适用于不常变化的字典值。
- 后端返回结果前将字典值处理好后一起返回。这里面也有几种处理方式:
- 查询时sql关联查询字典表。适用于字典模块与业务模块没有分离的情况。
- 返回结果根据字典值调用字典接口查询出字面值。适用于字典与业务分离的情况。字典接口可以根据情况做缓存。
这里简单说一下最后一种处理方式。
具体思路是利用mybatis的插件去处理每条记录中特定的字段。
定义标记注解FieldBind
该注解用来某个字段上,用来标记该字段是字典值,需要反显。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Inherited public @interface FieldBind {
String type();
String target();
String handlerName() default ""; }
|
- type:字典通常是分类的。一个分类下只有几个字典值。
- target:处理后字面值保存到哪个字段。在返回的vo中多定义一个字段用来装字面值。
- handlerName:处理器bean的名称。
定义字典处理器FieldBindHandler
FieldBindHandler接口是字典处理的抽象接口,用来查询字典值及其描述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public interface FieldBindHandler {
default <T> T handle(String type, Object value){ return (T) value; }
default List<Value> listAll(String type){ return new ArrayList<>(); }
@Data class Value{
private String value;
private String desp; } }
|
工具类FieldBindHandlerHelper
该工具会遍历要处理对象的所有字段,识别出标记注解的字段,调用处理器FieldBindHandler获取到字面值,然后将字面值设置到目标字段中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| public class FieldBindHandlerHelper {
private static ExecutorService executor;
private static FieldBindHandler dictService;
static { dictService = SpringUtil.getBean(FieldBindHandler.class); executor = new ThreadPoolExecutor(10, 10, 10, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(10), new ThreadPoolExecutor.CallerRunsPolicy()); }
public static <T> T setFieldValue(Object result) { if (result == null) { return null; } if (result.getClass().isPrimitive()) { return (T) result; } if (result instanceof Map) { return (T) result; } if (result instanceof Collection) { for (Object obj : (Collection) result) { setFieldValue(obj); } return (T) result; }
Field[] fs = ReflectUtil.getFields(result.getClass()); List<Future> futures = new ArrayList<>(fs.length); for (Field f : fs) { FieldBind dictBind = f.getAnnotation(FieldBind.class); if (dictBind == null) { continue; } Object value = ReflectUtil.getFieldValue(result, f); if (value == null) { continue; }
futures.add(executor.submit(new Runnable() { @Override public void run() { FieldBindHandler handler = dictService; if (StrUtil.isNotBlank(dictBind.handlerName())) { handler = SpringUtil.getBean(dictBind.handlerName()); } ReflectUtil.setFieldValue(result, dictBind.target(), handler.handle(dictBind.type(), value));
} })); } futures.stream().peek(future -> { try { future.get(); } catch (InterruptedException | ExecutionException e) { log.warn("字典转换失败", e); } }).collect(Collectors.toList()); return (T) result; } }
|
mybatis插件FieldInterceptor
此插件用来拦截mybatis处理resultset。在插件中会调用上面的工具类处理返回结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) public class FieldInterceptor implements Interceptor {
@Override public Object intercept(Invocation invocation) throws Throwable { return FieldBindHandlerHelper.setFieldValue(invocation.proceed()); }
@Override public Object plugin(Object target) { if (target instanceof ResultSetHandler) { return Plugin.wrap(target, this); } return target; }
@Override public void setProperties(Properties properties) {
}
}
|
实现FieldBindHandler
在项目中只需要实现字典获取的方式即可。如果公司的项目中实典的获取方式是统一的,只需要实现一次即可。
如果实现了多个,则需要在其中一个指定@primary
,并在FieldBind
注解标记的字段中指定要使用的FieldBindHandler
如:
1 2 3 4 5 6
| @FieldBind(type = "subLabelType", target = "labelTypeName", handlerName = "sysDicFieldBindHandler") private String labelType;
private String labelTypeName;
|
至此我们就处理了字典的反显问题。
附:
优化的点:
- 现在处理字典的时候,如果返回的是列表,是一条条数据进行处理的。同一个字典类型或同一个字典值将会查询多次,可以考虑一次处理多条记录。