How to inject(custructor) a spring bean into abstract mapper of Mapstruct?

Viewed 4793

I am having below mapper class in which I want to use CounterService. I am trying constructor injection but that's not working and null is printing.

@Mapper(componentModel = "spring", uses = CounterService.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public abstract class CarMapper {

    private CounterService counterService;

    public CarMapper(CounterService counterService) {
       this.counterService = counterService;
    }

    public abstract Car dtoToEntity(CarDto carDto);

    public CarDto entityToDto(Car car) {
        System.out.println(counterService)
        //....
        return carDto;
    }

}

Implementation class by mapStruct

@Component
public class CarMapperImpl extends CarMapper{

  @Override
  public Car dtoToEntity(CarDto carDto){
    //...
  }
}

If I use field injection using @AutoWired, that way it works fine. It means Spring doesn't support the constructor injection of abstract class. Is it because abstract class can't be instantiated directly and require a subclass to instantiate?
Is there any way mapStruct can create a constructor inside the implementation class as:

  public CarMapperImpl(CounterService counterService){
    super(counterService);
  }

That way, constructor injection should work.

3 Answers

This has nothing to do with Spring. It was a deliberate decision done by the MapStruct team to not use super constructors.

What you can do though is to use setter injection.

@Mapper(componentModel = "spring", uses = CounterService.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public abstract class CarMapper {

    private CounterService counterService;

    public abstract Car dtoToEntity(CarDto carDto);

    public CarDto entityToDto(Car car) {
        System.out.println(counterService)
        //....
        return carDto;
    }

    @Autowired
    public void setCounterService(CounterService counterService) {
        this.counterService = counterService;
    }

}

You can also use a Mapper Decorator like below. Also, always make sure to check the generated class.

@DecoratedWith(CarDecoratorMapper.class)
@Mapper(componentModel = "spring")
public interface CarMapper {

    Car dtoToEntity(CarDto carDto);
    CarDto entityToDto(Car car);

}

public abstract class CarDecoratorMapper implements CarMapper {

    @Autowired
    private CarMapper delegate;

    @Autowired
    private CounterService counterService;    

    public Car dtoToEntity(CarDto carDto) {
        Car car = delegate.dtoToEntity(carDto);
        car.setProperty(counterService.count(carDto));
    
        return car;
    }

    public CarDto entityToDto(Car car) {
        CarDto carDto = delegate.entityToDto(car);
        carDto.setProperty(counterService.countDto(car));

        return carDto;
    }
}

You don't have to use setter injection you can do the same what Filip suggested and just use field injection. If you don't need a setter of course

Related