javaspringmapstruct

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?


Solution

  • 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);
        }
    
    }