Optimized equals/hashCode handling in ResolvableType
Issue: SPR-13621
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user