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 1fea14ca60d..539be7f5616 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeType.java +++ b/spring-core/src/main/java/org/springframework/util/MimeType.java @@ -190,6 +190,18 @@ public class MimeType implements Comparable, Serializable { } } + /** + * Copy-constructor that copies the type, subtype and parameters of the given {@code MimeType}, + * skipping checks performed in other constructors. + * @param other the other MimeType + */ + protected MimeType(MimeType other) { + this.type = other.type; + this.subtype = other.subtype; + this.parameters = other.parameters; + this.toStringValue = other.toStringValue; + } + /** * Checks the given token string for illegal characters, as defined in RFC 2616, * section 2.2. @@ -197,7 +209,7 @@ public class MimeType implements Comparable, Serializable { * @see HTTP 1.1, section 2.2 */ private void checkToken(String token) { - for (int i = 0; i < token.length(); i++ ) { + for (int i = 0; i < token.length(); i++) { char ch = token.charAt(i); if (!TOKEN.get(ch)) { throw new IllegalArgumentException("Invalid token character '" + ch + "' in token \"" + token + "\""); diff --git a/spring-web/src/jmh/java/org/springframework/http/MediaTypeBenchmark.java b/spring-web/src/jmh/java/org/springframework/http/MediaTypeBenchmark.java new file mode 100644 index 00000000000..d86fb42ac6d --- /dev/null +++ b/spring-web/src/jmh/java/org/springframework/http/MediaTypeBenchmark.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; + +import org.springframework.util.MimeTypeUtils; + +/** + * Benchmarks for parsing Media Types using {@link MediaType}. + *

{@code MediaType is using }{@link MimeTypeUtils} has an internal parser only accessible through a package private method. + * The publicly accessible method is backed by a LRUCache for better performance. + * + * @author Brian Clozel + * @see MimeTypeUtils + */ +@BenchmarkMode(Mode.Throughput) +public class MediaTypeBenchmark { + + @Benchmark + public void parseAllMediaTypes(BenchmarkData data, Blackhole bh) { + for (String type : data.mediaTypes) { + bh.consume(MediaType.parseMediaType(type)); + } + } + + @Benchmark + public void parseSomeMediaTypes(BenchmarkData data, Blackhole bh) { + for (String type : data.requestedMediaTypes) { + bh.consume(MediaType.parseMediaType(type)); + } + } + + /** + * Benchmark data holding typical raw Media Types. + * A {@code customTypesCount} parameter can be used to pad the list with artificial types. + * The {@param requestedTypeCount} parameter allows to choose the number of requested types at runtime, + * since we don't want to use all available types in the cache in some benchmarks. + */ + @State(Scope.Benchmark) + public static class BenchmarkData { + + @Param({"40"}) + public int customTypesCount; + + @Param({"10"}) + public int requestedTypeCount; + + public List mediaTypes; + + public List requestedMediaTypes; + + @Setup(Level.Trial) + public void fillCache() { + this.mediaTypes = new ArrayList<>(); + // Add 25 common MIME types + this.mediaTypes.addAll(Arrays.asList( + "application/json", + "application/octet-stream", + "application/pdf", + "application/problem+json", + "application/xhtml+xml", + "application/rss+xml", + "application/stream+json", + "application/xml;q=0.9", + "application/atom+xml", + "application/cbor", + "application/x-www-form-urlencoded", + "*/*", + "image/gif", + "image/jpeg", + "image/webp", + "image/png", + "image/apng", + "text/plain", + "text/html", + "text/xml", + "text/event-stream", + "text/markdown", + "*/*;q=0.8", + "multipart/form-data", + "multipart/mixed" + )); + // Add custom types, allowing to fill the LRU cache (which has a default size of 64) + IntStream.range(0, this.customTypesCount).forEach(i -> this.mediaTypes.add("custom/type" + i)); + this.requestedMediaTypes = this.mediaTypes.subList(0, this.requestedTypeCount); + + // ensure that all known MIME types are parsed once and cached + this.mediaTypes.forEach(MediaType::parseMediaType); + } + + } + +} 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 9fd8e2ff87e..af3c9f9f9d0 100644 --- a/spring-web/src/main/java/org/springframework/http/MediaType.java +++ b/spring-web/src/main/java/org/springframework/http/MediaType.java @@ -479,6 +479,11 @@ public class MediaType extends MimeType implements Serializable { super(type, subtype, parameters); } + public MediaType(MimeType mimeType) { + super(mimeType); + this.getParameters().forEach(this::checkParameters); + } + @Override protected void checkParameters(String attribute, String value) { @@ -587,7 +592,8 @@ public class MediaType extends MimeType implements Serializable { throw new InvalidMediaTypeException(ex); } try { - return new MediaType(type.getType(), type.getSubtype(), type.getParameters()); + //return new MediaType(type.getType(), type.getSubtype(), type.getParameters()); + return new MediaType(type); } catch (IllegalArgumentException ex) { throw new InvalidMediaTypeException(mediaType, ex.getMessage());