Search code examples
grailsgrails-controller

Grails 4.1.1 Data Service cannot be injected into Command Object: NoSuchBeanDefinitionException


I Have a simple design of Controller/Command/Data Service:

@Secured("ROLE_BACKEND")
class CommunityController {
    static namespace = "api"

    static allowedMethods = [index: "GET"]

    def index(GetCommunitiesRequest request) {
        respond request.execute(params).get()
    }
}
@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)}
        }
    }
}
@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)

}

This is the UrlMappings file (just in case there is a link with namespacing the controller):

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')
    }
}

Most of this code is based on generated code by grails when using generate-all on my domain class. Obviously the added wrinkle is the Command (and the Try monad, but this should be irrelevant).

When I execute this endpoint I get the following exception:

java.lang.reflect.InvocationTargetException: null
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1043)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at grails.plugin.springsecurity.rest.RestLogoutFilter.doFilter(RestLogoutFilter.groovy:82)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at grails.plugin.springsecurity.web.filter.GrailsHttpPutFormContentFilter.doFilterInternal(GrailsHttpPutFormContentFilter.groovy:54)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at grails.plugin.springsecurity.rest.RestTokenValidationFilter.processFilterChain(RestTokenValidationFilter.groovy:121)
at grails.plugin.springsecurity.rest.RestTokenValidationFilter.doFilter(RestTokenValidationFilter.groovy:89)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at grails.plugin.springsecurity.rest.RestAuthenticationFilter.doFilter(RestAuthenticationFilter.groovy:136)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doFilter(AbstractPreAuthenticatedProcessingFilter.java:124)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter.doFilter(MutableLogoutFilter.groovy:64)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at grails.plugin.springsecurity.web.SecurityRequestHolderFilter.doFilter(SecurityRequestHolderFilter.groovy:58)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
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)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:660)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1425)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:392)
at grails.artefact.Controller$Trait$Helper.initializeCommandObject(Controller.groovy:456)
... 39 common frames omitted
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)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1717)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1273)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657)
... 45 common frames omitted

Any idea on how to start investigating this issue? Obviously if you know the answer straight you're welcome :).

What I tried

  • I have tried step debugging the instantiation of the Command object (that's why I have added an empty constructor, tobe able to set a breakpoint), but I am struggling with the complexity of the ApplicationContext layer and I feel that I am not close to understand why this Service bean is not injected.
    The issue happens in grails.artefact.Controller#initializeCommandObject

  • I installed the grails console plugin, and once the application starts up, I have tried accessing the communityService bean with ctx.communityService and got an propert bean in response: com.example.pr.domain.$CommunityServiceImplementation@5deeeb12

  • In the same grails console, when I try to execute the following code:

def controller = ctx.getBean("com.example.pr.http.api.CommunityController")
  
controller.initializeCommandObject(com.examplep.pr.request.GetCommunitiesRequest, "request")

I get the same exception

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)}

Any help appreciated.

UPDATE 1

Tried to remove the @Autowired annotation in the command object. It still fails, with a slightly different exception (ConversionNotSupportedException):


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' for property 'communityService': no matching editors or conversion strategy found

    org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:595)
    org.springframework.beans.AbstractNestablePropertyAccessor.convertForProperty(AbstractNestablePropertyAccessor.java:609)
    org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:219)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.convertForProperty(AbstractAutowireCapableBeanFactory.java:1751)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1707)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1447)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:392)
    grails.artefact.Controller$Trait$Helper.initializeCommandObject(Controller.groovy:456)
    Script1.run(Script1.groovy:3)
    org.grails.plugins.console.ConsoleService.eval(ConsoleService.groovy:37)
    org.grails.plugins.console.ConsoleController.execute(ConsoleController.groovy:53)
    java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
    org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
    org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1043)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    grails.plugin.springsecurity.rest.RestLogoutFilter.doFilter(RestLogoutFilter.groovy:82)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
    org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
    org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118)
    grails.plugin.springsecurity.web.UpdateRequestContextHolderExceptionTranslationFilter.doFilter(UpdateRequestContextHolderExceptionTranslationFilter.groovy:64)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    grails.plugin.springsecurity.web.filter.GrailsHttpPutFormContentFilter.doFilterInternal(GrailsHttpPutFormContentFilter.groovy:54)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter.doFilter(GrailsAnonymousAuthenticationFilter.groovy:54)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:158)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    grails.plugin.springsecurity.rest.RestAuthenticationFilter.doFilter(RestAuthenticationFilter.groovy:136)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doFilter(AbstractPreAuthenticatedProcessingFilter.java:124)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter.doFilter(MutableLogoutFilter.groovy:64)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    grails.plugin.springsecurity.web.SecurityRequestHolderFilter.doFilter(SecurityRequestHolderFilter.groovy:58)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
    org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
    java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    java.base/java.lang.Thread.run(Thread.java:834)


Solution

  • It turns out that I had mistakenly created a copy of my CommunityService Data Service in a different package. Grails got confused when trying to instantiate the Command object when it tried to autowire it and somehow did not report that there were 2 candidates (?).

    I had two identical copies of the Service:

    • com.example.pr.domain.CommunityService (generated by generate-all)
    • com.example.pr.service.data.CommunityService (attempt to repackage to a more logical package structure and not place the Service in the domain package)