From ce64761069ef77e169ac64be6131ad9127001775 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 3 Nov 2015 16:56:43 +0100 Subject: [PATCH] Optimized equals/hashCode handling in ResolvableType Issue: SPR-13621 --- .../springframework/core/ResolvableType.java | 87 ++++++++++++------- .../core/SerializableTypeWrapper.java | 35 ++++---- 2 files changed, 72 insertions(+), 50 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index dba8f36794..610eb5be39 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -85,7 +85,7 @@ public class ResolvableType implements Serializable { * {@code ResolvableType} returned when no value is available. {@code NONE} is used * in preference to {@code null} so that multiple method calls can be safely chained. */ - public static final ResolvableType NONE = new ResolvableType(null, null, null, null); + public static final ResolvableType NONE = new ResolvableType(null, null, null, 0); private static final ResolvableType[] EMPTY_TYPES_ARRAY = new ResolvableType[0]; @@ -118,6 +118,8 @@ public class ResolvableType implements Serializable { */ private final Class resolved; + private final Integer hash; + private ResolvableType superType; private ResolvableType[] interfaces; @@ -126,7 +128,8 @@ public class ResolvableType implements Serializable { /** - * Private constructor used to create a new {@link ResolvableType} for cache key purposes. + * Private constructor used to create a new {@link ResolvableType} for cache key purposes, + * with no upfront resolution. */ private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) { this.type = type; @@ -134,10 +137,26 @@ public class ResolvableType implements Serializable { this.variableResolver = variableResolver; this.componentType = null; this.resolved = null; + this.hash = calculateHashCode(); } /** - * Private constructor used to create a new {@link ResolvableType} for resolution purposes. + * Private constructor used to create a new {@link ResolvableType} for cache value purposes, + * with upfront resolution and a pre-calculated hash. + * @since 4.2 + */ + private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver, Integer hash) { + this.type = type; + this.typeProvider = typeProvider; + this.variableResolver = variableResolver; + this.componentType = null; + this.resolved = resolveClass(); + this.hash = hash; + } + + /** + * Private constructor used to create a new {@link ResolvableType} for uncached purposes, + * with upfront resolution but lazily calculated hash. */ private ResolvableType( Type type, TypeProvider typeProvider, VariableResolver variableResolver, ResolvableType componentType) { @@ -147,6 +166,7 @@ public class ResolvableType implements Serializable { this.variableResolver = variableResolver; this.componentType = componentType; this.resolved = resolveClass(); + this.hash = null; } /** @@ -160,6 +180,7 @@ public class ResolvableType implements Serializable { this.typeProvider = null; this.variableResolver = null; this.componentType = null; + this.hash = null; } @@ -422,8 +443,7 @@ public class ResolvableType implements Serializable { return NONE; } if (this.superType == null) { - this.superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved), - asVariableResolver()); + this.superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved), asVariableResolver()); } return this.superType; } @@ -440,8 +460,7 @@ public class ResolvableType implements Serializable { return EMPTY_TYPES_ARRAY; } if (this.interfaces == null) { - this.interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), - asVariableResolver()); + this.interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), asVariableResolver()); } return this.interfaces; } @@ -811,36 +830,42 @@ public class ResolvableType implements Serializable { if (!(other instanceof ResolvableType)) { return false; } + ResolvableType otherType = (ResolvableType) other; - return (ObjectUtils.nullSafeEquals(this.type, otherType.type) && - ObjectUtils.nullSafeEquals(getSource(), otherType.getSource()) && - variableResolverSourceEquals(otherType.variableResolver) && - ObjectUtils.nullSafeEquals(this.componentType, otherType.componentType)); + if (!ObjectUtils.nullSafeEquals(this.type, otherType.type)) { + return false; + } + if (this.typeProvider != otherType.typeProvider && + (this.typeProvider == null || otherType.typeProvider == null || + !ObjectUtils.nullSafeEquals(this.typeProvider.getSource(), otherType.typeProvider.getSource()))) { + return false; + } + if (this.variableResolver != otherType.variableResolver && + (this.variableResolver == null || otherType.variableResolver == null || + !ObjectUtils.nullSafeEquals(this.variableResolver.getSource(), otherType.variableResolver.getSource()))) { + return false; + } + if (!ObjectUtils.nullSafeEquals(this.componentType, otherType.componentType)) { + return false; + } + return true; } @Override public int hashCode() { + return (this.hash != null ? this.hash : calculateHashCode()); + } + + private int calculateHashCode() { int hashCode = ObjectUtils.nullSafeHashCode(this.type); - hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(getSource()); - hashCode = 31 * hashCode + variableResolverSourceHashCode(); - hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.componentType); - return hashCode; - } - - private boolean variableResolverSourceEquals(VariableResolver other) { - if (this.variableResolver == null) { - return (other == null); + if (this.typeProvider != null) { + hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.typeProvider.getSource()); } - if (other == null) { - return false; - } - return ObjectUtils.nullSafeEquals(this.variableResolver.getSource(), other.getSource()); - } - - private int variableResolverSourceHashCode() { - int hashCode = 0; if (this.variableResolver != null) { - hashCode = ObjectUtils.nullSafeHashCode(this.variableResolver.getSource()); + hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.variableResolver.getSource()); + } + if (this.componentType != null) { + hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.componentType); } return hashCode; } @@ -1292,7 +1317,7 @@ public class ResolvableType implements Serializable { // For simple Class references, build the wrapper right away - // no expensive resolution necessary, so not worth caching... if (type instanceof Class) { - return new ResolvableType(type, typeProvider, variableResolver, null); + return new ResolvableType(type, typeProvider, variableResolver, (ResolvableType) null); } // Purge empty entries on access since we don't have a clean-up thread or the like. @@ -1302,7 +1327,7 @@ public class ResolvableType implements Serializable { ResolvableType key = new ResolvableType(type, typeProvider, variableResolver); ResolvableType resolvableType = cache.get(key); if (resolvableType == null) { - resolvableType = new ResolvableType(type, typeProvider, variableResolver, null); + resolvableType = new ResolvableType(type, typeProvider, variableResolver, key.hash); cache.put(resolvableType, resolvableType); } return resolvableType; diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java index 4b52ee0dda..b74480be9f 100644 --- a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java +++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,15 +58,10 @@ abstract class SerializableTypeWrapper { private static final Class[] SUPPORTED_SERIALIZABLE_TYPES = { GenericArrayType.class, ParameterizedType.class, TypeVariable.class, WildcardType.class}; - private static final Method EQUALS_METHOD = ReflectionUtils.findMethod(Object.class, - "equals", Object.class); - - private static final Method GET_TYPE_PROVIDER_METHOD = ReflectionUtils.findMethod( - SerializableTypeProxy.class, "getTypeProvider"); - private static final ConcurrentReferenceHashMap cache = new ConcurrentReferenceHashMap(256); + /** * Return a {@link Serializable} variant of {@link Field#getGenericType()}. */ @@ -161,35 +156,33 @@ abstract class SerializableTypeWrapper { for (Class type : SUPPORTED_SERIALIZABLE_TYPES) { if (type.isAssignableFrom(provider.getType().getClass())) { ClassLoader classLoader = provider.getClass().getClassLoader(); - Class[] interfaces = new Class[] { type, - SerializableTypeProxy.class, Serializable.class }; + Class[] interfaces = new Class[] {type, SerializableTypeProxy.class, Serializable.class}; InvocationHandler handler = new TypeProxyInvocationHandler(provider); cached = (Type) Proxy.newProxyInstance(classLoader, interfaces, handler); cache.put(provider.getType(), cached); return cached; } } - throw new IllegalArgumentException("Unsupported Type class " + provider.getType().getClass().getName()); + throw new IllegalArgumentException("Unsupported Type class: " + provider.getType().getClass().getName()); } /** * Additional interface implemented by the type proxy. */ - static interface SerializableTypeProxy { + interface SerializableTypeProxy { /** * Return the underlying type provider. */ TypeProvider getTypeProvider(); - } /** * A {@link Serializable} interface providing access to a {@link Type}. */ - static interface TypeProvider extends Serializable { + interface TypeProvider extends Serializable { /** * Return the (possibly non {@link Serializable}) {@link Type}. @@ -213,7 +206,6 @@ abstract class SerializableTypeWrapper { public Object getSource() { return null; } - } @@ -233,10 +225,7 @@ abstract class SerializableTypeWrapper { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (GET_TYPE_PROVIDER_METHOD.equals(method)) { - return this.provider; - } - if (EQUALS_METHOD.equals(method)) { + if (method.getName().equals("equals")) { Object other = args[0]; // Unwrap proxies for speed if (other instanceof Type) { @@ -244,16 +233,24 @@ abstract class SerializableTypeWrapper { } return this.provider.getType().equals(other); } + else if (method.getName().equals("hashCode")) { + return this.provider.getType().hashCode(); + } + else if (method.getName().equals("getTypeProvider")) { + return this.provider; + } + if (Type.class == method.getReturnType() && args == null) { return forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, -1)); } - if (Type[].class == method.getReturnType() && args == null) { + else if (Type[].class == method.getReturnType() && args == null) { Type[] result = new Type[((Type[]) method.invoke(this.provider.getType(), args)).length]; for (int i = 0; i < result.length; i++) { result[i] = forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, i)); } return result; } + try { return method.invoke(this.provider.getType(), args); }