2

I have a need to dynamically create and destroy HttpClient objects, to correspond with clients registering/deregistering themselves with my Micronaut application. As part of this I want to add them as beans to the application context so that they automatically use the custom HttpFilters also in the project.

I thought it would be fairly simple to manage these beans using the ApplicationContext bean methods with a name Qualifier to keep track of them, but the way these APIs seem to behave is baffling to me:

  1. applicationContext.createBean(HttpClient.class, Qualifiers.byName("myLabel"), myUrl) fails with:
io.micronaut.context.exceptions.NoSuchBeanException: No bean of type 
[io.micronaut.http.client.HttpClient] exists. Make sure the bean is not disabled by bean 
requirements (enable trace logging for 'io.micronaut.context.condition' to check) and if the 
bean is enabled then ensure the class is declared a bean and annotation processing is 
enabled (for Java and Kotlin the 'micronaut-inject-java' dependency should be configured as 
an annotation processor).

Why does it matter if the bean exists? I'm trying to create it!

  1. applicationContext.findBean(HttpClient.class) fails with:
io.micronaut.context.exceptions.BeanInstantiationException: Error instantiating bean of type  [io.micronaut.http.client.DefaultHttpClient]

Message: Missing bean argument [LoadBalancer loadBalancer] for type: io.micronaut.http.client.DefaultHttpClient. Required arguments: LoadBalancer loadBalancer,HttpClientConfiguration configuration,String contextPath,HttpClientFilterResolver filterResolver

Why is it trying to create it? I just want to find it!

(NOTE. applicationContext.getBean(HttpClient.class, Qualifiers.byName("myLabel")) may work here, but because I haven't been able to resolve point one I haven't been able to verify this)

  1. applicationContext.destroyBean(HttpClient.class) doesn't allow a Qualifier to be specified in the method which means I can't use it to remove the bean from the context. It also return null after bean creation without a qualifier (applicationContext.createBean(HttpClient.class, myUrl)) which suggests it can't find the created bean anyway...

I assume I'm using the wrong API here, but what is the correct one?

All-in-all I'm thoroughly confused - any help on the proper use of these APIs would be welcome.

Burt Beckwith
  • 75,342
  • 5
  • 143
  • 156
user2521119
  • 165
  • 2
  • 14

1 Answers1

0

The HttpClient is the interface intended to define a an http-client but not a bean (note that the default ~ DefaultHttpClient ~ implementation afforded by Micronaut is not a bean). Hence any calls to the mentioned ApplicationContext context lookup or instantiation methods will all result in errors:

  • ApplicationContext#createBean
  • ApplicationContext#findBean
  • ApplicationContext#getBean
  • ... // other instantiation methods

Note this is intentionally made by design since the HttpClient API is intended to be injected automatically and operating through AOP advice.

You can either inject a raw (with default methods) HttpClient:

@Singleton
public class MyBeanIml implements MyBean {

  @Client
  HttpClient client;

  // ...
}

Or for custom methods, generated implementation, you can use the HttpClient declarative implementation:

@Client("endpoint-uri")
public interface MyHttpClient {

  @Get
  String query();

  // ...
}

Without specifying your exact requirements for creating custom (dynamic by the way is too vague in this context) HttpClients, the way you can go with is to have a custom @Prototyped bean wrapping a injected HttpClient that can be instantiated with your custom parameters and where you delegate your methods to the wrapped HttpClient or implement your custom methods:

@Prototype
public class CustomHttpClientImpl implements CustomHttpClientImpl.CustomHttpClient {

    @Inject
    @Client
    private final HttpClient httpClient;

    private final String endpoint;

    public CustomHttpClientImpl(@Parameter String endpoint, HttpClient httpClient) {
        this.httpClient = httpClient;
        this.endpoint = endpoint;
    }

        @Override
    public String query() {
        return this.httpClient.toBlocking()
                .exchange(HttpRequest.GET(URI.create(this.endpoint)), String.class)
                .body();
    }
    
    // delegate to the `httpClient` bean or implement your custom API querying methods

    public interface CustomHttpClient {

        String query();
    }
}

Then you can create your Prototyped bean instances using the ApplicationContext:

applicationContext.createBean(CustomHttpClient.class, "som-uri-for-custom-client");
tmarwen
  • 15,750
  • 5
  • 43
  • 62