Mapstruct generate Constructor for dependency injection - java

I'm trying to generate an implementation in MapStruct that will create a constructor for me that I can use for constructor-based dependency injection. I have learned that I can't use constructor-injection in the mapper definition(seen below), but how do I make it so that my generated class has one?
I have tried below:
#Mapper(componentModel = "spring", uses = Dependency.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public abstract class MapStructTest {
private Dependency dependency;
#Mapping(source = "field", target "target")
#Mapping(target = "target2", ignore = true)
#AfterMapping
public final void runAfter() {
//dostuff for target2
}
}
With no success. My class is generated, looks ok, except there is no constructor. How do I define my mapper so that I get a constructor I can work with in the implementation?
Best regards,
Prince of Sweden

MapStruct does not support calling constructors for abstract classes. You have to have a default empty constructor.
In order to inject Dependency you can use #Autowired on your field or add a setter with #Autowired

Related

Use a custom mapper inside another custom mapper with mapstruct (in default method)

I want to use MapperB inside MapperA's default method
Similar to this question:
How can I use another mapping from different class in mapstruct
However afaik this question did not ask for 'custom mappers', i.e. mappers that exist as their own interface.
How would I be able to do that?
I have an interface of MapperA and an interface of MapperB
How would I use the MapperB inside MapperA?
like so:
#Mapper
public interface MapperA {
#Autowired
MapperB mapperB;
default ArrayList<AudioDto> audiosToDto(List<Audio> audios, ApplicationUser loggedInUser) {
Stream<AudioDto> audiosStream = audios.stream().map((Audio audio) -> mapperB.audioToAudioDto(audio, loggedInUser));
return audiosStream.collect(Collectors.toCollection(ArrayList::new));
}
The above code didn't work. Now I tried adding #Component(to MapperA & MapperB) to be able to autowire it, but it's still giving me:
#Autowired <- Field injection is not recommended
MapperB mapperB; <- Variable 'audioMapper' might not have been initialized
even after maven-cleaning the project to get rid of the MapperAImpl.
You should define the MapperA as an abstract class instead of an interface, and use setter injection to inject MapperB as follows:
#Mapper(componentModel="spring")
public abstract class MapperA {
private MapperB mapperB;
#Autowired
public final void setMapperB(MapperB mapperB) {
this.mapperB = mapperB;
}
public ArrayList<AudioDto> audiosToDto(List<Audio> audios, ApplicationUser loggedInUser) {
Stream<AudioDto> audiosStream = audios.stream().map((Audio audio) -> mapperB.audioToAudioDto(audio, loggedInUser));
return audiosStream.collect(Collectors.toCollection(ArrayList::new));
}
}

MapStruct: externalizing custom map method results in Qualifier error

I am trying to create custom mapper in external class to map String to Integer. the map method is called successfully when it is defined in the mapper that uses it. but when I put the method in an external class, I get error that MapStruct cannot find the method:
error: Qualifier error. No method found annotated with #Named#value: [ MapperUtils and mapEnum ]. See https://mapstruct.org/faq/#qualifier for more info. #Mapping(target = "invoiceLanguage", source = "invoiceLanguage", qualifiedByName = {"MapperUtils", "mapEnum"})
This is the mapper abstract class. the commented code contains the custom mapping method that is called successfully. this method I want to externalize in order to be able to call it from multiple mappers
#Mapper(componentModel = "spring", uses = MapperUtils.class, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class CustomerAccountMapper {
public abstract ExistingCustomerAccountDto map(CustomerAccountDao dao);
public abstract CustomerAccountDao map(NewCustomerAccountRequest request);
#Mapping(target = "invoiceLanguage", source = "invoiceLanguage",
qualifiedByName = {"MapperUtils", "mapEnum"})
public abstract CustomerAccountDao map(UpdateCustomerAccountRequest request);
// works fine when method is in mapper class
// #Named("mapEnum")
// Integer mapEnum(String input) {
// if ("null".equalsIgnoreCase(input)) {
// return null;
// }
// return Integer.valueOf(input);
// }
}
I have followed the instructions in the user guide:
Created external class with custom map method. added #Named to class and method
added uses to #Mapper annotation of mapper class
added qualifiedByName to #Mapping annotation on method of mapper
This is my external mapper class
#Component
#Named("MapperUtils")
#SuppressWarnings("unused")
public class MapperUtils {
#Named("mapEnum")
Integer mapEnum(String input) {
if ("null".equalsIgnoreCase(input)) {
return null;
}
return Integer.valueOf(input);
}
}
what am I missing?
Looks like when implementing it with #Named and qualifiedByName will work only if MapperUtils will be in the exact same package as CustomerAccountMapper
Mapstruct documentation mentions that working with qualifiedByName is not the best way as its not very predictable
Although the used mechanism is the same, the user has to be a bit more
careful. Refactoring the name of a defined qualifier in an IDE will
neatly refactor all other occurrences as well. This is obviously not
the case for changing a name.
Also from qualifiedByName Java docs
Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large number of qualifiers as no custom annotation types are needed.

How to correctly instantiate a class using #Autowired in a mapper interface in Spring Boot?

I am currently using mapstruct to map data between entities and DTOs, inside a mapper I need to instantiate a class using #Autowired, inside the class I need to instantiate I have a method that loads data into the cache, when I try to do the following: # Autowired RepositoryImpl repository; IntelliJ tells me: The variable 'repository' may not have been initialized. How could I use instantiate the class correctly or use the method I need?
mapper
#Service
#Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DataMapper {
**#Autowired
RepositoryImpl repository;**
}
default DetailTemp mapDetail(String itemType, counter){
**ItemType itemType = repository.getType(itemType);**
DetailTemp detailTemp = new DetailTemp();
detailTemp.setPosition(counter);
detailTemp.setItemType(itemType);
return DetailTemp;
}
}
According to this you need to be using an abstract class if you are using Spring components (i.e. #Autowired RepositoryImpl repository):
5.2. Inject Spring Components into the Mapper Sometimes, we'll need to utilize other Spring components inside our mapping logic. In this
case, we have to use an abstract class instead of an interface:
#Mapper(componentModel = "spring") public abstract class
SimpleDestinationMapperUsingInjectedService
Then, we can easily inject
the desired component using a well-known #Autowired annotation and use
it in our code:
#Mapper(componentModel = "spring") public abstract class
SimpleDestinationMapperUsingInjectedService {
#Autowired
protected SimpleService simpleService;
#Mapping(target = "name", expression = "java(simpleService.enrichName(source.getName()))")
public abstract SimpleDestination sourceToDestination(SimpleSource source); }
We must remember not to make the injected bean private!
This is because MapStruct has to access the object in the generated
implementation class.

Is it possible to add qualifiers in #RequiredArgsConstructor(onConstructor = #__(#Autowired))?

If I wanted to use the annotation #Qualifier on a constructor dependency injection, I would have something like the following:
public class Example {
private final ComponentExample component;
#Autowired
public Example(#Qualifier("someComponent") ComponentExample component) {
this.component = component;
}
}
I know Lombok's annotations to reduce boilerplate code and don't have to include a constructor would be as follows: #RequiredArgsConstructors(onConstructor=#__(#Inject)) but this only works with properties without qualifiers.
Anyone know if it is possible to add qualifiers in #RequiredArgsConstructor(onConstructor = #__(#Autowired))?
EDIT:
It is FINALLY POSSIBLE to do so! You can have a service defined like this:
#Service
#RequiredArgsConstructor
public class SomeRouterService {
#NonNull private final DispatcherService dispatcherService;
#Qualifier("someDestination1") #NonNull private final SomeDestination someDestination1;
#Qualifier("someDestination2") #NonNull private final SomeDestination someDestination2;
public void onMessage(Message message) {
//..some code to route stuff based on something to either destination1 or destination2
}
}
Provided that you have a lombok.config file like this in the root of the project:
# Copy the Qualifier annotation from the instance variables to the constructor
# see https://github.com/rzwitserloot/lombok/issues/745
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
This was recently introduced in latest lombok 1.18.4, I wrote about it in my blogpost, and I am proud to say I was one of the main driving forces pushing for the implementation of the feature.
The blog post where the issue is discussed in detail
The original issue on github
And a small github project to see it in action
You may use spring trick to qualify field by naming it with desired qualifier without #Qualifier annotation.
#RequiredArgsConstructor
public class ValidationController {
//#Qualifier("xmlFormValidator")
private final Validator xmlFormValidator;
I haven't test whether the accepted answer works well, but instead of create or edit lombok's config file, I think the cleaner way is rename the member variable to which name you want to qualifier.
// Error code without edit lombok config
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class Foo {
#Qualifier("anotherDao") UserDao userDao;
}
Just remove #Qualifier and change your variable's name
// Works well
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class Foo {
UserDao anotherDao;
}

Overriding #JsonDeserialize and #JsonSerialize Behaviour

I have a Spring(4.1.6) MVC project, properties annotated with #JsonDeserialize and #JsonSerialize in class Foo are working fine. Foo is used within a RestController hence managed by Rest calls.
Foo is packed within a common module hence need to be reused in other modules.
Something like:
-Web
--Common
-Services
--Common(Common is used in both)
we will be using ObjectMapper for conversion in Services module.But some how we need to override behavior so that #JsonDeserialize and #JsonSerialize are ignored in Services module and we get values as is.
One option I can think of is creating new bean extends Foo and overriding annotated properties.
Any pointers to other simple way of doing the same?
I got a solution within Mixin provided by jackson.
We can override Annotations using mixin as shown below for Deserialization(Serialization would be similar):
Step 1. Create a DummyDateDeSerializer, where we can write custom logic for date parsing.In my case I returned date as received.
Step 2. Create a Mixin Class defining properties for which anootations should be overriden.
public abstract class DateMixin {
#JsonDeserialize(using=com.test.jackson.DummyDateDeSerializer.class)
public abstract Date getLastModifiedDate() ;
#JsonDeserialize(using=com.test.jackson.DummyDateDeSerializer.class)
public abstract Date getCreatedDate() ;
}
Step 3.Create a DummyDateModule
public class DummyDateModule extends SimpleModule {
public DummyDateModule() {
super("DummyDateModule", new Version(0, 0, 1, null));
}
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Foo.class, DateMixin.class);
}
}
Step 4. Register mdoule
private static void updateMapper(ObjectMapper mapper){
mapper.registerModule(new DummyDateModule());
}
This will override any #JsonDeserialize defined in Foo or its super class(es) with DummyDateDesrializer for properties createdDate and lastModifiedDate.

Categories

Resources