abubakirov
abubakirov

Reputation: 13

@Service Class Not Autowired in org.mapstruct.@Mapper Class

I am trying to add json serialization to my SpringBoot app using MapStruct. @Mapper class uses @Service to add some "aftermapping" logic. The problem is, that this @Service class is not autowired.

This is my Mapper class:

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
public abstract class InstrumentMapper {

    protected MarketDataService marketDataService; // is @Service

    @Mapping(target = "marketCode",
            expression = "java(instrument.getMarket().getCode())")
    public abstract InstrumentDto fromInstrument(Instrument instrument);

    public abstract List<InstrumentDto> fromInstruments(List<Instrument> instruments);

    @Mapping(target = "market",
            expression = "java(marketDataService.findMarketByCode(instrumentDto.getMarketCode()))")
    public abstract Instrument toInstrument(InstrumentDto instrumentDto);

    public abstract List<Instrument> toInstruments(List<InstrumentDto> instrumentDtos);

    @Autowired
    public void setMarketDataService(MarketDataService marketDataService) {
        this.marketDataService = marketDataService;
    }
}

When toInstrument method is called, application fails with NPE, trying to marketDataService.findMarketByCode(instrumentDto.getMarketCode()).

Hopefully, this information will be enough. Let me know if anything else is needed.

Thanks in advance!

Update:

MarketDataService class. It is added to the context through @Service annotation.

@Service
public class MarketDataService {

    @Autowired
    private InstrumentRepository instrumentRepository;

    public Instrument findInstrumentByCode(String code) {
        return instrumentRepository.findFirstByCode(code);
    }

    public List<InstrumentDto> getAllInstrumentDtos() {
        List<Instrument> instruments = getAllInstruments();
        List<InstrumentDto> dtos = Mappers.getMapper(InstrumentMapper.class).fromInstruments(instruments);
        return dtos;
    }

    public void updateInstrument(InstrumentDto instrumentDto) {
        Instrument instrument = findInstrumentByCode(instrumentDto.getCode());
        if (instrument == null) {
            throw new IllegalArgumentException("Market with given code not found!");
        }
        instrumentRepository.delete(instrument);
        instrument = Mappers.getMapper(InstrumentMapper.class).toInstrument(instrumentDto);
        instrumentRepository.save(instrument);
    }
}

The algorithm is the following: @Controller class gets PUT request and calls MarketDataService.updateInstrument method with the body of the request (instrumentDto parameter). The latter one calls toInstrument method with the same parameter.

Upvotes: 0

Views: 4037

Answers (1)

Filip
Filip

Reputation: 21393

The reason why you have an NPE is because you are using the MapStruct Mappers factory for a non default component model.

The Mappers factory does not perform any dependency injections.

You have to inject your mapper in your MarketDataService. Be careful when doing that because you have a cyclic dependency.


In addition to that the patterns you are using in your Mapper are not really the right ones. You are using an expression when a simple source will do.

e.g.

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
public abstract class InstrumentMapper {

    protected InstrumentRepository instrumentRepository;

    @Mapping(target = "marketCode", source = "market.code")
    public abstract InstrumentDto fromInstrument(Instrument instrument);

    public abstract List<InstrumentDto> fromInstruments(List<Instrument> instruments);

    @Mapping(target = "market", source = "marketCode")
    public abstract Instrument toInstrument(InstrumentDto instrumentDto);

    public abstract List<Instrument> toInstruments(List<InstrumentDto> instrumentDtos);

    protected Instrument findInstrumentByCode(String code) {
        return instrumentRepository.findFirstByCode(code);
    }

    @Autowired
    public void setMarketDataService(MarketDataService marketDataService) {
        this.marketDataService = marketDataService;
    }
}

Upvotes: 1

Related Questions