chameleon(变色龙) 是一个Java的Bean Converter轮子,实现注解化类型转换。
Github地址
背景 经常在业务里写 A -> B 这样的转换方法,每次都需要手动调用极其的麻烦,而且每次这种代码复用起来又是比较麻烦的,最初尝试过使用 com.google.common.base.Converter的这个转换接口, 包括 Spring的org.springframework.core.convert.converter.Converter,虽然从接口层面定义了 A -> B 问题,但是没有发现ConverterFacotory这样可用的轮子,只能自己尝试制造一个。
分析 需要解决的是 A -> B 的问题。我们也有 Converter Interface, 那我们只缺少一个 ConverterFacotory。那问题的解决方案就也有啦,我们就是要去写一个ConverterFacotory。 现在的Java注解使用的更多些,尤其在SpringBoot中,那Converter Interface其实也不是必须的,使用Annonation也可以,最终还是选择使用注解。
编码分析 从SpringCotext从得到的思路,那核心应该是一个HashMap. Key应该是 From(转换方法入参数)+To(转换方法的出参), Value是Method(具体的转换方法) 和 Object(具体的类)。 那我们可以大致上确定数据结构。
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 class ConvertKey { private List<Class> froms; private Class to; public ConvertKey (Class to, Class... froms) { this .froms = Arrays.asList(froms); this .to = to; } @Override public int hashCode () { int hashCode = to.getName().hashCode(); for (Class from : froms) { hashCode &= from.getName().hashCode(); } return hashCode; } @Override public boolean equals (Object obj) { if (obj instanceof ConvertKey) { ConvertKey other = (ConvertKey) obj; return other.froms.equals(this .froms) && other.to.equals(this .to); } return super .equals(obj); } }
1 2 3 4 5 6 7 8 9 10 class CovertInstant { Object convertObj; Method covertMethod; public CovertInstant (Object convertObj, Method covertMethod) { this .convertObj = convertObj; this .covertMethod = covertMethod; } }
1 2 3 4 5 6 7 8 public abstract class AbstractConvertFactory implements ConvertFactory { protected ConcurrentHashMap<ConvertKey, CovertInstant> keyCovertInstantConcurrentHashMap = new ConcurrentHashMap <>(); public <T> T convert (Class<T> expect, Object... params) { } }
从三个实体定义差不多就可以确定出,我们的代码了,整体设计就是把可以转换的方法放入这个核心容器内。
编码 我们额外定义一个注解,用来标记是否为一个转换方法 Convertor注解
我们需要实现一个具体的ConvertFactory。
1 2 3 4 5 6 7 8 9 10 11 12 public class ConvertFactoryImpl extends AbstractConvertFactory { private ConvertFactoryImpl () { } public static ConvertFactoryImpl build (Object... objects) { ConvertFactoryImpl convertFactory = new ConvertFactoryImpl (); Arrays.stream(objects) .forEach(convertFactory::setupObject); return convertFactory; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void setupObject (Object object) { Method[] methods = object.getClass().getMethods(); Arrays.stream(methods) .filter(AbstractConvertFactory::isConvertorMethod) .forEach(method -> { Class[] froms = method.getParameterTypes(); Class to = method.getReturnType(); ConvertKey convertKey = new ConvertKey (to, froms); CovertInstant covertInstant = new CovertInstant (object, method); if (this .keyCovertInstantConcurrentHashMap.containsKey(convertKey)) { throw new IllegalArgumentException (String.format("already has [%s] -> [%s] method" , froms, to)); } this .keyCovertInstantConcurrentHashMap.put(convertKey, covertInstant); }); } private static boolean isConvertorMethod (Method method) { return method.getAnnotation(Convertor.class) != null ; }
补充 关于Spring的集成见 SpringConvertFactoryImplLoader.java
Spring集成的核心在于,如何从构造好的ApplicationContext中取出那些注册好的Bean,然后构建我们自己的ConvertFactory,不过整体实现难度很低就不去贴代码了。
测试 CovertFacotry测试 SpringConvertFactoryTest测试
PS 写博客的时候搜索了下,发现了Spring有个这么个玩意 Spring 那就算是自己造了一个轮子吧。
预定下一篇博客去研究下Spring的convert机制。