related issue: #29611
RequestMappingInfoHandlerMapping handleNoMatch()
method has the following format.
https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java#L246
https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java#L169
@Override
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, ...) throws Exception {
PartialMatchHelper helper = new PartialMatchHelper(infos, exchange);
if (helper.isEmpty()) {
return null;
}
...
}
And this method may allow an empty value in Set<RequestMappingInfo> infos
parameter.
(Unfortunately, it happens a lot in the service I am running.)
The constructor code of org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping.PartialMatchHelper is shown below. https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java#L227
public PartialMatchHelper(Set<RequestMappingInfo> infos, ServerWebExchange exchange) {
this.partialMatches.addAll(infos.stream().
filter(info -> info.getPatternsCondition().getMatchingCondition(exchange) != null).
map(info -> new PartialMatch(info, exchange)).
collect(Collectors.toList()));
}
This code executes follwoing method even if there is no value in infos
java.util.Collection#stream
java.util.stream.Collectors#toList
java.util.stream.ReferencePipeline#collect(java.util.stream.Collector<? super P_OUT,A,R>)
Unnecessary resources are used via these methods.(create objects and execute operations, so )
Even though parameters have values, it seems better not to use Stream.
It can save resources by changing the constructor like mvc's RequestMappingInfoHandlerMapping. https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java#L300
public PartialMatchHelper(Set<RequestMappingInfo> infos, HttpServletRequest request) {
for (RequestMappingInfo info : infos) {
if (info.getActivePatternsCondition().getMatchingCondition(request) != null) {
this.partialMatches.add(new PartialMatch(info, request));
}
}
}
Below is a test created similar to the code above. 1) style of webflux 2) style of mvc 3) style of reuse object
- test code
public class PartialMatchHelperTest {
@Test
public void partialMatchHelperTest() {
int count = 1000 * 1000 * 100;
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
// 1)
long startTime = System.currentTimeMillis();
final long startCpuTime = mxBean.getCurrentThreadCpuTime();
for (int i = 0; i < count; i++) {
new PartialMatchHelper(Collections.emptySet());
}
System.out.println(System.currentTimeMillis() - startTime);
System.out.println(mxBean.getCurrentThreadCpuTime() - startCpuTime);
// 2)
long startTime2 = System.currentTimeMillis();
final long startCpuTime2 = mxBean.getCurrentThreadCpuTime();
for (int i = 0; i < count; i++) {
new PartialMatchHelper2(Collections.emptySet());
}
System.out.println(System.currentTimeMillis() - startTime2);
System.out.println(mxBean.getCurrentThreadCpuTime() - startCpuTime2);
// 3)
long startTime3 = System.currentTimeMillis();
final long startCpuTime3 = mxBean.getCurrentThreadCpuTime();
for (int i = 0; i < count; i++) {
PartialMatchHelper3.of(Collections.emptySet());
}
System.out.println(System.currentTimeMillis() - startTime3);
System.out.println(mxBean.getCurrentThreadCpuTime() - startCpuTime3);
}
private static class PartialMatchHelper {
private final List<PartialMatch> partialMatches = new ArrayList<>();
public PartialMatchHelper(Set<RequestMappingInfo> infos) {
partialMatches.addAll(infos.stream().
filter(info -> info.getPatternsCondition().getMatchingCondition(null) != null).
map(info -> new PartialMatch(info)).
collect(Collectors.toList()));
}
public boolean isEmpty() {
return partialMatches.isEmpty();
}
}
private class PartialMatchHelper2 {
private final List<PartialMatch> partialMatches = new ArrayList<>();
public PartialMatchHelper2(Set<RequestMappingInfo> infos) {
for (RequestMappingInfo info : infos) {
if (info.getMatchingCondition(null) != null) {
this.partialMatches.add(new PartialMatch(info));
}
}
}
public boolean isEmpty() {
return partialMatches.isEmpty();
}
}
private static class PartialMatchHelper3 {
static PartialMatchHelper3 DISALBELD = new PartialMatchHelper3(Collections.emptySet());
private final List<PartialMatch> partialMatches = new ArrayList<>();
public static PartialMatchHelper3 of(Set<RequestMappingInfo> infos) {
if (infos.isEmpty()) {
return DISALBELD;
} else {
return new PartialMatchHelper3(infos);
}
}
public PartialMatchHelper3(Set<RequestMappingInfo> infos) {
for (RequestMappingInfo info : infos) {
if (info.getMatchingCondition(null) != null) {
this.partialMatches.add(new PartialMatch(info));
}
}
}
public boolean isEmpty() {
return partialMatches.isEmpty();
}
}
private static class PartialMatch {
private final RequestMappingInfo info;
public PartialMatch(RequestMappingInfo info) {
this.info = info;
}
public RequestMappingInfo getInfo() {
return this.info;
}
@Override
public String toString() {
return this.info.toString();
}
}
}
Result (from Async Profiler)
1) samples : CPU 393, Memory allocations 13,474,672
2) samples : CPU 580, Memory allocations 4,804,133,448
3) samples : CPU 6,547, Memory allocations 37,581,912,072
In my opinion, the following order seems like a good way. (Suggested solution (3 + 2) -> 3 -> 2 -> 1)
I will make PR according to your opinion.
Thanks :)
Comment From: koo-taejin
I have worked for Only the initialization part mentioned in the issue. The performance of Stream itself is not good, so it would be better if other parts were improved. http://www.angelikalanger.com/Conferences/Videos/Conference-Video-GeeCon-2015-Performance-Model-of-Streams-in-Java-8-Angelika-Langer.html
If you want to change the other parts to the collector's loop as before. Please let me know, then I am going to work for it.
Thanks :)
Comment From: rstoyanchev
Backported to 5.3.25 with #29667.