You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Grails集成springdoc swagger时出现Ambiguous mapping(模糊映射)错误

Grails集成springdoc swagger时出现Ambiguous mapping(模糊映射)错误

问题场景

你写的这个获取认证Token的接口逻辑很清晰,但启动时遇到的模糊映射错误确实让人头疼。先把你的代码和错误日志整理清楚,方便分析:

你的AuthenticationApiController代码

package example.infra.adapters.input.api.v1.endpoints

import example.aplication.services.authentication.LoginAuthenticator
import example.aplication.dtos.authentication.CredencialsDTO
import example.aplication.dtos.authentication.AuthenticationTokenDTO
import example.aplication.exceptions.authentication.AuthenticationErrorException
import org.springframework.web.bind.annotation.*
import org.springframework.http.ResponseEntity
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.ExampleObject
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.parameters.RequestBody

@RestController
@RequestMapping("/api/v1/authentication")
@Tag(name = "Authentication", description = "User authentication")
class AuthenticationApiController {

    LoginAuthenticator loginAuthenticator

    @PostMapping(value = "/token")
    @Operation(
        summary = "Get authentication token",
        requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
            required = true,
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = CredencialsDTO),
                examples = [
                    @ExampleObject(
                        value = '{"login": "loginExample", "senha": "senhaExample"}'
                    )
                ]
            )
        ),
        responses = [
            @ApiResponse(
                responseCode = "200",
                description = "Success",
                content = @Content(
                    mediaType = "application/json",
                    schema = @Schema(implementation = AuthenticationTokenDTO),
                    examples = [
                        @ExampleObject(
                            value = '{"token": "abc123", "expiration": "2025-09-03T18:00:00Z"}'
                        )
                    ]
                )
            ),
            @ApiResponse(
                responseCode = "401",
                description = "Incorrect login or password",
                content = @Content()
            )
        ]
    )
    ResponseEntity<?> token(@org.springframework.web.bind.annotation.RequestBody CredencialsDTO credencials) {
        try {
            AuthenticationTokenDTO tokenDto = loginAuthenticator.authenticate(credencials)
            return JsonResponse.success(tokenDto)
        } catch (AuthenticationErrorException e) {
            return JsonResponse.unauthorized()
        }
    }
}

启动时的错误日志

| Running application...
Configuring Spring Security Core ...
... finished configuring Spring Security Core
2025-09-04 14:58:23.510 ERROR --- [ restartedMain] o.s.boot.SpringApplication : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'authenticationApiController' method 
example.infra.adapters.input.api.v1.endpoints.AuthenticationApiController#token(CredencialsDTO) to {POST [/api/v1/authentication/token]}: There is already 'authenticationApiController' bean method 
example.infra.adapters.input.api.v1.endpoints.AuthenticationApiController#token() mapped.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
	at grails.boot.GrailsApp.run(GrailsApp.groovy:99)
	at grails.boot.GrailsApp.run(GrailsApp.groovy:485)
	at grails.boot.GrailsApp.run(GrailsApp.groovy:472)
	at example.Application.main(Application.groovy:8)

问题分析

从错误日志能明确看到:Spring发现你的AuthenticationApiController里有两个token方法都映射到了POST /api/v1/authentication/token路径——一个是你写的带CredencialsDTO参数的token(CredencialsDTO),另一个是无参的token()

为什么会凭空多出一个无参的token()方法?大概率是这几个原因:

  1. Groovy语法解析问题:你最初粘贴的代码没有正确换行,Groovy在编译时错误解析了代码结构,生成了额外的无参方法;
  2. Groovy自动方法生成特性:你用了字段注入LoginAuthenticator loginAuthenticator(没有加private final),Groovy会自动生成getter/setter,结合Swagger注解的处理可能触发了意外的方法重载;
  3. Swagger注解冲突:你在@Operation里用了全限定类名的@io.swagger.v3.oas.annotations.parameters.RequestBody,同时方法参数又用了全限定的@org.springframework.web.bind.annotation.RequestBody,双重注解可能导致Spring的映射处理器误判。

解决方案

针对这些可能的原因,按以下步骤修改代码,应该能解决问题:

1. 修正代码格式,确保语法解析正确

每个字段、注解、方法都单独占一行,避免Groovy编译时的歧义。

2. 使用构造注入代替字段注入(Spring官方推荐)

构造注入能避免Groovy自动生成getter/setter的潜在问题,同时让依赖关系更清晰:

// 替换原来的字段注入
private final LoginAuthenticator loginAuthenticator

// 增加构造方法
AuthenticationApiController(LoginAuthenticator loginAuthenticator) {
    this.loginAuthenticator = loginAuthenticator
}

3. 统一注解的使用方式

既然已经导入了注解包,就不要用全限定类名,减少冲突:

// 方法参数上直接用@RequestBody,不用写全限定名
ResponseEntity<?> token(@RequestBody CredencialsDTO credencials) {
    // ... 原有逻辑
}

// @Operation里的requestBody也简化
requestBody = @RequestBody(
    required = true,
    content = @Content(
        // ... 原有内容
    )
)

4. 最终修正后的完整代码

package example.infra.adapters.input.api.v1.endpoints

import example.aplication.services.authentication.LoginAuthenticator
import example.aplication.dtos.authentication.CredencialsDTO
import example.aplication.dtos.authentication.AuthenticationTokenDTO
import example.aplication.exceptions.authentication.AuthenticationErrorException
import org.springframework.web.bind.annotation.*
import org.springframework.http.ResponseEntity
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.ExampleObject
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.parameters.RequestBody

@RestController
@RequestMapping("/api/v1/authentication")
@Tag(name = "Authentication", description = "User authentication")
class AuthenticationApiController {

    private final LoginAuthenticator loginAuthenticator

    AuthenticationApiController(LoginAuthenticator loginAuthenticator) {
        this.loginAuthenticator = loginAuthenticator
    }

    @PostMapping("/token")
    @Operation(
        summary = "Get authentication token",
        requestBody = @RequestBody(
            required = true,
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = CredencialsDTO),
                examples = [
                    @ExampleObject(
                        value = '{"login": "loginExample", "senha": "senhaExample"}'
                    )
                ]
            )
        ),
        responses = [
            @ApiResponse(
                responseCode = "200",
                description = "Success",
                content = @Content(
                    mediaType = "application/json",
                    schema = @Schema(implementation = AuthenticationTokenDTO),
                    examples = [
                        @ExampleObject(
                            value = '{"token": "abc123", "expiration": "2025-09-03T18:00:00Z"}'
                        )
                    ]
                )
            ),
            @ApiResponse(
                responseCode = "401",
                description = "Incorrect login or password",
                content = @Content()
            )
        ]
    )
    ResponseEntity<?> token(@RequestBody CredencialsDTO credencials) {
        try {
            AuthenticationTokenDTO tokenDto = loginAuthenticator.authenticate(credencials)
            return JsonResponse.success(tokenDto)
        } catch (AuthenticationErrorException e) {
            return JsonResponse.unauthorized()
        }
    }
}

验证修改

修改后重新启动应用grails run-app --port 8080,应该不会再出现模糊映射的错误了。如果还有问题,可以检查:

  • 有没有其他控制器类映射了相同的路径;
  • Grails的组件扫描配置是否正确,避免重复扫描同一个包;
  • springdoc-openapi的版本是否和Grails/Spring Boot版本兼容。

火山引擎 最新活动