Optimized equals/hashCode handling in ResolvableType

Issue: SPR-13621
This commit is contained in:
Juergen Hoeller
2015-11-03 16:56:43 +01:00
parent 09cb286c84
commit ce64761069
2 changed files with 72 additions and 50 deletions

View File

@@ -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;

View File

@@ -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<Type, Type> cache =
new ConcurrentReferenceHashMap<Type, Type>(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);
}