diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java index f6d3703c20..8f1ddd4b0e 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeType.java +++ b/spring-core/src/main/java/org/springframework/util/MimeType.java @@ -103,6 +103,8 @@ public class MimeType implements Comparable, Serializable { private final Map parameters; + private String mimetype; + /** * Create a new {@code MimeType} for the given primary type. @@ -469,9 +471,12 @@ public class MimeType implements Comparable, Serializable { @Override public String toString() { - StringBuilder builder = new StringBuilder(); - appendTo(builder); - return builder.toString(); + if (this.mimetype == null) { + StringBuilder builder = new StringBuilder(); + appendTo(builder); + this.mimetype = builder.toString(); + } + return this.mimetype; } protected void appendTo(StringBuilder builder) { diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java index d09ee8c4a2..2a05265b09 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java +++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -28,6 +28,11 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.lang.Nullable; @@ -39,6 +44,7 @@ import org.springframework.util.MimeType.SpecificityComparator; * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Dimitrios Liapis + * @author Brian Clozel * @since 4.0 */ public abstract class MimeTypeUtils { @@ -49,6 +55,9 @@ public abstract class MimeTypeUtils { 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + private static final ConcurrentLRUCache CACHED_MIMETYPES = + new ConcurrentLRUCache<>(32, MimeTypeUtils::parseMimeTypeInternal); + /** * Comparator used by {@link #sortBySpecificity(List)}. */ @@ -157,28 +166,31 @@ public abstract class MimeTypeUtils { @Nullable private static volatile Random random; - static { - ALL = MimeType.valueOf(ALL_VALUE); - APPLICATION_JSON = MimeType.valueOf(APPLICATION_JSON_VALUE); - APPLICATION_OCTET_STREAM = MimeType.valueOf(APPLICATION_OCTET_STREAM_VALUE); - APPLICATION_XML = MimeType.valueOf(APPLICATION_XML_VALUE); - IMAGE_GIF = MimeType.valueOf(IMAGE_GIF_VALUE); - IMAGE_JPEG = MimeType.valueOf(IMAGE_JPEG_VALUE); - IMAGE_PNG = MimeType.valueOf(IMAGE_PNG_VALUE); - TEXT_HTML = MimeType.valueOf(TEXT_HTML_VALUE); - TEXT_PLAIN = MimeType.valueOf(TEXT_PLAIN_VALUE); - TEXT_XML = MimeType.valueOf(TEXT_XML_VALUE); + ALL = new MimeType("*", "*"); + APPLICATION_JSON = new MimeType("application", "json"); + APPLICATION_OCTET_STREAM = new MimeType("application", "octet-stream"); + APPLICATION_XML = new MimeType("application", "xml"); + IMAGE_GIF = new MimeType("image", "gif"); + IMAGE_JPEG = new MimeType("image", "jpeg"); + IMAGE_PNG = new MimeType("image", "png"); + TEXT_HTML = new MimeType("text", "html"); + TEXT_PLAIN = new MimeType("text", "plain"); + TEXT_XML = new MimeType("text", "xml"); } - /** * Parse the given String into a single {@code MimeType}. + * Recently parsed {@code MimeType} are cached for further retrieval. * @param mimeType the string to parse * @return the mime type * @throws InvalidMimeTypeException if the string cannot be parsed */ public static MimeType parseMimeType(String mimeType) { + return CACHED_MIMETYPES.get(mimeType); + } + + private static MimeType parseMimeTypeInternal(String mimeType) { if (!StringUtils.hasLength(mimeType)) { throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty"); } @@ -387,4 +399,52 @@ public abstract class MimeTypeUtils { return new String(generateMultipartBoundary(), StandardCharsets.US_ASCII); } + static class ConcurrentLRUCache { + + private final int maxSize; + + private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + + private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + private final Function generator; + + ConcurrentLRUCache(int maxSize, Function generator) { + Assert.isTrue(maxSize > 0, "LRU max size should be positive"); + Assert.notNull(generator, "Generator function should not be null"); + this.maxSize = maxSize; + this.generator = generator; + } + + public V get(K key) { + this.lock.readLock().lock(); + try { + if (this.queue.remove(key)) { + this.queue.add(key); + return this.cache.get(key); + } + } + finally { + this.lock.readLock().unlock(); + } + this.lock.writeLock().lock(); + try { + if (this.queue.size() == this.maxSize) { + K leastUsed = this.queue.poll(); + this.cache.remove(leastUsed); + } + V value = this.generator.apply(key); + this.queue.add(key); + this.cache.put(key, value); + return value; + } + finally { + this.lock.writeLock().unlock(); + } + } + + } + } diff --git a/spring-web/src/main/java/org/springframework/http/MediaType.java b/spring-web/src/main/java/org/springframework/http/MediaType.java index 3c6bfea981..b00eabec6e 100644 --- a/spring-web/src/main/java/org/springframework/http/MediaType.java +++ b/spring-web/src/main/java/org/springframework/http/MediaType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -18,6 +18,7 @@ package org.springframework.http; import java.io.Serializable; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -25,7 +26,6 @@ import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -323,29 +323,29 @@ public class MediaType extends MimeType implements Serializable { static { - ALL = valueOf(ALL_VALUE); - APPLICATION_ATOM_XML = valueOf(APPLICATION_ATOM_XML_VALUE); - APPLICATION_FORM_URLENCODED = valueOf(APPLICATION_FORM_URLENCODED_VALUE); - APPLICATION_JSON = valueOf(APPLICATION_JSON_VALUE); - APPLICATION_JSON_UTF8 = valueOf(APPLICATION_JSON_UTF8_VALUE); - APPLICATION_OCTET_STREAM = valueOf(APPLICATION_OCTET_STREAM_VALUE); - APPLICATION_PDF = valueOf(APPLICATION_PDF_VALUE); - APPLICATION_PROBLEM_JSON = valueOf(APPLICATION_PROBLEM_JSON_VALUE); - APPLICATION_PROBLEM_JSON_UTF8 = valueOf(APPLICATION_PROBLEM_JSON_UTF8_VALUE); - APPLICATION_PROBLEM_XML = valueOf(APPLICATION_PROBLEM_XML_VALUE); - APPLICATION_RSS_XML = valueOf(APPLICATION_RSS_XML_VALUE); - APPLICATION_STREAM_JSON = valueOf(APPLICATION_STREAM_JSON_VALUE); - APPLICATION_XHTML_XML = valueOf(APPLICATION_XHTML_XML_VALUE); - APPLICATION_XML = valueOf(APPLICATION_XML_VALUE); - IMAGE_GIF = valueOf(IMAGE_GIF_VALUE); - IMAGE_JPEG = valueOf(IMAGE_JPEG_VALUE); - IMAGE_PNG = valueOf(IMAGE_PNG_VALUE); - MULTIPART_FORM_DATA = valueOf(MULTIPART_FORM_DATA_VALUE); - TEXT_EVENT_STREAM = valueOf(TEXT_EVENT_STREAM_VALUE); - TEXT_HTML = valueOf(TEXT_HTML_VALUE); - TEXT_MARKDOWN = valueOf(TEXT_MARKDOWN_VALUE); - TEXT_PLAIN = valueOf(TEXT_PLAIN_VALUE); - TEXT_XML = valueOf(TEXT_XML_VALUE); + ALL = new MediaType("*", "*"); + APPLICATION_ATOM_XML = new MediaType("application", "atom+xml"); + APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded"); + APPLICATION_JSON = new MediaType("application", "json"); + APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8); + APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream");; + APPLICATION_PDF = new MediaType("application", "pdf"); + APPLICATION_PROBLEM_JSON = new MediaType("application", "problem+json"); + APPLICATION_PROBLEM_JSON_UTF8 = new MediaType("application", "problem", StandardCharsets.UTF_8); + APPLICATION_PROBLEM_XML = new MediaType("application", "problem+xml"); + APPLICATION_RSS_XML = new MediaType("application", "rss+xml"); + APPLICATION_STREAM_JSON = new MediaType("application", "stream+json"); + APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml"); + APPLICATION_XML = new MediaType("application", "xml"); + IMAGE_GIF = new MediaType("image", "gif"); + IMAGE_JPEG = new MediaType("image", "jpeg"); + IMAGE_PNG = new MediaType("image", "png"); + MULTIPART_FORM_DATA = new MediaType("multipart", "form-data"); + TEXT_EVENT_STREAM = new MediaType("text", "event-stream"); + TEXT_HTML = new MediaType("text", "html"); + TEXT_MARKDOWN = new MediaType("text", "markdown"); + TEXT_PLAIN = new MediaType("text", "plain"); + TEXT_XML = new MediaType("text", "xml"); } @@ -552,8 +552,12 @@ public class MediaType extends MimeType implements Serializable { if (!StringUtils.hasLength(mediaTypes)) { return Collections.emptyList(); } - return MimeTypeUtils.tokenize(mediaTypes).stream() - .map(MediaType::parseMediaType).collect(Collectors.toList()); + List tokenizedTypes = MimeTypeUtils.tokenize(mediaTypes); + List result = new ArrayList<>(tokenizedTypes.size()); + for (String type : tokenizedTypes) { + result.add(parseMediaType(type)); + } + return result; } /** @@ -586,7 +590,11 @@ public class MediaType extends MimeType implements Serializable { * @since 5.0 */ public static List asMediaTypes(List mimeTypes) { - return mimeTypes.stream().map(MediaType::asMediaType).collect(Collectors.toList()); + List mediaTypes = new ArrayList<>(mimeTypes.size()); + for(MimeType mimeType : mimeTypes) { + mediaTypes.add(MediaType.asMediaType(mimeType)); + } + return mediaTypes; } /**