My project has following setup
#Module
abstract class CoreModule {
#Provides
#Named("text1")
fun textOne() = "text1"
#Provides
#Named("text2")
fun textTwo() = "text2"
}
#Component(modules=[CoreModule::class])
interface CoreComponent{
#Named("text1")
fun textOne(): String
}
#Component(
dependencies = [CoreComponent::class]
modules=[AppModule::class]
)
interface AppComponent()
#Module()
class AppModule {
fun getStringDouble(#Named("text1") text1: String) = text1 + text1
}
Here I have 2 components CoreComponent which provides dependencies to AppComponent. Now I want to provide only text1 to AppComponent.
Since I have added #Named("text1") in CoreComponent to denote which string to provide to AppComponent. It forces me to use #Named("text1") in AppModule as well, which I don't want.
How can I create provision method in CoreComponent to provide only text1 to AppComponent in such a way, that I don't have to use #Named everywhere in AppComponent
The simplest solution would probably be to simply bind/provide it under a different key, a plain String in your example.
#Binds // bind it without any qualifier
fun plainText1(#Named("text1") text1: String) : String
If you do have multiple values for the same type then qualifiers would probably be easier to use in the long run though, since they allow you proper naming of the keys used.
Keep in mind that you can also create your own qualifiers and don't have to use #Named, it's just available by default.
An alternative to your setup altogether would be to use (Sub)components for encapsulation which would allow you to only ever return the value produced without binding the whole component dependency.
you can remove #Named("<name>") from Provider method in CoreModule and CoreComponent.
instead, you can create a custom Qualifier annotation like this
#Qualifier
#Documented
#Retention(RetentionPolicy.RUNTIME)
annotation class TextTwoName {
}
and do following changes
In CoreModule, use #TextTwoName in textTwo() for instead of #Named("text2") and remove #Named("text1") from fun textOne() = "text1"
In CoreComponent, remove #Named("text1") from fun textOne(): String while name function name textOne doesn't matter here only return type matters. it will take fun textOne() = "text1" from CoreModule
If you want to expose fun textTwo() = "text2" then you can add #TextTwoName annotation in the CoreComponent's fun textOne():String method.
Related
New to DI and guice..
I want to use a service (StoreLevelClient) This is a class defined by other team.
I inject this class in my main file like this:
class ClientAccessor {
companion object {
private val LOGGER = KotlinLogging.logger { }
}
private val myStoreLevelClient: StoreLevelClient =
Guice.createInjector(ServiceModule()).getInstance(StoreLevelClient::class.java)
And made a module file for the StoreLevelClient like below:
class ServiceModule : AbstractModule() {
#Provides
#Singleton
fun getClient(myServiceClient : KasServiceClient): StoreLevelClient {
return StoreLevelClient(myServiceClient, AppConfigObject.trackedDocument, AppConfigObject.appConfigFallback)
}
It gave me errors:
Caused by: com.google.inject.ProvisionException: Unable to provision, see the following errors:
3
2022-05-20T18:27:50.800-07:00
1) No implementation for com.kasservice.KasServiceClient was bound.
4
2022-05-20T18:27:50.800-07:00
while locating com.kasservice.KasServiceClient
5
2022-05-20T18:27:50.800-07:00
for the 1st parameter of com.myservice.dependency.ServiceModule.getClient
The KasServiceClient is also from other's
So I #Provides it in the ServiceModule as well:
#Provides
#Singleton
fun getService(
cloudAuthCredentialVisitor: CloudAuthDefaultCredentialsVisitor,
metricsAwareCallVisitor: MetricsAwareCallVisitor,
#Named(BINGBONG_SERVICE_CLIENT_RETRY_STRATEGY)
retryStrategy: RetryStrategy<*>
): KasServiceClient {
val domain = AppConfig.findString(DOMAIN)
val realm = AppConfig.getRealm().name()
val qualifier = "$domain.$realm"
return ClientBuilder()
.remoteOf(KasServiceClient::class.java)
.withConfiguration(qualifier)
.withCallVisitors(cloudAuthCredentialVisitor, metricsAwareCallVisitor, CallAttachmentVisitor(Calls.retry(retryStrategy)))
.newClient()
}
But it gave me errors like below:
Could not find a suitable constructor in com.amazon.coral.client.cloudauth.CloudAuthDefaultCredentialsVisitor. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
Could not find a suitable constructor in com.amazon.metrics.declarative.client.MetricsAwareCallVisitor. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
The CloudAuthDefaultCredentialsVisitor and MetricsAwareCallVisitor are use #Provides and instantiate already.
So I don't know why guice can't find them...??
Any idea for this?? I wonder I have some mistake when using Guice. But I have hard time to debug and find
Solved the problem by changing the way of inject:
Instead use:
class ClientAccessor {
companion object {
private val LOGGER = KotlinLogging.logger { }
}
private val myStoreLevelClient: StoreLevelClient =
Guice.createInjector(ServiceModule()).getInstance(StoreLevelClient::class.java)
Tried this:
class ClientAccessor #Inject constructor(private val myStoreLevelClient: StoreLevelClient){
companion object {
private val LOGGER = KotlinLogging.logger { }
}
Reason:
use #Inject instead of using the createInjector manually on particular modules, let guice inject it for us. When I tried to directly use createInjector in instantiating in my code, it will only lookup the specified module and not able to load other modules.
Lets say I want to register a class with Dagger that doesn't have the #Inject annotation on the constructor and that also takes dependencies in its constructor.
i.e.
// Java
class MyClass {
public MyClass(MyDependency dependency) { ... }
}
// Kotlin
class MyClass(private val dependency: MyDependency) { ... }
Is there a way to register this class without having to manually wire the dependencies into the constructor?
// This is how I currently do it. I must list all constructor dependencies
// and then pass them through to the constructor by hand
#Provides
public MyClass getMyClass(MyDependency depdencency) {
return new MyClass(dependency);
}
// Something like this is how I'd like to do it
// In this case, the class is registered _as if_ it had the #Inject on the constructor
#ProvideEvenWithoutAnInject
public abstract MyClass getMyClass();
Does Dagger offer something like the #ProvideEvenWithoutAnInject annotation shown above?
If you can't put an #Inject annotation on the constructor then Dagger can't create the object for you. You'll need to use a #Provides annotated method with a module or #BindsInstance with your component to add it. (Or as a component dependency from a provision method if you want more extra steps)
I am making a library,
In one of the functionality, I receive an object and I have to perform an operation on fields and save them on a Map.
The object can have a field of type custom class which will again have fields and in that case, I'll need a nested hashmap. To do that I'll need to call my function recursively if the type of field in a custom class.
Now the problem is, how will I check if the type of field is a custom class or not, right now I am doing it by package name but have to make it general
private fun getAllFields(`object`: Any): MutableMap<String, Any> {
val map: MutableMap<String, Any> = HashMap()
val internalMap: MutableMap<String, Any> = HashMap()
for (field in `object`::class.java.declaredFields.toMutableList()) {
field.isAccessible = true
if (field.type.name.contains("com.example")) {
internalMap.putAll(getAllFields(field.get(`object`)))
}
As you're using reflection, you could introduce an annotation:
package org.your.library
#Target(AnnotationTarget.CLASS)
#Retention(AnnotationRetention.RUNTIME)
annotation class YourCoolAnnotation
Which then can be used by the users of your library to annotate the classes that they want to be nested:
package com.example.libraryuser
#YourCoolAnnotation
class MyCustomClass
Then you can replace your:
if (field.type.name.contains("com.example")) {
With:
if (field.type.isAnnotationPresent(YourCoolAnnotation::class.java)) {
You could also specify the annotation to be only used on fields which would make this a lot more dynamic:
#Target(AnnotationTarget.PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
annotation class YourCoolAnnotation
and then check if the field has the annotation and not the type itself:
if (field.isAnnotationPresent(YourCoolAnnotation::class.java)) {
How can I add a qualifier to distinguish between these two beans? I know I need to use the #Qualifier annotation but I am not sure how to add it in the beans and then how to create the autowired object with reference to the appropriate bean.
#Configuration
#Slf4j
#PropertySources(PropertySource("classpath:application.properties"),
PropertySource(value = ["file:\${credentials.config}"]))
class CredentialsConfig(#Autowired private val env: Environment) {
#Bean fun getCredentials(): Credentials? {
val user: String = env.getRequiredProperty("user1")
val pass: String = env.getRequiredProperty("pass1")
return Credentials.info(user, pass)
}
#Bean fun getCredentials2(): Credentials {
val user: String = env.getRequiredProperty("user2")
val pass: String = env.getRequiredProperty("pass2")
return Credentials.info(user, pass)
}
}
In situations like this, I find it beneficial to explicitly name my beans so it is more clear which one I am picking. Otherwise, you will end up with what Spring decides to call it (based on the method name). When we want to inject a bean, but there are more than one of them, we use the #Qualifer annotation at the injection point, specifying the name of the bean we care about.
So...
// In CredentialsConfig
#Bean("firstCredentials) fun firstCredentials(): Credentials = TODO()
#Bean("secondCredentials) fun secondCredentials(): Credentials = TODO()
And when wiring in one of these, you can add a #Qualifier to pick your specific implementation (note, if you use constructor injection, you don't need #Autowired):
#Component
class MyComponent(#Qualifier("firstCredentials") creds: Credentials) { ... }
You could just add #Qualifier with bean name whenever you do an Autowire of Credentials.
#Autowired
#Qualifier("getCredentials")
Credentials credentials;
I have an interface that has 20 or so annotated implementations. I can inject the correct one if I know which I need at compile time, but I now need to dynamically inject one based on runtime parameters.
As I understood the documentation, I would have to use 20 or so Provider<T> injections and then use the one I need, which seems rather excessive to me. Is there a way to have something like an inst(Provider<T>).get(MyAnnotation.class) to bind a specific implementation, and then have only that Provider injected into my class?
Inject a MapBinder.
In your module, load the bindings into the MapBinder, then make your runtime parameters injectable as well. This example is based on the one in the documentation:
public class SnacksModule extends AbstractModule {
protected void configure() {
MapBinder<String, Snack> mapbinder
= MapBinder.newMapBinder(binder(), String.class, Snack.class);
mapbinder.addBinding("twix").to(Twix.class);
mapbinder.addBinding("snickers").to(Snickers.class);
mapbinder.addBinding("skittles").to(Skittles.class);
}
}
Then, in your object, inject the Map and the parameter. For this example I will assume you've bound a java.util.Properties for your runtime parameters:
#Inject
public MyObject(Map<String, Provider<Snack>> snackProviderMap, Properties properties) {
String snackType = (String) properties.get("snackType");
Provider<Snack> = snackProviderMap.get(property);
// etc.
}
Note, with the same MapBinder you can inject either a simple Map<String, Snack> or a Map<String, Provider<Snack>>; Guice binds both.
If all you want is to get an instance programmatically, you can inject an Injector. It's rarely a good idea--injecting a Provider<T> is a much better idea where you can, especially for the sake of testing--but to get a binding reflectively it's the only way to go.
class YourClass {
final YourDep yourDep; // this is the dep to get at runtime
#Inject YourClass(Injector injector) {
YourAnnotation annotation = deriveYourAnnotation();
// getProvider would work here too.
yourDep = injector.getInstance(Key.get(YourDep.class, annotation));
}
}
If you're trying write a Provider that takes a parameter, the best way to express this is to write a small Factory.
class YourDepFactory {
#Inject #A Provider<YourDep> aProvider;
#Inject #B Provider<YourDep> bProvider;
// and so forth
Provider<YourDep> getProvider(YourParameter parameter) {
if (parameter.correspondsToA()) {
return aProvider;
} else if (parameter.correspondsToB()) {
return bProvider;
}
}
YourDep get(YourParameter parameter) {
return getProvider(parameter);
}
}