Skip to content

MethodIntrospector.selectMethods() fails to detect bridge methods across ApplicationContexts #32586

Closed
@RHarryH

Description

@RHarryH

Spring Boot version: 3.2.3, 3.2.4 (Spring Core 6.1.4)
Java version: Temurin 17 (17.0.10)

Reproduced on Windows and Github Actions with Ubuntu 22.04.

Minimal example: https://github.com/RHarryH/spring-webmvc-github-issue

Description:

I have observed weird issue of WebMvcTest failure with code 405 instead of expected 200 because Spring does not resolve controller method based on the request url. 405 error happens when there are two endpoints with the same request url but different HTTP method. When request urls are different 404 error is thrown.

This happens only when specific hierarchy of controllers is used and when WebMvcTest is run after SpringBootTest (achieved by changing test class execution order in junit-platform.properties.

The hierarchy of the controllers is as follows:

  • Controller interface defining endpoints and annotating them with @XMapping annotations
  • AbstractController implementing delete method. Please not it is a package-private abstract class
  • ActualController implementing remaining methods

The presence of AbstractController is the main cause of the issue. Working workaround is making it public.

When debugging tests SpringBootTest logs contains:

2024-04-07T12:01:20.781+02:00 DEBUG 33568 --- [           main] _.s.web.servlet.HandlerMapping.Mappings  : 
	c.a.i.ActualController:
	{POST [/v1/a]}: add(Body,BindingResult)
	{POST [/v1/a/{id}]}: update(UUID,Body,BindingResult)
	{DELETE [/v1/a/{id}]}: delete(UUID)

while WebMvcTest logs miss DELETE method:

2024-04-07T12:01:22.203+02:00 DEBUG 33568 --- [           main] _.s.web.servlet.HandlerMapping.Mappings  : 
	c.a.i.ActualController:
	{POST [/v1/a]}: add(Body,BindingResult)
	{POST [/v1/a/{id}]}: update(UUID,Body,BindingResult)

I have tracked down the rootcause to the org.springframework.core.MethodIntrocpector class and selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) method (

).

Line 74 correctly inspects the method. The problem is in line 77. When SpringBootTest tests are run the fields looks like below:

method = {Method@7492} "public void com.avispa.issue.AbstractController.delete(java.util.UUID)"
specificMethod = {Method@7493} "public void com.avispa.issue.ActualController.delete(java.util.UUID)"
result = {RequestMappingInfo@7494} "{DELETE [/v1/a/{id}]}"
bridgedMethod = {Method@7492} "public void com.avispa.issue.AbstractController.delete(java.util.UUID)"

But then whenWebMvcTest tests are run it looks like below:

method = {Method@9155} "public void com.avispa.issue.AbstractController.delete(java.util.UUID)"
specificMethod = {Method@9156} "public void com.avispa.issue.ActualController.delete(java.util.UUID)"
result = {RequestMappingInfo@9157} "{DELETE [/v1/a/{id}]}"
bridgedMethod = {Method@7492} "public void com.avispa.issue.AbstractController.delete(java.util.UUID)"

As you can see in second case method and bridgedMethod represents the same method but are in fact different instances of Method class. And because the comparison in line 77 is done by reference, it failes and does not add found DELETE method to the mappings registry.

When SpringBootTest tests are disabled, the problem does not exist.

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)status: backportedAn issue that has been backported to maintenance branchestype: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions