#749 - More exhaustive caching of mappings, UriTemplates and base URIs.
We now cache the UriTemplates created for request mappings to avoid repeated parsing of the input string. We also cache the base URI within the RequestAttributes for a particular request.
This commit is contained in:
committed by
Oliver Drotbohm
parent
8c65f4b349
commit
a5f2b602fb
@@ -0,0 +1,73 @@
|
||||
package org.springframework.hateoas.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class CachingMappingDiscoverer implements MappingDiscoverer {
|
||||
private Map<String, String> mappingCache = new ConcurrentHashMap<String, String>();
|
||||
private MappingDiscoverer discoverer;
|
||||
|
||||
public CachingMappingDiscoverer(MappingDiscoverer discoverer) {
|
||||
this.discoverer = discoverer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMapping(final Class<?> type) {
|
||||
String key = key(type, null);
|
||||
return getMapping(key, new CachedCall() {
|
||||
@Override
|
||||
public String getMapping() {
|
||||
return discoverer.getMapping(type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMapping(final Method method) {
|
||||
String key = key(method.getDeclaringClass(), method);
|
||||
return getMapping(key, new CachedCall() {
|
||||
@Override
|
||||
public String getMapping() {
|
||||
return discoverer.getMapping(method);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMapping(final Class<?> type, final Method method) {
|
||||
String key = key(type, method);
|
||||
return getMapping(key, new CachedCall() {
|
||||
@Override
|
||||
public String getMapping() {
|
||||
return discoverer.getMapping(type, method);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getMapping(String key, CachedCall cachedCall) {
|
||||
if (mappingCache.containsKey(key)) {
|
||||
return mappingCache.get(key);
|
||||
} else {
|
||||
String mapping = cachedCall.getMapping();
|
||||
mappingCache.put(key, mapping);
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
private interface CachedCall {
|
||||
String getMapping();
|
||||
}
|
||||
|
||||
private String key(Class<?> type, Method method) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(type.getName());
|
||||
if (method != null) {
|
||||
buf.append(method.getName());
|
||||
for (Class<?> par: method.getParameterTypes()) {
|
||||
buf.append(par.getName());
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
private static final CachingAnnotationMappingDiscoverer DISCOVERER = new CachingAnnotationMappingDiscoverer(
|
||||
new AnnotationMappingDiscoverer(RequestMapping.class));
|
||||
private static final ControllerLinkBuilderFactory FACTORY = new ControllerLinkBuilderFactory();
|
||||
private static final String CACHE_KEY = ControllerLinkBuilder.class.getName() + "#BUILDER_CACHE";
|
||||
private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler();
|
||||
|
||||
private final TemplateVariables variables;
|
||||
@@ -234,7 +235,7 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
return UriComponentsBuilder.fromUri(toUri());
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.LinkBuilderSupport#toString()
|
||||
*/
|
||||
@@ -260,15 +261,26 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
* request contains an {@code X-Forwarded-Ssl} header, which is not (yet) supported by the underlying
|
||||
* {@link UriComponentsBuilder}. If no {@link RequestContextHolder} exists (you're outside a Spring Web call), fall
|
||||
* back to relative URIs.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static UriComponentsBuilder getBuilder() {
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() == null) {
|
||||
return UriComponentsBuilder.fromPath("/");
|
||||
}
|
||||
|
||||
URI baseUri = getCachedBaseUri();
|
||||
if (baseUri == null) {
|
||||
UriComponentsBuilder builderFromRequest = createBuilderFromRequest();
|
||||
cacheBaseUri(builderFromRequest.build().toUri());
|
||||
return builderFromRequest;
|
||||
} else {
|
||||
return UriComponentsBuilder.fromUri(baseUri);
|
||||
}
|
||||
}
|
||||
|
||||
private static UriComponentsBuilder createBuilderFromRequest() {
|
||||
|
||||
HttpServletRequest request = getCurrentRequest();
|
||||
ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request);
|
||||
|
||||
@@ -303,15 +315,27 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
private static HttpServletRequest getCurrentRequest() {
|
||||
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
Assert.state(requestAttributes != null, REQUEST_ATTRIBUTES_MISSING);
|
||||
Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);
|
||||
RequestAttributes requestAttributes = getRequestAttributes();
|
||||
HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
Assert.state(servletRequest != null, "Could not find current HttpServletRequest");
|
||||
return servletRequest;
|
||||
}
|
||||
|
||||
private static RequestAttributes getRequestAttributes() {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
Assert.state(requestAttributes != null, REQUEST_ATTRIBUTES_MISSING);
|
||||
Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);
|
||||
return requestAttributes;
|
||||
}
|
||||
|
||||
private static void cacheBaseUri(URI uri) {
|
||||
getRequestAttributes().setAttribute(CACHE_KEY, uri, RequestAttributes.SCOPE_REQUEST);
|
||||
}
|
||||
|
||||
private static URI getCachedBaseUri() {
|
||||
return (URI) getRequestAttributes().getAttribute(CACHE_KEY, RequestAttributes.SCOPE_REQUEST);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class CachingAnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
|
||||
@@ -339,7 +363,7 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
setStrictEncoding(true);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.util.Map)
|
||||
*/
|
||||
@@ -348,7 +372,7 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.lang.Object[])
|
||||
*/
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.core.AnnotationAttribute;
|
||||
import org.springframework.hateoas.core.AnnotationMappingDiscoverer;
|
||||
import org.springframework.hateoas.core.CachingMappingDiscoverer;
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware;
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils.MethodInvocation;
|
||||
import org.springframework.hateoas.core.LinkBuilderSupport;
|
||||
@@ -67,12 +68,14 @@ import org.springframework.web.util.UriTemplate;
|
||||
*/
|
||||
public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<ControllerLinkBuilder> {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class);
|
||||
private static final MappingDiscoverer DISCOVERER = new CachingMappingDiscoverer(
|
||||
new AnnotationMappingDiscoverer(RequestMapping.class));
|
||||
private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR = new AnnotatedParametersParameterAccessor(
|
||||
new AnnotationAttribute(PathVariable.class));
|
||||
private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new RequestParamParameterAccessor();
|
||||
|
||||
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<UriComponentsContributor>();
|
||||
private UriTemplateFactory uriTemplateFactory = new UriTemplateFactory();
|
||||
|
||||
/**
|
||||
* Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method
|
||||
@@ -103,7 +106,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
return ControllerLinkBuilder.linkTo(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@@ -138,7 +141,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), method);
|
||||
UriComponentsBuilder builder = ControllerLinkBuilder.getBuilder().path(mapping);
|
||||
|
||||
UriTemplate template = new UriTemplate(mapping);
|
||||
UriTemplate template = uriTemplateFactory.templateFor(mapping);
|
||||
Map<String, Object> values = new HashMap<String, Object>();
|
||||
Iterator<String> names = template.getVariableNames().iterator();
|
||||
|
||||
@@ -280,7 +283,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
super(new AnnotationAttribute(RequestParam.class));
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#createParameter(org.springframework.core.MethodParameter, java.lang.Object, org.springframework.hateoas.core.AnnotationAttribute)
|
||||
*/
|
||||
@@ -290,7 +293,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
|
||||
return new BoundMethodParameter(parameter, value, attribute) {
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor.BoundMethodParameter#isRequired()
|
||||
*/
|
||||
@@ -309,7 +312,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#verifyParameterValue(org.springframework.core.MethodParameter, java.lang.Object)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.springframework.hateoas.mvc;
|
||||
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Builds and caches UriTemplates.
|
||||
*/
|
||||
class UriTemplateFactory {
|
||||
private Map<String, UriTemplate> templateCache = new ConcurrentHashMap<String, UriTemplate>();
|
||||
UriTemplate templateFor(String mapping) {
|
||||
if (templateCache.containsKey(mapping)) {
|
||||
return templateCache.get(mapping);
|
||||
} else {
|
||||
UriTemplate template = new UriTemplate(mapping);
|
||||
templateCache.put(mapping, template);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user