Emilien Brigand
Emilien Brigand

Reputation: 11021

Mapstruct Mapper return a Spring Bean not initialized

I have a Mapper class WishListMapper like this:

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public abstract class WishListMapper {

    @Mappings ({
       @Mapping(source = "productCountDtos", target = "productCounts", qualifiedByName = "productCountDtosToProductCounts"),
       @Mapping(target = "account", ignore = true),
       @Mapping(target = "id", ignore = true)
    })
    public abstract WishList mapToWishList(WishListDto wishListDto);

    @Named("productCountDtosToProductCounts")
    Set<ProductCount> productCountDtosToProductCounts(List<ProductCountDto> productCountDtos) {
        return productCountDtos.stream().map(productCountDto -> Mappers.getMapper(ProductCountMapper.class).mapToProductCount(productCountDto)).collect(Collectors.toSet());
    }
}

This is calling another Mapper class ProductCountMapper via Mappers.getMapper(ProductCountMapper.class)

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public abstract class ProductCountMapper {

    @Autowired
    protected ProductService productService;

    @Mappings({
            @Mapping(source = "productId", target = "product", qualifiedByName = "productIdToProduct"),
            @Mapping(target = "wishList", ignore = true),
            @Mapping(target = "id", ignore = true)
    })
    public abstract ProductCount mapToProductCount(ProductCountDto ProductCountDto);

    @Named("productIdToProduct")
    Product productIdToProduct(Long productId) {
        return productService.get(productId);
    }
}

And I get a NullPointerException on productService, when I inspect, the Mappers.getMapper(ProductCountMapper.class) return the mapper with null for productService.

PS: if ProductCountMapper is used straight (without beeing called by WishListMapper) it's working fine.

What is the solution?

Upvotes: 0

Views: 40

Answers (2)

mkoterba
mkoterba

Reputation: 1

according to the documentation

When using this factory, mapper types - and any mappers they use - are instantiated by invoking their public no-args constructor.

So when you use Mappers.getMapper, it does not inject you service.

Upvotes: 0

Emilien Brigand
Emilien Brigand

Reputation: 11021

I found a way using an expression instead of a qualifiedByName and moving the @Named method productCountDtosToProductCounts from WishListMapper to ProductCountMapper and removing the @Named which is not needed anymore (to be honest it makes sense since the method is dealing with ProductCounts and ProductCountDtos):

 @Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public abstract class WishListMapper {

    @Autowired
    protected ProductCountMapper productCountMapper;
    
    @Mappings ({
        @Mapping(target = "productCounts", expression = "java(productCountMapper.productCountDtosToProductCounts(wishListDto.productCountDtos()))"),
        @Mapping(target = "account", ignore = true),
        @Mapping(target = "id", ignore = true)
    })
    public abstract WishList mapToWishList(WishListDto wishListDto);
    
}

And:

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public abstract class ProductCountMapper {

    @Autowired
    protected ProductService productService;

    @Mappings({
            @Mapping(source = "productId", target = "product", qualifiedByName = "productIdToProduct"),
            @Mapping(target = "wishList", ignore = true),
            @Mapping(target = "id", ignore = true)
    })
    public abstract ProductCount mapToProductCount(ProductCountDto ProductCountDto);

    public Set<ProductCount> productCountDtosToProductCounts(List<ProductCountDto> productCountDtos) {
        return productCountDtos.stream().map(this::mapToProductCount).collect(Collectors.toSet());
    }

    @Named("productIdToProduct")
    protected Product productIdToProduct(Long productId) {
        return productService.get(productId);
    }

}

Upvotes: 0

Related Questions