Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation needed: Low-level HTTP-client from Micronaut needs to be in a Micronaut bean in order to be injected #11411

Open
4 tasks done
andersaaberg opened this issue Sep 26, 2019 · 1 comment

Comments

@andersaaberg
Copy link
Contributor

andersaaberg commented Sep 26, 2019

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. Create a new Grails 4 project: curl -O start.grails.org/test-micronaut-http.zip -d version=4.0.0 -d profile=rest-api
  2. Create a new controller with a low-level HTTP-client from Micronaut:
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.annotation.Client
import javax.inject.Inject

class LowLevelClientController {

    @Inject
    @Client('http://www.google.com')
    RxHttpClient rxHttpClient

    def index() {
        render rxHttpClient.retrieve('/search?q=hello').blockingSingle()
    }
}
  1. Start the application: ./gradlew bootRun
  2. Call the controller: curl -i http://localhost:8080/lowLevelClient

Expected Behaviour

The rxHttpClient bean should be injected and the call should not fail.

Actual Behaviour

The rxHttpClient bean could not be injected and the call fails with this stacktrace:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'test.micronaut.http.LowLevelClientController': Unsatisfied dependency expressed through field 'rxHttpClient'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.micronaut.http.client.RxHttpClient' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax.inject.Inject(), @io.micronaut.http.client.annotation.Client(path=, id=, configuration=class io.micronaut.http.client.HttpClientConfiguration, value=http://www.google.com, errorType=class io.micronaut.http.hateoas.JsonError)}
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596)
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1105)
        at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:73)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
        at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
        at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

Environment Information

  • Operating System: Ubuntu 18.04
  • Grails Version: 4.0.0
  • JDK Version: 8.0.222-zulu

Example Application

https://github.com/andersaaberg/test-micronaut-http/tree/master

Analysis

I had a talk with James Kleeh on slack, who explained that @client is a custom scope and that annotation is not visible to Micronaut because its not being injected into a Micronaut bean. He did not think that the issue can be solved. So I have made this ticket just to document the following workaround and I hope that someone will include it in the Grails documentation.

Workaround to inject low-level HTTP-client from Micronaut in Grails 4:

  1. Add a new Micronaut bean (that contains the RxHttpClient) in /src/main/groovy/...:
import javax.inject.Inject
import javax.inject.Singleton
import groovy.transform.CompileStatic
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.annotation.Client

@CompileStatic
@Singleton
class LowLevelClient {

    @Client('http://www.google.com')
    @Inject
    RxHttpClient rxHttpClient
}
  1. Inject the rxHttpClient in the controller using the Micronaut bean (LowLevelClient):
import javax.inject.Inject

class LowLevelClientController {

    @Inject
    LowLevelClient lowLevelClient

    def index() {
        render lowLevelClient.rxHttpClient.retrieve('/search?q=hello').blockingSingle()
    }
}

The workaround is in the example project branch called "workaround": https://github.com/andersaaberg/test-micronaut-http/tree/workaround

Workaround to manually instantiate low-level HTTP-client from Micronaut in Grails 4, but still get configuration from application.yml (but possible missing other features):

It is also possible to manually instantiate the low-level HTTP-client from Micronaut in Grails 4, but then it does not load micronaut.http.client.* configuration from application.yml. This can be added by injecting the DefaultHttpClientConfiguration bean and then adding it to the DefaultHttpClient constructor. However, this workaround is missing all other injections than DefaultHttpClientConfiguration in the rxHttpClient, so other rxHttpClient features might not work with this workaround. Hence the workaround above is preferred.
Example:

import io.micronaut.http.client.DefaultHttpClient
import io.micronaut.http.client.DefaultHttpClientConfiguration
import io.micronaut.http.client.RxHttpClient
import javax.annotation.PostConstruct
import javax.inject.Inject

class LowLevelClientController {

    @Inject
    DefaultHttpClientConfiguration defaultHttpClientConfiguration

    RxHttpClient rxHttpClient

    @PostConstruct
    void initHttpClient() {
        rxHttpClient = new DefaultHttpClient(new URL('http://google.com'), defaultHttpClientConfiguration)
    }

    def index() {
        render rxHttpClient.retrieve('/search?q=hello').blockingSingle()
    }
}

The workaround is in the example project branch called "workaround2": https://github.com/andersaaberg/test-micronaut-http/tree/workaround2

@val235
Copy link

val235 commented Oct 7, 2021

Thanks for posting this, been battling this same problem.

Like you suggested, I believe the issue is that Micronaut will not look inside grails artifacts (Controller in this case) to perform its own annotation processing. However it will process annotations on top level classes. So another solution, similar to what you provided, is to create Micronaut Clients inside their own classes/interfaces by extending the core Micronaut interfaces.

@Client('http://www.google.com')
interface LowLevelClient extends RxHttpClient{

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants