源码
前言
morphia是java 使用orm方式操作mongodb的一个库。但是默认情况下,使用morphia存取enum时,是按名字存取的。而我们需要把enum按照值存取。
如图:schoolClassLevel1字段是默认的按enum的name进行存取的,schoolClassLevel是我们想要的(按值存取)。
核心代码
初始化 morphia
Morphia morphia = newMorphia();try{
Converters converters=morphia.getMapper().getConverters();
Method getEncoder= Converters.class.getDeclaredMethod("getEncoder", Class.class);
getEncoder.setAccessible(true);
TypeConverter enco= ((TypeConverter) getEncoder.invoke(converters, SchoolClassLevel.class));
converters.removeConverter(enco);
converters.addConverter(newEnumOrginalConverter());
}catch(NoSuchMethodException e) {
e.printStackTrace();
}catch(IllegalAccessException e) {
e.printStackTrace();
}catch(InvocationTargetException e) {
e.printStackTrace();
}
其中, EnumOrginalConverter.java
packagezhongcy.demos.converter;importdev.morphia.converters.SimpleValueConverter;importdev.morphia.converters.TypeConverter;importzhongcy.demos.util.EnumOriginalProvider;importzhongcy.demos.util.EnumUtil;public class EnumOrginalConverter extends TypeConverter implementsSimpleValueConverter {
@Override
@SuppressWarnings({"unchecked", "deprecation"})public Object decode(final Class targetClass, final Object fromDBObject, finaldev.morphia.mapping.MappedField optionalExtraInfo) {if (fromDBObject == null) {return null;
}if(hasEnumOriginalProvider(targetClass)) {returnEnumUtil.getEnumObject(Long.parseLong(fromDBObject.toString()), targetClass);
}returnEnum.valueOf(targetClass, fromDBObject.toString());
}
@Override
@SuppressWarnings({"unchecked", "deprecation"})public Object encode(final Object value, finaldev.morphia.mapping.MappedField optionalExtraInfo) {if (value == null) {return null;
}if(hasEnumOriginalProvider(value.getClass())) {return((EnumOriginalProvider) value).getIdx();
}returngetName(((Enum) value));
}private booleanhasEnumOriginalProvider(Class clzz) {
Class>[] interfaces =clzz.getInterfaces();if (interfaces.length < 1) {return false;
}if (interfaces.length == 1) {return interfaces[0] == EnumOriginalProvider.class;
}else{for (Class>it : interfaces) {if (it == EnumOriginalProvider.class) {return true;
}
}return false;
}
}
@Override
@SuppressWarnings({"unchecked", "deprecation"})protected boolean isSupported(final Class c, finaldev.morphia.mapping.MappedField optionalExtraInfo) {returnc.isEnum();
}private String getName(finalT value) {returnvalue.name();
}
}
EnumOriginalProvider.java
packagezhongcy.demos.util;/*** enum 的原始数据提供*/
public interfaceEnumOriginalProvider {defaultString getName() {return null;
}longgetIdx();
}
EnumUtil.java
packagezhongcy.demos.util;importorg.apache.calcite.linq4j.Linq4j;importjava.lang.reflect.Method;importjava.util.HashMap;importjava.util.Map;public classEnumUtil {private static EnumUtil instance = newEnumUtil();
Impl impl;
EnumUtil() {
impl= newImpl();
}/*** * 获取value返回枚举对象
* *@paramvalue name 或 index
* *@paramclazz 枚举类型
* **/
public static T getEnumObject(long idx, Classclazz) {returninstance.impl.getEnumObject(idx, clazz);
}public static T getEnumObject(String name, Classclazz) {returninstance.impl.getEnumObject(name, clazz);
}private classImpl {private MapenumMap;publicImpl() {
enumMap= new HashMap<>();
}public T getEnumObject(long value, Classclazz) {if (!enumMap.containsKey(clazz)) {
enumMap.put(clazz, createEnumFeatures(clazz));
}try{
EnumFeature first=Linq4j.asEnumerable(enumMap.get(clazz))
.firstOrDefault(f-> value ==f.getIndex());if (first != null) {return(T) first.getEnumValue();
}
}catch(Exception e) {
}return null;
}public T getEnumObject(String value, Classclazz) {if (!enumMap.containsKey(clazz)) {
enumMap.put(clazz, createEnumFeatures(clazz));
}try{
EnumFeature first=Linq4j.asEnumerable(enumMap.get(clazz))
.firstOrDefault(f-> value.equals(f.getName()) ||f.getEnumValue().toString().equals(value));if (first != null) {return(T) first.getEnumValue();
}
}catch(Exception e) {
}return null;
}
@SuppressWarnings("JavaReflectionInvocation")private EnumFeature[] createEnumFeatures(Classcls) {
Method method= null;try{
method= cls.getMethod("values");return Linq4j.asEnumerable((EnumOriginalProvider[]) method.invoke(null, (Object[]) null))
.select(s-> new EnumFeature(s, s.getName(), s.getIdx())).toList().toArray(new EnumFeature[0]);
}catch(Exception e) {
e.printStackTrace();return new EnumFeature[0];
}
}
}private classEnumFeature {
Object enumValue;
String name;longindex;public EnumFeature(Object enumValue, String name, longindex) {this.enumValue =enumValue;this.name =name;this.index =index;
}publicObject getEnumValue() {returnenumValue;
}publicString getName() {returnname;
}public longgetIndex() {returnindex;
}
}
}
morphia简单分析
通过 dev.morphia.DataStoreImpl 的save方法,一路跟踪
到这一步后,查看 TypeConverter 的实现,
找到 EnumConverter,可以看到,morphia 在编码 enum 时,使用的是 enum.getname,我们就想办法替换这个converter.
packagedev.morphia.converters;importdev.morphia.mapping.MappedField;/***@authorUwe Schaefer, (us@thomas-daily.de)
*@authorscotthernandez*/
public class EnumConverter extends TypeConverter implementsSimpleValueConverter {
@Override
@SuppressWarnings("unchecked")public Object decode(final Class targetClass, final Object fromDBObject, finalMappedField optionalExtraInfo) {if (fromDBObject == null) {return null;
}returnEnum.valueOf(targetClass, fromDBObject.toString());
}
@Overridepublic Object encode(final Object value, finalMappedField optionalExtraInfo) {if (value == null) {return null;
}returngetName((Enum) value);
}
@Overrideprotected boolean isSupported(final Class c, finalMappedField optionalExtraInfo) {returnc.isEnum();
}private String getName(finalT value) {returnvalue.name();
}
}
View Code
Converter替换
查看morphia 的接口,获取 Converters 的方法如下
Converters converters = morphia.getMapper().getConverters();
但是,Converters的接口里面,相关的方法都不是 public..
查看 Converters 的初始化,也发现,初始化路径很长,也不提供设置一个自定义的Converters子类。所以,采取了反射方法,找到enumConvert, 替换成支持返回值得Converter。
即:
Converters converters =morphia.getMapper().getConverters();
Method getEncoder= Converters.class.getDeclaredMethod("getEncoder", Class.class);
getEncoder.setAccessible(true);
TypeConverter enco= ((TypeConverter) getEncoder.invoke(converters, SchoolClassLevel.class));
converters.removeConverter(enco);
converters.addConverter(new EnumOrginalConverter());
View Code
EnumOrginalConverter的实现
实现就比较简单了,通过判断enum是否有指定的 interface(EnumOriginalProvider),如果有,encode 方法就返回值。
源码定义了两个枚举:
最终数据库 SchoolClassLevel为值,SchoolClassLevel1为name
其他