Grails 4.1.1:Data Service无法注入Command Object,报NoSuchBeanDefinitionException
问题分析与解决:Grails命令对象依赖注入失败
我采用Controller/Command/Data Service的简单架构,代码如下:
1. 核心代码
CommunityController
@Secured("ROLE_BACKEND") class CommunityController { static namespace = "api" static allowedMethods = [index: "GET"] def index(GetCommunitiesRequest request) { respond request.execute(params).get() } }
GetCommunitiesRequest 命令对象
@CompileStatic class GetCommunitiesRequest implements Validateable { @Autowired CommunityService communityService GetCommunitiesRequest() { println "Hello" } Boolean withPartners = false Integer max Try<List<Map>> execute(Map params) { def r = Try.of { params.max = Math.min(max ?: 10, 100) communityService.list(params) }.map {communities-> communities.collect {it.asMap(withPartners)} } } }
CommunityService 接口
@Service(Community) interface CommunityService { abstract Community get(Serializable id) abstract List<Community> list(Map args) abstract Long count() abstract void delete(Serializable id) abstract Community save(Community community) }
UrlMappings 配置
class UrlMappings { static mappings = { group "/api", { delete "/$controller/$id(.$format)?", action:"delete", namespace:'api' get "/$controller(.$format)?", action:"index", namespace:'api' get "/$controller/$id(.$format)?", action:"show", namespace:'api' post "/$controller(.$format)?", action:"save", namespace:'api' put "/$controller/$id(.$format)?", action:"update", namespace:'api' patch "/$controller/$id(.$format)?", action:"patch", namespace:'api' "/", controller: 'application', action: 'index', namespace:"api" "500", view: '/error' "404", view: '/notFound' } "/$controller/$action?/$id?(.$format)?"{ constraints { // apply constraints here } } "/", controller: 'application', action:'index' "500", view: '/error' "404", view: '/notFound' } }
2. 异常信息
调用接口时抛出以下异常:
java.lang.reflect.InvocationTargetException: null ... Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.pr.request.GetCommunitiesRequest': Unsatisfied dependency expressed through field 'communityService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.pr.data.service.CommunityService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} ... Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.pr.data.service.CommunityService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
3. 已尝试操作
- 调试命令对象实例化过程,问题出在
grails.artefact.Controller#initializeCommandObject,但无法理解ApplicationContext层逻辑; - 通过Grails控制台确认
communityServiceBean存在:执行ctx.communityService返回com.example.pr.domain.$CommunityServiceImplementation@5deeeb12; - 在控制台执行
def controller = ctx.getBean("com.example.pr.http.api.CommunityController"); controller.initializeCommandObject(com.example.pr.request.GetCommunitiesRequest, "request")时,出现相同异常。
4. 更新1:移除@Autowired后的异常
移除命令对象中的@Autowired注解后,抛出新的异常:
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'com.example.pr.domain.$CommunityServiceImplementation' to required type 'com.example.pr.data.service.CommunityService' for property 'communityService'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'com.example.pr.domain.$CommunityServiceImplementation' to required type 'com.example.pr.data.service.CommunityService': no matching editors or conversion strategy found
问题分析
- 命令对象不支持@Autowired:Grails的命令对象是通过数据绑定机制实例化的,不属于Spring容器管理的Bean,因此
@Autowired注解无法生效,Spring找不到对应的依赖注入点。 - 类型转换异常原因:移除
@Autowired后,Grails会尝试将请求参数绑定到命令对象的communityService字段,但该字段名与Spring容器中的Bean名冲突,导致框架错误地尝试将Service实现类实例绑定到字段;若实现类与接口的包路径或继承关系存在问题,就会触发类型转换失败。
解决方案
方案1:Controller注入Service后传递给命令对象
这是最规范的做法,保持命令对象职责单一(仅处理请求参数验证和业务逻辑执行),依赖由Controller注入:
修改CommunityController:
@Secured("ROLE_BACKEND") class CommunityController { static namespace = "api" static allowedMethods = [index: "GET"] // 在Controller中注入Service CommunityService communityService def index(GetCommunitiesRequest request) { // 将Service传递给命令对象 request.communityService = communityService respond request.execute(params).get() } }
修改GetCommunitiesRequest(移除@Autowired):
@CompileStatic class GetCommunitiesRequest implements Validateable { CommunityService communityService GetCommunitiesRequest() { println "Hello" } Boolean withPartners = false Integer max Try<List<Map>> execute(Map params) { def r = Try.of { params.max = Math.min(max ?: 10, 100) communityService.list(params) }.map {communities-> communities.collect {it.asMap(withPartners)} } } }
方案2:命令对象直接从Spring上下文获取Service
如果不想修改Controller,可以通过Grails提供的Holders工具类直接从上下文获取Service实例:
修改GetCommunitiesRequest:
@CompileStatic class GetCommunitiesRequest implements Validateable { Boolean withPartners = false Integer max // 私有方法获取Service private CommunityService getCommunityService() { return Holders.grailsApplication.applicationContext.getBean(CommunityService) } Try<List<Map>> execute(Map params) { def r = Try.of { params.max = Math.min(max ?: 10, 100) // 调用私有方法获取Service getCommunityService().list(params) }.map {communities-> communities.collect {it.asMap(withPartners)} } } }
额外检查点
- 确认Service实现类与接口的继承关系:在Grails控制台执行
ctx.communityService instanceof com.example.pr.data.service.CommunityService,如果返回false,说明生成的实现类未正确实现接口,需检查@Service(Community)注解的使用位置(必须放在接口上),以及接口的包路径配置。 - 避免命令对象字段名与Spring Bean名冲突:尽量不要使用
communityService这类与Bean名一致的字段名,防止数据绑定机制误绑定。
内容的提问来源于stack exchange,提问作者Luis Muñíz
相关产品推荐
相关产品推荐

