From this question, it is possible to inject map with enums?
For example, i have enum:
class enum SomeEnum (val value) {
ONE("one"),
TWO("two"),
THREE("three")
}
And i have some interface with implementations:
interface SomeInterface {
}
#Component
#Qualifier("one")
class OneClass: SomeInterface {
...
}
#Component
#Qualifier("two")
class TwoClass: SomeInterface {
...
}
#Component
#Qualifier("three")
class ThreeClass: SomeInterface {
...
}
But such injection not works:
#Component
#ConfigurationProperties
class SomeProperties {
#Autowired
lateinit var someMap: Map<SomeEnum, SomeInterface>
}
I want to autoinject someMap. How can i fix it, to make such code on spring framework side?
var someMap: Map<SomeEnum, SomeInterface> = Map.of(ONE, oneClass,
TWO, twoClass,
THREE, threeClass)
// Where oneClass, twoClass, threeClass - beans instances
First of all you misuse #Qualifier annotation. It's intended not to name the bean but to help Spring to choose single autowire candidate among multiple. #Qualifier is only meaningful at injection point - either in pair with #Autowired for class properties
#Autowired
#Qualifier("one")
lateinit var someImpl: SomeInterface
or for injection into method/constructor arguments
#Bean
fun createSomeService(#Qualifier("two") someImpl: SomeInterface): SomeService {
return SomeService(someImpl)
}
//or
class SomeService(#Qualifier("three") private val someImpl: SomeInterface) {
...
}
Correct way to name a bean is simply #Component("name") (or #Service("name"))
As far as I know, you can only autowire Maps with String keys, where key is the name of the bean. However you can easily convert this map into enum-key map like this:
interface SomeInterface
#Component("one")
class OneClass: SomeInterface
#Component("two")
class TwoClass: SomeInterface
#Component("three")
class ThreeClass: SomeInterface
#Component
#ConfigurationProperties
class SomeProperties(implMap: Map<String, SomeInterface>) {
val someMap: Map<SomeEnum, SomeInterface> = implMap.mapKeys {
SomeEnum.values().firstOrNull { e -> e.value == it.key }!!
}
}
UPDATED:
or using field injection (not recommended)
#Component
#ConfigurationProperties
class SomeProperties {
private lateinit var someMap: Map<SomeEnum, SomeInterface>
#Autowired
private lateinit var implMap: Map<String, SomeInterface>
#PostConstruct
fun initMap() {
someMap = implMap.mapKeys {
SomeEnum.values().firstOrNull { e -> e.value == it.key }!!
}
}
}
Not quite sure what you want to do, but from my point of view you dont need this mapping. I assume you want to know which implementation to use for certain case. So just autowire a list or set of your interface and iterate trough it to find the right implementation. (I show the stuff in Java)
#Autowired
List<SomeInterface> someInterfaces;
In this list you will have all the injected implementations of this interface. If you still need an Enum to see which implementation to use, just add this as an attribute to every of your implementation class. So you can get the Enum value by its implementation.
EDIT:
Create a config class and autowire the list of implementations. In this config class you create a Bean of your map.
#Configuration
public class MyMapConfig {
#Autowired
List<SomeInterface> someInterfaces;
#Bean
public Map<SomeEnum, SomeInterface> myMap() {
Map<SomeEnum, SomeInterface> map = new HashMap<>();
someInterfaces.forEach(s -> {
// logic here to add correct Enum to its implementation.
map.put(SomeEnum.A, s);
});
return map;
}
public enum SomeEnum {
A, B, C
}
}
Then you can autowire your map anywhere you want:
#Autowired
Map<SomeEnum, SomeInterface> myMap;
Related
Let me show what I mean by code.
Interface:
interface MyInterface {
}
I have 2 implementation classes:
#Singleton
class Implementation1 #Inject constructor(
private val gson: Gson,
) : MyInterface {
}
#Singleton
class Implementation2 #Inject constructor(
#ApplicationContext private val context: Context,
) : MyInterface {
}
I need a repository class with list of implementations of MyInterface:
#Singleton
class MyRepository #Inject constructor(
private val implementations: List<MyInterface>,
) {
}
Problem part: I am trying to inject as below:
#InstallIn(SingletonComponent::class)
#Module
object MyDiModule {
#Provides
fun providesImplementations(
imp1: Implementation1,
imp2: Implementation2,
): List<MyInterface> {
return listOf(imp1, imp2)
}
}
But I get compilation error:
/home/sayantan/AndroidStudioProjects/example/app/build/generated/hilt/component_sources/debug/com/example/ExampleApplication_HiltComponents.java:188: error: [Dagger/MissingBinding] java.util.List<? extends com.example.MyInterface> cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint,
^
Any way to achieve this?
This might be late but would like to drop my thoughts.
Firstly, you need to annotate your #Provides methods in the module class because dagger would certainly throw an error of multiple bindings of MyInterface.
For example:
enum class ImplType {
Impl1, Impl2
}
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class ImplTypeAnnotation(val type: ImplType)
#Singleton
#Provides
#ImplTypeAnnotation(ImplType.Impl1)
fun provideImplementation1(impl1: Implementation1): MyInterface
// Do the same for Implementation2
Secondly, once these are done, you need to individually inject them into the Repository class. e.g.
class MyRepository #Inject constructor(
#ImplTypeAnnotation(ImplType.Impl1) private val implementation1: MyInterface,
#ImplTypeAnnotation(ImplType.Impl2) private val implementation2: MyInterface
) {
// get the implementations as list here using Kotlin listOf()
}
NB: I'm not sure if providing List<MyInterface> would work.
Hope this helps!
I do not know how correct this answer is, but it works for me, so posting as answer.
This can be done using the annotation #JvmSuppressWildcards.
In the above example, everything is fine, I just need to add this annotation to the class where the dependencies are being injected, i.e. the MyRepository class.
#JvmSuppressWildcards
#Singleton
class MyRepository #Inject constructor(
private val implementations: List<MyInterface>,
) {
}
I was seeing the same problem in the project I've been working on, but moving from List<> to Array<> seemed to fix it for me.
So in your case the hilt injection would look like:
#Provides
fun providesImplementations(
imp1: Implementation1,
imp2: Implementation2,
): Array<MyInterface> {
return arrayOf(imp1, imp2)
}
Then used with:
#Singleton
class MyRepository #Inject constructor(
private val implementations: Array<MyInterface>,
) {
}
I have a configuration providing a single bean and a configuration providing a list of beans. All these beans have the same type.
When I start up an application context with these configurations, I see that an autowired list of the bean type only contains the single bean. I want it to include all beans of that type. I use Spring 5.2.0.
I have boiled it down to one configuration: if I provide a single bean and a list of beans, only the single bean will be used.
This is reproduced in the following test. It fails, because the list only contains "A" and "D" (which shows it did not autowire the list of beans):
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = { TestConfiguration.class })
class AutowiringListsTest {
#Autowired
private List<TestBean> testBeanList;
#Test
void testThatBothConfigurationsContributeToBeanList() {
final List<String> idList = testBeanList.stream().map(TestBean::getId).sorted().collect(Collectors.toList());
assertThat(idList, hasItems("A", "B", "C", "D"));
}
#Configuration
public static class TestConfiguration {
#Bean
public TestBean someBean() {
return new TestBean("A");
}
#Bean
public List<TestBean> someMoreBeans() {
return Arrays.asList(new TestBean("B"), new TestBean("C"));
}
#Bean
public TestBean anotherBean() {
return new TestBean("D");
}
}
public static class TestBean {
private final String id;
public TestBean(final String id) {
this.id = id;
}
private String getId() {
return id;
}
}
}
I want to get this to run so that multiple modules can provide beans of a certain type.
Some modules want to provide multiple beans and their number depends on a property.
Some modules will always provide one bean.
The module using the beans (autowiring them as list) should autowire all beans.
How can I get this to run? In what scenario does Spring's behavior make sense?
I can work around the issue by introducing a TestBeanFactory. Each configuration that wants to contribute to the list of TestBeans instead provides a factory.
#Configuration
public static class TestConfiguration {
/** Implemented once in the configuration that defines <code>TestBean</code>. */
#Bean
public List<TestBean> testBeansFromFactory(Collection<TestBeanFactory> factories) {
return factories.stream().map(TestBeanFactory::createTestBeans).flatMap(Collection::stream)
.collect(toList());
}
// Further methods can be defined in various configurations that want to add to the list of TestBeans.
#Bean
public TestBeanFactory someBean() {
return () -> Arrays.asList(new TestBean("A"));
}
#Bean
public TestBeanFactory someMoreBeans() {
return () -> Arrays.asList(new TestBean("B"), new TestBean("C"));
}
#Bean
public TestBeanFactory anotherBean() {
return () -> Arrays.asList(new TestBean("D"));
}
}
public static class TestBean { ... }
public static interface TestBeanFactory {
public Collection<TestBean> createTestBeans();
}
That works and is only slightly more code.
M.Deinum makes a point in the comments for Spring's behavior being consistent:
As you defined a bean of type List it will be used. Autowiring is based on type, it will try to detect the bean of the certain type. The collection (and map as well) is a special one looking up all dependencies of the given type.
If there are two beans of the same type but with different names. Will spring Autowire the bean based on the name without us adding #Qualifier on the variable? I saw in the documentation, "As a fallback Spring uses the bean name as a default qualifier value".
#Component
class A{
}
#Component
class B extends A{
}
class C{
#AutoWired
A a;
//Will a be of type class A, even without #Qualifier...?
}
If there are two beans of the same type but with different names. Will spring Autowire the bean based on the name without us adding #Qualifier on the variable?
#Autowire in the first place cares about type, later about the name. You will get exception saying that there are multiple candidates for injection while only 1 is expected.
#Resource on the other hand, cares about name first, type later.
In this case, B will get injected because there is an A and a B for Spring to choose from and only one of them matches what the #Autowired field is asking for (Class B, because A is not assignable to B).
However, if you had two B's, you would have to qualify it or mark one of them as the primary.
For example, given this:
#Configuration
public class MyConfig {
#Bean
public B example1() {
return new B();
}
#Bean
public B example2() {
return new B();
}
}
Well, now you have two instances of B, with different names. You can fix this one of two ways:
Qualify your Autowire
Note that I'm using field injection here, you really should use constructor, I'm doing it to save space.
#Component
public class SomeComponent {
#Autowired
#Qualifier("example1")
private B b;
}
Or
Mark A Bean as Primary
Redefine the beans, marking one as #Primary
#Configuration
public class MyConfig {
#Bean
#Primary // <-------- NEW!
public B example1() {
return new B();
}
#Bean
public B example2() {
return new B();
}
}
And then inject without needing to name it:
#Component
public class SomeComponent {
#Autowired // (Will pick Primary)
private B b;
}
I need to inject a spring service class in the generated mapper implementation, so that I can use it via
#Mapping(target="x", expression="java(myservice.findById(id))")"
Is this applicable in Mapstruct-1.0?
As commented by brettanomyces, the service won't be injected if it is not used in mapping operations other than expressions.
The only way I found to this is :
Transform my mapper interface into an abstract class
Inject the service in the abstract class
Make it protected so the "implementation" of the abstract class has access
I'm using CDI but it should be the samel with Spring :
#Mapper(
unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE,
componentModel = "spring",
uses = {
// My other mappers...
})
public abstract class MyMapper {
#Autowired
protected MyService myService;
#Mappings({
#Mapping(target="x", expression="java(myservice.findById(obj.getId())))")
})
public abstract Dto myMappingMethod(Object obj);
}
It should be possible if you declare Spring as the component model and add a reference to the type of myservice:
#Mapper(componentModel="spring", uses=MyService.class)
public interface MyMapper { ... }
That mechanism is meant for providing access to other mapping methods to be called by generated code, but you should be able to use them in the expression that way, too. Just make sure you use the correct name of the generated field with the service reference.
Since 1.2 this can be solved with a combination of #AfterMapping and #Context.. Like this:
#Mapper(componentModel="spring")
public interface MyMapper {
#Mapping(target="x",ignore = true)
// other mappings
Target map( Source source, #Context MyService service);
#AfterMapping
default void map( #MappingTarget Target.X target, Source.ID source, #Context MyService service) {
target.set( service.findById( source.getId() ) );
}
}
The service can be passed as context.
A nicer solution would be to use an #Context class which wrap MyService in stead of passing MyService directly. An #AfterMapping method can be implemented on this "context" class: void map( #MappingTarget Target.X target, Source.ID source ) keeping the mapping logic clear of lookup logic. Checkout this example in the MapStruct example repository.
What's worth to add in addition to the answers above is that there is more clean way to use spring service in mapstruct mapper, that fits more into "separation of concerns" design concept, called "qualifier". Easy re-usability in other mappers as a bonus.
For sake of simplicity I prefer named qualifier as noted here http://mapstruct.org/documentation/stable/reference/html/#selection-based-on-qualifiers
Example would be:
import org.mapstruct.Mapper;
import org.mapstruct.Named;
import org.springframework.stereotype.Component;
#Component
#Mapper
public class EventTimeQualifier {
private EventTimeFactory eventTimeFactory; // ---> this is the service you want yo use
public EventTimeQualifier(EventTimeFactory eventTimeFactory) {
this.eventTimeFactory = eventTimeFactory;
}
#Named("stringToEventTime")
public EventTime stringToEventTime(String time) {
return eventTimeFactory.fromString(time);
}
}
This is how you use it in your mapper:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
#Mapper(componentModel = "spring", uses = EventTimeQualifier.class)
public interface EventMapper {
#Mapping(source = "checkpointTime", target = "eventTime", qualifiedByName = "stringToEventTime")
Event map(EventDTO eventDTO);
}
I am using Mapstruct 1.3.1 and I have found this problem is easy to solve using a decorator.
Example:
#Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE,
componentModel = "spring")
#DecoratedWith(FooMapperDecorator.class)
public interface FooMapper {
FooDTO map(Foo foo);
}
public abstract class FooMapperDecorator implements FooMapper{
#Autowired
#Qualifier("delegate")
private FooMapper delegate;
#Autowired
private MyBean myBean;
#Override
public FooDTO map(Foo foo) {
FooDTO fooDTO = delegate.map(foo);
fooDTO.setBar(myBean.getBar(foo.getBarId());
return fooDTO;
}
}
Mapstruct will generate 2 classes and mark the FooMapper that extends FooMapperDecorator as the #Primary bean.
I can't use componentModel="spring" because I work in a large project that doesn't use it. Many mappers includes my mapper with Mappers.getMapper(FamilyBasePersonMapper.class), this instance is not the Spring bean and the #Autowired field in my mapper is null.
I can't modifiy all mappers that use my mapper. And I can't use particular constructor with the injections or the Spring's #Autowired dependency injection.
The solution that I found: Using a Spring bean instance without using Spring directly:
Here is the Spring Component that regist itself first instance (the Spring instance):
#Component
#Mapper
public class PermamentAddressMapper {
#Autowired
private TypeAddressRepository typeRepository;
#Autowired
private PersonAddressRepository personAddressRepository;
static protected PermamentAddressMapper FIRST_INSTANCE;
public PermamentAddressMapper() {
if(FIRST_INSTANCE == null) {
FIRST_INSTANCE = this;
}
}
public static PermamentAddressMapper getFirstInstance(){
return FIRST_INSTANCE;
}
public static AddressDTO idPersonToPermamentAddress(Integer idPerson) {
//...
}
//...
}
Here is the Mapper that use the Spring Bean accross getFirstInstance method:
#Mapper(uses = { NationalityMapper.class, CountryMapper.class, DocumentTypeMapper.class })
public interface FamilyBasePersonMapper {
static FamilyBasePersonMapper INSTANCE = Mappers.getMapper(FamilyBasePersonMapper.class);
#Named("idPersonToPermamentAddress")
default AddressDTO idPersonToPermamentAddress(Integer idPerson) {
return PermamentAddressMapper.getFirstInstance()
.idPersonToPermamentAddress(idPersona);
}
#Mapping(
source = "idPerson",
target="permamentAddres",
qualifiedByName="idPersonToPermamentAddress" )
#Mapping(
source = "idPerson",
target = "idPerson")
FamilyDTO toFamily(PersonBase person);
//...
Maybe this is not the best solution. But it has helped to decrement the impact of changes in the final resolution.
Is it possible to do something like following in Kotlin?
#Autowired
internal var mongoTemplate: MongoTemplate
#Autowired
internal var solrClient: SolrClient
Recommended approach to do Dependency Injection in Spring is constructor injection:
#Component
class YourBean(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
) {
// code
}
Prior to Spring 4.3 constructor should be explicitly annotated with Autowired:
#Component
class YourBean #Autowired constructor(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
) {
// code
}
In rare cases, you might like to use field injection, and you can do it with the help of lateinit:
#Component
class YourBean {
#Autowired
private lateinit var mongoTemplate: MongoTemplate
#Autowired
private lateinit var solrClient: SolrClient
}
Constructor injection checks all dependencies at bean creation time and all injected fields is val, at other hand lateinit injected fields can be only var, and have little runtime overhead. And to test class with constructor, you don't need reflection.
Links:
Documentation on lateinit
Documentation on constructors
Developing Spring Boot applications with Kotlin
Yes, java annotations are supported in Kotlin mostly as in Java.
One gotcha is annotations on the primary constructor requires the explicit 'constructor' keyword:
From https://kotlinlang.org/docs/reference/annotations.html
If you need to annotate the primary constructor of a class, you need to add the constructor keyword to the constructor declaration, and add the annotations before it:
class Foo #Inject constructor(dependency: MyDependency) {
// ...
}
You can also autowire dependencies through the constructor. Remember to annotate your dependencies with #Configuration, #Component, #Service etc
import org.springframework.stereotype.Component
#Component
class Foo (private val dependency: MyDependency) {
//...
}
like that
#Component class Girl( #Autowired var outfit: Outfit)
If you want property injection but don't like lateinit var, here is my solution using property delegate:
private lateinit var ctx: ApplicationContext
#Component
private class CtxVarConfigurer : ApplicationContextAware {
override fun setApplicationContext(context: ApplicationContext) {
ctx = context
}
}
inline fun <reified T : Any> autowired(name: String? = null) = Autowired(T::class.java, name)
class Autowired<T : Any>(private val javaType: Class<T>, private val name: String?) {
private val value by lazy {
if (name == null) {
ctx.getBean(javaType)
} else {
ctx.getBean(name, javaType)
}
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
}
Then you can use the much better by delegate syntax:
#Service
class MyService {
private val serviceToBeInjected: ServiceA by autowired()
private val ambiguousBean: AmbiguousService by autowired("qualifier")
}