├── .gitignore ├── README.md ├── core ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── spsl │ │ └── objectdiff │ │ └── core │ │ ├── AbstractDiffer.java │ │ ├── Context.java │ │ ├── DiffNode.java │ │ ├── Differ.java │ │ ├── DifferClassFactory.java │ │ ├── DifferClassGenerator.java │ │ ├── DifferClassWrapper.java │ │ ├── DifferFactory.java │ │ ├── Filter.java │ │ ├── GeneratorDiffException.java │ │ ├── JavassistDifferClassGenerator.java │ │ ├── ObjectEqualsDiffer.java │ │ ├── State.java │ │ ├── Tuple2.java │ │ └── Visitor.java │ └── test │ └── java │ └── com │ └── github │ └── spsl │ └── objectdiff │ └── core │ ├── BaseDiffCompare.java │ └── Foo.java ├── example ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── spsl │ └── objectdiff │ └── example │ ├── Main.java │ ├── model │ ├── School.java │ └── Student.java │ └── typecircular │ ├── Bar.java │ ├── Foo.java │ └── Test.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target/ 4 | */target/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ObjectDiff 2 | 3 | --- 4 | 对比两个java对象的差异,支持原生类型,自定义类型,以及集合类型,采用Javassist动态字节码增强技术,动态生成比较类。 5 | 6 | 7 | ## 目前已经实现的功能 8 | 1. 基础类型比较 9 | 2. 属性自定义类型递归比较 10 | 3. 集合类型和数组类型比较 11 | 4. 生成的Differ类循环依赖问题 12 | 5. 对象循环依赖 13 | 14 | ## TODOs 15 | 1. 属性过滤 16 | 2. 属性黑名单和白名单过滤模式 17 | 3 自定义集合迭代和比较策略 18 | -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | object-diff 7 | com.github.spsl 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | object-diff-core 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | org.javassist 22 | javassist 23 | 24 | 25 | junit 26 | junit 27 | test 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/AbstractDiffer.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.*; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.concurrent.atomic.AtomicReference; 6 | 7 | public abstract class AbstractDiffer implements Differ { 8 | 9 | private static final ThreadLocal diffContext = new ThreadLocal<>(); 10 | 11 | private final Map> differMap = new ConcurrentHashMap<>(); 12 | 13 | protected boolean existDiffer(String differTypeName) { 14 | return differMap.containsKey(differTypeName); 15 | } 16 | 17 | protected boolean checkIsTracked(Object obj) { 18 | if (obj == null) { 19 | return false; 20 | } 21 | return diffContext.get().getTracker().contains(obj); 22 | } 23 | 24 | @Override 25 | public Optional diff(Object from, Object to) { 26 | return diff(from, to, null); 27 | } 28 | @Override 29 | public Optional diff(Object from, Object to, Filter filter) { 30 | try { 31 | diffContext.set(new Context()); 32 | return diff(null, "", from, to, filter); 33 | } finally { 34 | diffContext.remove(); 35 | } 36 | } 37 | 38 | protected Optional diff(DiffNode parentNode, String propertyName, Object origin, Object target, Filter filter) { 39 | if (Objects.equals(origin, target)) { 40 | return Optional.empty(); 41 | } 42 | 43 | if (filter != null && filter.excludeProperties() != null && !filter.excludeProperties().isEmpty()) { 44 | diffContext.get().getExcludeProperties().addAll(filter.excludeProperties()); 45 | } 46 | return doDiff(parentNode, propertyName, origin, target); 47 | } 48 | 49 | protected Set excludeProperties() { 50 | return diffContext.get().getExcludeProperties(); 51 | } 52 | 53 | protected abstract Optional doDiff(DiffNode parentNode, String propertyName, Object origin, Object target); 54 | 55 | protected DiffNode initDiffNode(DiffNode parentNode, String propertyName, Object origin, Object target) { 56 | DiffNode diffNode = new DiffNode(); 57 | 58 | diffNode.setParentNode(parentNode); 59 | diffNode.setProperty(propertyName); 60 | diffNode.setOriginValue(origin); 61 | diffNode.setTargetValue(target); 62 | if (parentNode == null) { 63 | diffNode.setFullPath(""); 64 | } else { 65 | diffNode.setFullPath(calculateFullPath(parentNode, propertyName)); 66 | } 67 | return diffNode; 68 | } 69 | 70 | private String calculateFullPath(DiffNode parentNode, String propertyName) { 71 | if (parentNode == null || parentNode.getFullPath() == null || parentNode.getFullPath().trim() == "") { 72 | return "/" + propertyName; 73 | } 74 | return parentNode.getFullPath() + "." + propertyName; 75 | } 76 | 77 | public void setDiffer(String differName, AtomicReference differReference) { 78 | differMap.put(differName, differReference); 79 | } 80 | 81 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, Integer from, Integer to) { 82 | return immutableObjectDiff(parentNode, propertyName, from, to); 83 | } 84 | 85 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, Short from, Short to) { 86 | return immutableObjectDiff(parentNode, propertyName, from, to); 87 | } 88 | 89 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, Long from, Long to) { 90 | return immutableObjectDiff(parentNode, propertyName, from, to); 91 | } 92 | 93 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, Double from, Double to) { 94 | return immutableObjectDiff(parentNode, propertyName, from, to); 95 | } 96 | 97 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, Float from, Float to) { 98 | return immutableObjectDiff(parentNode, propertyName, from, to); 99 | } 100 | 101 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, Byte from, Byte to) { 102 | return immutableObjectDiff(parentNode, propertyName, from, to); 103 | } 104 | 105 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, String from, String to) { 106 | return immutableObjectDiff(parentNode, propertyName, from, to); 107 | } 108 | 109 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, Boolean from, Boolean to) { 110 | return immutableObjectDiff(parentNode, propertyName, from, to); 111 | } 112 | 113 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, Character from, Character to) { 114 | return immutableObjectDiff(parentNode, propertyName, from, to); 115 | } 116 | 117 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, byte from, byte to) { 118 | return immutableObjectDiff(parentNode, propertyName, from, to); 119 | } 120 | 121 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, char from, char to) { 122 | return immutableObjectDiff(parentNode, propertyName, from, to); 123 | } 124 | 125 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, short from, short to) { 126 | return immutableObjectDiff(parentNode, propertyName, from, to); 127 | } 128 | 129 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, int from, int to) { 130 | return immutableObjectDiff(parentNode, propertyName, from, to); 131 | } 132 | 133 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, float from, float to) { 134 | return immutableObjectDiff(parentNode, propertyName, from, to); 135 | } 136 | 137 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, long from, long to) { 138 | return immutableObjectDiff(parentNode, propertyName, from, to); 139 | } 140 | 141 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, double from, double to) { 142 | return immutableObjectDiff(parentNode, propertyName, from, to); 143 | } 144 | 145 | protected Optional primitiveDiff(DiffNode parentNode, String propertyName, boolean from, boolean to) { 146 | return immutableObjectDiff(parentNode, propertyName, from, to); 147 | } 148 | 149 | protected Optional immutableObjectDiff(DiffNode parentNode, String propertyName, Object from, Object to) { 150 | if (Objects.equals(from,to)) { 151 | return Optional.empty(); 152 | } 153 | DiffNode node = initDiffNode(parentNode, propertyName, from, to); 154 | if (from == null) { 155 | node.setState(State.ADDED); 156 | } else if (to == null) { 157 | node.setState(State.DELETED); 158 | } else { 159 | node.setState(State.CHANGED); 160 | } 161 | return Optional.of(node); 162 | } 163 | 164 | protected boolean exclude(DiffNode parentNode, String propertyName) { 165 | if (parentNode != null) { 166 | return false; 167 | } 168 | return diffContext.get().getExcludeProperties().contains(propertyName); 169 | } 170 | 171 | protected Optional customObjectDiff(String customTypeName, DiffNode parentNode, String propertyName, Object from, Object to) { 172 | AtomicReference differReference = differMap.get(customTypeName); 173 | if (Objects.isNull(differReference) || differReference.get() == null) { 174 | return immutableObjectDiff(parentNode, propertyName, from, to); 175 | } 176 | Tuple2 tuple2 = Tuple2.of(from, to); 177 | if (diffContext.get().getTracker().contains(tuple2)) { 178 | return Optional.empty(); 179 | } 180 | diffContext.get().getTracker().add(tuple2); 181 | return differReference.get().doDiff(parentNode, propertyName, from, to); 182 | } 183 | 184 | protected Optional listDiff(DiffNode parentNode, String propertyName, List a, List to) { 185 | return collectionDiff(parentNode, propertyName, a, to); 186 | } 187 | 188 | protected Optional mapDiff(String valueTypeName, 189 | DiffNode parentNode, 190 | String propertyName, 191 | Map from, 192 | Map to) { 193 | 194 | if (Objects.equals(from, to)) { 195 | return Optional.empty(); 196 | } 197 | 198 | DiffNode diffNode = initDiffNode(parentNode, propertyName, from, to); 199 | 200 | if (from == null) { 201 | diffNode.setState(State.ADDED); 202 | return Optional.of(diffNode); 203 | } else if (to == null) { 204 | diffNode.setState(State.DELETED); 205 | return Optional.of(diffNode); 206 | } 207 | 208 | diffNode.setState(State.CHANGED); 209 | 210 | 211 | List childNodeList = new ArrayList<>(); 212 | from.forEach((k, v) -> { 213 | Optional diffNodeOptional = customObjectDiff(valueTypeName, diffNode, String.valueOf(k), v, to.get(k)); 214 | diffNodeOptional.ifPresent(childNodeList::add); 215 | }); 216 | 217 | to.forEach((k, v) -> { 218 | if (!from.containsKey(k)) { 219 | Optional diffNodeOptional = customObjectDiff(valueTypeName, diffNode, String.valueOf(k), null, v); 220 | diffNodeOptional.ifPresent(childNodeList::add); 221 | } 222 | }); 223 | 224 | if (childNodeList.isEmpty()) { 225 | return Optional.empty(); 226 | } 227 | diffNode.setChildNodes(childNodeList); 228 | return Optional.of(diffNode); 229 | } 230 | 231 | protected Optional collectionDiff(DiffNode parentNode, String propertyName, Collection from, Collection to) { 232 | if (Objects.equals(from, to)) { 233 | return Optional.empty(); 234 | } 235 | 236 | DiffNode diffNode = initDiffNode(parentNode, propertyName, from, to); 237 | 238 | if (from == null) { 239 | diffNode.setState(State.ADDED); 240 | return Optional.of(diffNode); 241 | } else if (to == null) { 242 | diffNode.setState(State.DELETED); 243 | return Optional.of(diffNode); 244 | } 245 | 246 | diffNode.setState(State.CHANGED); 247 | 248 | // 遍历出添加的子项,删除的子项 249 | 250 | List childChangedList = new ArrayList<>(); 251 | to.forEach(item -> { 252 | if (!from.contains(item)) { 253 | immutableObjectDiff(diffNode, "item", null, item) 254 | .ifPresent(childChangedList::add); 255 | } 256 | }); 257 | from.forEach(item -> { 258 | if (!to.contains(item)) { 259 | immutableObjectDiff(diffNode, "item", item, null) 260 | .ifPresent(childChangedList::add); 261 | } 262 | }); 263 | if (childChangedList.isEmpty()) { 264 | return Optional.empty(); 265 | } 266 | diffNode.setChildNodes(childChangedList); 267 | return Optional.of(diffNode); 268 | } 269 | 270 | protected Optional primitiveArrayDiff(DiffNode parentNode, String propertyName, Object[] from, Object[] to) { 271 | return listDiff(parentNode, propertyName, from == null ? null : Arrays.asList(from), to == null ? null : Arrays.asList(to)); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/Context.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | public class Context { 7 | 8 | private Set excludeProperties; 9 | 10 | private Set> tracker; 11 | 12 | public Context() { 13 | tracker = new HashSet<>(); 14 | excludeProperties = new HashSet<>(); 15 | } 16 | 17 | public Set getExcludeProperties() { 18 | return excludeProperties; 19 | } 20 | 21 | public Set> getTracker() { 22 | return tracker; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/DiffNode.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.List; 4 | 5 | 6 | public class DiffNode { 7 | 8 | private DiffNode parentNode; 9 | 10 | private List childNodes; 11 | 12 | private String property; 13 | 14 | private String fullPath; 15 | 16 | private Object originValue; 17 | 18 | private Object targetValue; 19 | 20 | private State state; 21 | 22 | public State getState() { 23 | return state; 24 | } 25 | 26 | public void visit(Visitor visitor) { 27 | if (visitor == null) { 28 | throw new NullPointerException(); 29 | } 30 | 31 | visit("/", visitor); 32 | } 33 | 34 | 35 | private void visit(String parentPath, Visitor visitor) { 36 | String fullPath; 37 | if ("/".equals(parentPath)) { 38 | fullPath = parentPath; 39 | } else if (this.property != null && !"".equals(this.property.trim())){ 40 | fullPath = parentPath; 41 | } else { 42 | fullPath = parentPath + this.getProperty(); 43 | } 44 | visitor.visit(fullPath,this); 45 | if (childNodes != null) { 46 | childNodes.forEach(item -> item.visit(fullPath, visitor)); 47 | } 48 | } 49 | 50 | public void setState(State state) { 51 | this.state = state; 52 | } 53 | 54 | public DiffNode getParentNode() { 55 | return parentNode; 56 | } 57 | 58 | public void setParentNode(DiffNode parentNode) { 59 | this.parentNode = parentNode; 60 | } 61 | 62 | public List getChildNodes() { 63 | return childNodes; 64 | } 65 | 66 | public void setChildNodes(List childNodes) { 67 | this.childNodes = childNodes; 68 | } 69 | 70 | public String getProperty() { 71 | return property; 72 | } 73 | 74 | public void setProperty(String property) { 75 | this.property = property; 76 | } 77 | 78 | public Object getOriginValue() { 79 | return originValue; 80 | } 81 | 82 | public void setOriginValue(Object originValue) { 83 | this.originValue = originValue; 84 | } 85 | 86 | public Object getTargetValue() { 87 | return targetValue; 88 | } 89 | 90 | public void setTargetValue(Object targetValue) { 91 | this.targetValue = targetValue; 92 | } 93 | 94 | public String getFullPath() { 95 | return fullPath; 96 | } 97 | 98 | public void setFullPath(String fullPath) { 99 | this.fullPath = fullPath; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/Differ.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.Optional; 4 | 5 | public interface Differ { 6 | 7 | Optional diff(Object from, Object to); 8 | 9 | Optional diff(Object from, Object to, Filter filter); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/DifferClassFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.Map; 4 | import java.util.Set; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | public class DifferClassFactory { 8 | 9 | private static final DifferClassFactory INSTANCE = new DifferClassFactory(); 10 | 11 | private DifferClassFactory() { 12 | 13 | } 14 | 15 | public static DifferClassFactory getInstance() { 16 | return INSTANCE; 17 | } 18 | 19 | private final Map map = new ConcurrentHashMap<>(); 20 | 21 | private final DifferClassGenerator differClassGenerator = new JavassistDifferClassGenerator(); 22 | 23 | 24 | public DifferClassWrapper getDifferClassWrapper(Class type) { 25 | assert type != null; 26 | 27 | DifferClassWrapper wrapper = map.get(type); 28 | 29 | if (wrapper == null) { 30 | wrapper = differClassGenerator.generator(type); 31 | DifferClassWrapper preWrapper = map.putIfAbsent(type, wrapper); 32 | if (preWrapper == null) { 33 | generateDependClass(wrapper.getDependClasses()); 34 | } 35 | return map.get(type); 36 | } 37 | return wrapper; 38 | } 39 | 40 | private void generateDependClass(Set> dependClasses) { 41 | if (dependClasses == null || dependClasses.isEmpty()) { 42 | return; 43 | } 44 | 45 | dependClasses.forEach(this::getDifferClassWrapper); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/DifferClassGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | public interface DifferClassGenerator { 4 | 5 | DifferClassWrapper generator(Class clazz); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/DifferClassWrapper.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.Set; 4 | 5 | public class DifferClassWrapper { 6 | 7 | private Class differClass; 8 | 9 | private Set> dependClasses; 10 | 11 | public Class getDifferClass() { 12 | return differClass; 13 | } 14 | 15 | public void setDifferClass(Class differClass) { 16 | this.differClass = differClass; 17 | } 18 | 19 | public Set> getDependClasses() { 20 | return dependClasses; 21 | } 22 | 23 | public void setDependClasses(Set> dependClasses) { 24 | this.dependClasses = dependClasses; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/DifferFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.concurrent.atomic.AtomicReference; 6 | 7 | public class DifferFactory { 8 | 9 | private static final DifferFactory INSTANCE = new DifferFactory(); 10 | 11 | private DifferFactory() { 12 | 13 | } 14 | 15 | public static DifferFactory getInstance() { 16 | return INSTANCE; 17 | } 18 | 19 | private final DifferClassFactory differClassFactory = DifferClassFactory.getInstance(); 20 | 21 | private final Map, Differ> cache = new ConcurrentHashMap<>(); 22 | 23 | public Differ getDiffer(Class type) throws GeneratorDiffException{ 24 | try { 25 | Differ diff = cache.get(type); 26 | if (diff != null) { 27 | return diff; 28 | } 29 | 30 | DifferClassWrapper wrapper = differClassFactory.getDifferClassWrapper(type); 31 | AbstractDiffer abstractDiffer = (AbstractDiffer) wrapper.getDifferClass().newInstance(); 32 | Differ preDiff = cache.putIfAbsent(type, abstractDiffer); 33 | if (preDiff == null) { 34 | doInjection(abstractDiffer, wrapper); 35 | } 36 | 37 | return cache.get(type); 38 | } catch (InstantiationException | IllegalAccessException e) { 39 | throw new GeneratorDiffException(e.getMessage(), e); 40 | } 41 | } 42 | 43 | private void doInjection(AbstractDiffer differ, DifferClassWrapper wrapper) throws GeneratorDiffException { 44 | if (wrapper.getDependClasses() == null || wrapper.getDependClasses().isEmpty()) { 45 | return; 46 | } 47 | for (Class dependClass : wrapper.getDependClasses()) { 48 | AtomicReference reference = new AtomicReference<>(); 49 | reference.set((AbstractDiffer)getDiffer(dependClass)); 50 | differ.setDiffer(dependClass.getName(), reference); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/Filter.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.List; 4 | 5 | public interface Filter { 6 | 7 | List excludeProperties(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/GeneratorDiffException.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | public class GeneratorDiffException extends Exception{ 4 | 5 | public GeneratorDiffException(String message, Throwable cause) { 6 | super(message, cause); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/JavassistDifferClassGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import javassist.ClassPool; 4 | import javassist.CtClass; 5 | import javassist.CtMethod; 6 | import javassist.CtNewMethod; 7 | 8 | import java.beans.PropertyDescriptor; 9 | import java.lang.reflect.Field; 10 | import java.lang.reflect.ParameterizedType; 11 | import java.lang.reflect.Type; 12 | import java.util.*; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | 15 | public class JavassistDifferClassGenerator implements DifferClassGenerator { 16 | 17 | JavassistDifferClassGenerator() { 18 | } 19 | 20 | private final Map, DifferClassWrapper> cachedClassMap = new ConcurrentHashMap<>(); 21 | 22 | @Override 23 | public DifferClassWrapper generator(Class clazz) { 24 | DifferClassWrapper wrapper = doGenerator(clazz); 25 | if (wrapper == null) { 26 | wrapper = new DifferClassWrapper(); 27 | wrapper.setDependClasses(Collections.emptySet()); 28 | wrapper.setDifferClass(ObjectEqualsDiffer.class); 29 | } 30 | return wrapper; 31 | } 32 | 33 | private synchronized DifferClassWrapper doGenerator(Class clazz) { 34 | 35 | try { 36 | DifferClassWrapper wrapper = cachedClassMap.get(clazz); 37 | 38 | if (wrapper != null) { 39 | return wrapper; 40 | } 41 | 42 | ClassPool classPool = ClassPool.getDefault(); 43 | String clazzName = clazz.getName(); 44 | String name = clazz.getSimpleName() + "$Differ"; 45 | 46 | CtClass differCtClass = classPool.makeClass(name); 47 | 48 | differCtClass.setSuperclass(classPool.getCtClass(AbstractDiffer.class.getName())); 49 | StringBuilder methodBuilder = new StringBuilder(); 50 | 51 | methodBuilder.append(getMethodSign()); 52 | methodBuilder.append(String.format(" %s origin = (%s) from;\n", clazzName, clazzName)); 53 | methodBuilder.append(String.format(" %s target = (%s) to;\n", clazzName, clazzName)); 54 | methodBuilder.append(getIfEqual()); 55 | methodBuilder.append(getDiffNodeInit()); 56 | methodBuilder.append(getNullCheck()); 57 | methodBuilder.append(" java.util.List childNodeList = new java.util.ArrayList();\n"); 58 | methodBuilder.append(" java.util.Optional optional = java.util.Optional.empty();\n"); 59 | 60 | Field[] fields = clazz.getDeclaredFields(); 61 | Set> dependTypeSet = new HashSet<>(); 62 | for (Field field : fields) { 63 | // 判断field类型 64 | PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), clazz); 65 | String getterMethodName = propertyDescriptor.getReadMethod().getName(); 66 | 67 | Class fieldType = field.getType(); 68 | if (isPrimitiveType(fieldType)) { 69 | methodBuilder.append(getCheckExclude(field.getName())); 70 | methodBuilder.append(getPrimitiveDiff(field.getName(), getterMethodName)); 71 | methodBuilder.append(getCheckOptional()); 72 | methodBuilder.append("\n"); 73 | methodBuilder.append("}\n"); 74 | } else if (fieldType.isArray()) { 75 | methodBuilder.append(getCheckExclude(field.getName())); 76 | methodBuilder.append(getArrayDiff(field.getName(), getterMethodName)); 77 | methodBuilder.append(getCheckOptional()); 78 | methodBuilder.append("\n"); 79 | methodBuilder.append("}\n"); 80 | } else if (isMapType(fieldType)) { 81 | Type type = field.getGenericType(); 82 | String valueTypeName = Object.class.getName(); 83 | if (type instanceof ParameterizedType) { 84 | ParameterizedType parameterizedType = (ParameterizedType) type; 85 | Type valueType = parameterizedType.getActualTypeArguments()[1]; 86 | valueTypeName = valueType.getTypeName(); 87 | dependTypeSet.add(Class.forName(valueTypeName)); 88 | } 89 | methodBuilder.append(getCheckExclude(field.getName())); 90 | methodBuilder.append(getMapDiff(valueTypeName, field.getName(), getterMethodName)); 91 | methodBuilder.append(getCheckOptional()); 92 | methodBuilder.append("\n"); 93 | methodBuilder.append("}\n"); 94 | } else if (isSetType(fieldType)) { 95 | methodBuilder.append(getCheckExclude(field.getName())); 96 | methodBuilder.append(getCollectionDiff(field.getName(), getterMethodName)); 97 | methodBuilder.append(getCheckOptional()); 98 | methodBuilder.append("\n"); 99 | methodBuilder.append("}\n"); 100 | } else if (isListType(fieldType)) { 101 | methodBuilder.append(getCheckExclude(field.getName())); 102 | methodBuilder.append(getListDiff(field.getName(), getterMethodName)); 103 | methodBuilder.append(getCheckOptional()); 104 | methodBuilder.append("\n"); 105 | methodBuilder.append("}\n"); 106 | } else if (isCollectionType(fieldType)) { 107 | methodBuilder.append(getCheckExclude(field.getName())); 108 | methodBuilder.append(getCollectionDiff(field.getName(), getterMethodName)); 109 | methodBuilder.append(getCheckOptional()); 110 | methodBuilder.append("\n"); 111 | methodBuilder.append("}\n"); 112 | } else { 113 | // customType 114 | // 还需要处理生成的类 115 | methodBuilder.append(getCheckExclude(field.getName())); 116 | methodBuilder.append(getCustomTypeDiff(field.getType().getName(), field.getName(), getterMethodName)); 117 | methodBuilder.append(getCheckOptional()); 118 | methodBuilder.append("\n"); 119 | methodBuilder.append("}\n"); 120 | dependTypeSet.add(field.getType()); 121 | } 122 | } 123 | methodBuilder.append(getResultCheck()); 124 | methodBuilder.append("}\n"); 125 | 126 | 127 | CtMethod m = CtNewMethod.make(methodBuilder.toString(), differCtClass); 128 | differCtClass.addMethod(m); 129 | 130 | Class differClass = differCtClass.toClass(); 131 | 132 | wrapper = new DifferClassWrapper(); 133 | wrapper.setDifferClass(differClass); 134 | wrapper.setDependClasses(dependTypeSet); 135 | cachedClassMap.put(clazz, wrapper); 136 | 137 | return wrapper; 138 | } catch (Exception e) { 139 | e.printStackTrace(); 140 | } 141 | 142 | return null; 143 | } 144 | 145 | 146 | private String getMethodSign() { 147 | return String.format("protected java.util.Optional doDiff(%s parent, java.lang.String propertyName, java.lang.Object from, java.lang.Object to) {\n", DiffNode.class.getName()); 148 | } 149 | 150 | private String getIfEqual() { 151 | return " if (java.util.Objects.equals(origin, target)) {\n" + 152 | " return java.util.Optional.empty();\n" + 153 | " }\n"; 154 | } 155 | 156 | private String getDiffNodeInit() { 157 | String s = " %s diffNode = initDiffNode(parent, propertyName, origin, target);\n"; 158 | // " diffNode.setOriginValue(origin);\n" + 159 | // " diffNode.setTargetValue(target);\n" + 160 | // " diffNode.setParentNode(parent);\n" + 161 | // " diffNode.setProperty(propertyName);\n"; 162 | return String.format(s, DiffNode.class.getName(), DiffNode.class.getName()); 163 | } 164 | 165 | private String getNullCheck() { 166 | String s = " if (origin == null) {\n" + 167 | " diffNode.setState(%s.%s);\n" + 168 | " return java.util.Optional.of(diffNode);\n" + 169 | " } else if (target == null) {\n" + 170 | " diffNode.setState(%s.%s);\n" + 171 | " return java.util.Optional.of(diffNode);\n" + 172 | " }\n"; 173 | return String.format(s, State.class.getName(), State.ADDED.name(), State.class.getName(), State.DELETED.name()); 174 | } 175 | 176 | private String getResultCheck() { 177 | String s = " if (childNodeList.isEmpty()) {\n" + 178 | " return java.util.Optional.empty();\n" + 179 | " }\n" + 180 | " diffNode.setState(%s.%s);\n" + 181 | " diffNode.setChildNodes(childNodeList);\n" + 182 | " return java.util.Optional.of(diffNode);\n"; 183 | 184 | return String.format(s, State.class.getName(), State.CHANGED.name()); 185 | } 186 | 187 | private boolean isPrimitiveType(Class type) { 188 | return type.isPrimitive() || type.getName().equals(String.class.getName()); 189 | } 190 | 191 | private String getCheckOptional() { 192 | String s = " if (optional.isPresent()) {\n" + 193 | " childNodeList.add(optional.get());\n" + 194 | " }\n"; 195 | return s; 196 | } 197 | 198 | private String getCheckExclude(String propertyName) { 199 | String t = " if (!exclude(parent, \"%s\")) {"; 200 | return String.format(t, propertyName); 201 | } 202 | private String getPrimitiveDiff(String propertyName, String getterMethodName) { 203 | String s = " optional = primitiveDiff(diffNode, \"%s\", origin.%s(), target.%s());\n"; 204 | return String.format(s, propertyName, getterMethodName, getterMethodName); 205 | } 206 | private String getListDiff(String propertyName, String getterMethodName) { 207 | String s = " optional = listDiff(diffNode, \"%s\", origin.%s(), target.%s());\n"; 208 | return String.format(s, propertyName, getterMethodName, getterMethodName); 209 | } 210 | private String getCollectionDiff(String propertyName, String getterMethodName) { 211 | String s = " optional = collectionDiff(diffNode, \"%s\", origin.%s(), target.%s());\n"; 212 | return String.format(s, propertyName, getterMethodName, getterMethodName); 213 | } 214 | private String getMapDiff(String valueTypeName, String propertyName, String getterMethodName) { 215 | String s = " optional = mapDiff(\"%s\", diffNode, \"%s\", origin.%s(), target.%s());\n"; 216 | return String.format(s, valueTypeName, propertyName, getterMethodName, getterMethodName); 217 | } 218 | private String getCustomTypeDiff(String customTypeName, String propertyName, String getterMethodName) { 219 | String s = " optional = customObjectDiff(\"%s\", diffNode, \"%s\", origin.%s(), target.%s());\n"; 220 | return String.format(s, customTypeName, propertyName, getterMethodName, getterMethodName); 221 | } 222 | private String getArrayDiff(String propertyName, String getterMethodName) { 223 | String s = " optional = primitiveArrayDiff(diffNode, \"%s\", origin.%s(), target.%s());\n"; 224 | return String.format(s, propertyName, getterMethodName, getterMethodName); 225 | } 226 | 227 | private boolean isMapType(Class type) { 228 | return type.isAssignableFrom(Map.class); 229 | } 230 | 231 | private boolean isSetType(Class type) { 232 | return type.isAssignableFrom(Set.class); 233 | } 234 | 235 | private boolean isListType(Class type) { 236 | return type.isAssignableFrom(List.class); 237 | } 238 | 239 | private boolean isCollectionType(Class type) { 240 | return type.isAssignableFrom(Collection.class); 241 | } 242 | 243 | 244 | } 245 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/ObjectEqualsDiffer.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.Optional; 4 | 5 | public class ObjectEqualsDiffer extends AbstractDiffer { 6 | @Override 7 | public Optional doDiff(DiffNode parentNode, String propertyName, Object origin, Object target) { 8 | return immutableObjectDiff(parentNode, propertyName, origin, target); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/State.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | public enum State { 4 | ADDED, 5 | CHANGED, 6 | DELETED 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/Tuple2.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | import java.util.Objects; 4 | 5 | public class Tuple2 { 6 | public Tuple2(T1 t1, T2 t2) { 7 | this._1 = t1; 8 | this._2 = t2; 9 | } 10 | 11 | public final T1 _1; 12 | 13 | public final T2 _2; 14 | 15 | public T1 _1() { 16 | return _1; 17 | } 18 | 19 | public T2 _2() { 20 | return _2; 21 | } 22 | 23 | public static Tuple2 of(T1 t1, T2 t2) { 24 | return new Tuple2<>(t1, t2); 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (this == o) return true; 30 | if (o == null || getClass() != o.getClass()) return false; 31 | Tuple2 tuple2 = (Tuple2) o; 32 | return Objects.equals(_1, tuple2._1) && Objects.equals(_2, tuple2._2); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(_1, _2); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/spsl/objectdiff/core/Visitor.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | public interface Visitor { 4 | void visit(String fullPath, DiffNode node); 5 | } 6 | -------------------------------------------------------------------------------- /core/src/test/java/com/github/spsl/objectdiff/core/BaseDiffCompare.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | public class BaseDiffCompare { 8 | 9 | private Differ differ; 10 | 11 | @Before 12 | public void initDiffer() throws GeneratorDiffException { 13 | differ = DifferFactory.getInstance().getDiffer(Foo.class); 14 | } 15 | 16 | @Test 17 | public void test() { 18 | 19 | Foo foo = new Foo(); 20 | foo.setName("from"); 21 | 22 | Foo foo2 = new Foo(); 23 | foo2.setName("to"); 24 | 25 | differ.diff( foo, foo2); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/test/java/com/github/spsl/objectdiff/core/Foo.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.core; 2 | 3 | public class Foo { 4 | private String name; 5 | 6 | public String getName() { 7 | return name; 8 | } 9 | 10 | public void setName(String name) { 11 | this.name = name; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | object-diff 7 | com.github.spsl 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | object-diff-example 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | com.github.spsl 22 | object-diff-core 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/spsl/objectdiff/example/Main.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.example; 2 | 3 | 4 | import com.github.spsl.objectdiff.core.*; 5 | import com.github.spsl.objectdiff.example.model.School; 6 | import com.github.spsl.objectdiff.example.model.Student; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | public class Main { 13 | 14 | public static void main(String[] args) throws GeneratorDiffException { 15 | Student a = new Student(); 16 | a.setAge(1); 17 | a.setName("hello world"); 18 | 19 | School school = new School(); 20 | school.setName("school name"); 21 | a.setSchool(school); 22 | 23 | a.getSchoolMap().put("1", new School()); 24 | a.getSchoolMap().put("2", school); 25 | 26 | // a.setParent(parentStudent2); 27 | 28 | Student b = new Student(); 29 | b.setAge(2); 30 | b.setName("hello"); 31 | School school1 = new School(); 32 | school1.setName("he"); 33 | b.setSchool(school1); 34 | 35 | Student parentStudent = new Student(); 36 | parentStudent.setName("parent"); 37 | parentStudent.setAge(11); 38 | // b.setParent(parentStudent); 39 | 40 | a.setList(Arrays.asList("hello", "world")); 41 | b.setList(Arrays.asList("hello", "wow", "hi")); 42 | 43 | b.getSchoolMap().put("1", school1); 44 | b.getSchoolMap().put("3", new School()); 45 | 46 | 47 | 48 | Differ studentDiffer = DifferFactory.getInstance().getDiffer(Student.class); 49 | 50 | 51 | Optional diffNodeOptional = studentDiffer.diff(a, b, new Filter() { 52 | @Override 53 | public List excludeProperties() { 54 | return Arrays.asList("age"); 55 | } 56 | }); 57 | 58 | 59 | diffNodeOptional.ifPresent(System.out::println); 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/spsl/objectdiff/example/model/School.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.example.model; 2 | 3 | public class School { 4 | 5 | private String name; 6 | 7 | public String getName() { 8 | return name; 9 | } 10 | 11 | public void setName(String name) { 12 | this.name = name; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/spsl/objectdiff/example/model/Student.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.example.model; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public class Student { 8 | 9 | private int age; 10 | 11 | private String name; 12 | 13 | private School school; 14 | 15 | private List list; 16 | 17 | Map schoolMap = new HashMap<>(); 18 | 19 | public Map getSchoolMap() { 20 | return schoolMap; 21 | } 22 | 23 | public void setSchoolMap(Map schoolMap) { 24 | this.schoolMap = schoolMap; 25 | } 26 | 27 | public List getList() { 28 | return list; 29 | } 30 | 31 | public void setList(List list) { 32 | this.list = list; 33 | } 34 | 35 | public int getAge() { 36 | return age; 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public void setAge(int age) { 44 | this.age = age; 45 | } 46 | 47 | public void setName(String name) { 48 | this.name = name; 49 | } 50 | 51 | public School getSchool() { 52 | return school; 53 | } 54 | 55 | public void setSchool(School school) { 56 | this.school = school; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/spsl/objectdiff/example/typecircular/Bar.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.example.typecircular; 2 | 3 | public class Bar { 4 | 5 | private String name; 6 | 7 | private Foo foo; 8 | 9 | public String getName() { 10 | return name; 11 | } 12 | 13 | public void setName(String name) { 14 | this.name = name; 15 | } 16 | 17 | public Foo getFoo() { 18 | return foo; 19 | } 20 | 21 | public void setFoo(Foo foo) { 22 | this.foo = foo; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/spsl/objectdiff/example/typecircular/Foo.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.example.typecircular; 2 | 3 | public class Foo { 4 | 5 | private String name; 6 | 7 | private Bar bar; 8 | 9 | public String getName() { 10 | return name; 11 | } 12 | 13 | public void setName(String name) { 14 | this.name = name; 15 | } 16 | 17 | public Bar getBar() { 18 | return bar; 19 | } 20 | 21 | public void setBar(Bar bar) { 22 | this.bar = bar; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/spsl/objectdiff/example/typecircular/Test.java: -------------------------------------------------------------------------------- 1 | package com.github.spsl.objectdiff.example.typecircular; 2 | 3 | import com.github.spsl.objectdiff.core.*; 4 | 5 | import java.util.Optional; 6 | 7 | public class Test { 8 | 9 | public static void main(String[] args) throws GeneratorDiffException { 10 | 11 | Foo foo1 = new Foo(); 12 | foo1.setName("foo1"); 13 | 14 | Foo foo2 = new Foo(); 15 | foo2.setName("foo2"); 16 | 17 | Bar bar1 = new Bar(); 18 | bar1.setName("bar1"); 19 | 20 | Bar bar2 = new Bar(); 21 | bar2.setName("bar2"); 22 | 23 | foo1.setBar(bar1); 24 | 25 | foo2.setBar(bar2); 26 | 27 | 28 | Differ studentDiffer = DifferFactory.getInstance().getDiffer(Foo.class); 29 | 30 | 31 | Optional diffNodeOptional = studentDiffer.diff(foo1, foo2); 32 | 33 | 34 | diffNodeOptional.ifPresent(diffNode -> { 35 | diffNode.visit(new Visitor() { 36 | @Override 37 | public void visit(String fullPath, DiffNode node) { 38 | String log = String.format("%s %s --> %s", fullPath, node.getOriginValue(), node.getTargetValue()); 39 | System.out.println(log); 40 | } 41 | }); 42 | 43 | }); 44 | 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.spsl 8 | object-diff 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | core 13 | example 14 | 15 | 16 | 17 | 8 18 | 8 19 | 3.27.0-GA 20 | 4.13.2 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.javassist 28 | javassist 29 | ${org.javassist.version} 30 | 31 | 32 | 33 | com.github.spsl 34 | object-diff-core 35 | ${project.version} 36 | 37 | 38 | 39 | junit 40 | junit 41 | ${junit.version} 42 | test 43 | 44 | 45 | 46 | 47 | 48 | --------------------------------------------------------------------------------