如何在 Spring boot 应用程序中使用 Mapstruct 创建装饰器?

问题描述 投票:0回答:1

我有一个 Spring boot 应用程序,其中启用了自动组件扫描,并且 mapstruct 似乎设置正确,因为它适用于在接口上创建的基本映射,但我似乎无法向其中添加自定义装饰器。

这是我旧的解决方案(这是一个简单的例子)

@Mapper
public interface CategoryMapper {

    Category mapToCategory(CategoryDTO categoryDTO);

    @Mappings({
            @Mapping(target="imageBase64", expression="java(mapImageToBase64(category.getImage()))"),
    })
    CategoryDTO mapToCategoryDTO(Category category);

    List<Category> mapToCategoryList(List<CategoryDTO> categoryDTOList);

    List<CategoryDTO> mapToCategoryDTOList(List<Category> categoryList);

    // A default method to handle the IOException
    default String mapImageToBase64(Image image) {
        if (image != null) {
            try {
                return image.getBase64Image();
            } catch (IOException e) {
                // handle the exception in some way (e.g., log an error message and return null)
                System.err.println("Failed to convert image to Base64: " + e.getMessage());
            }
        }
        return null;
    }
}

这个解决方案还有一个配置java类,我在其中为我拥有的所有映射器手动创建了一个bean。例如:

@Configuration
public class MapperConfiguration {

    @Bean
    public ProductMapper getProductMapper() {
        return new ProductMapperImpl();
    }

    @Bean
    public CategoryMapper getCategoryMapper() {
        return new CategoryMapperImpl();
    }

    @Bean
    public PropertyMapper getPropertyMapper() {
        return new PropertyMapperImpl();
    }
}

现在我想选择不手动创建bean,因为自动组件扫描已启用,并且我想将映射器接口中定义的额外逻辑提取到装饰器类中,到目前为止我已经创建了这个:

@Mapper(componentModel = "spring")
@DecoratedWith(value = CategoryMapperDecorator.class)
public interface CategoryMapper {

    Category mapToCategory(CategoryDTO categoryDTO);

    @Mappings({
            @Mapping(target = "imageBase64", ignore = true)
    })
    CategoryDTO mapToCategoryDTO(Category category);

    List<Category> mapToCategoryList(List<CategoryDTO> categoryDTOList);

    List<CategoryDTO> mapToCategoryDTOList(List<Category> categoryList);
    
}

public abstract class CategoryMapperDecorator implements CategoryMapper {


    @Autowired
    @Qualifier("delegate")
    private CategoryMapper delegate;

    @Override
    public CategoryDTO mapToCategoryDTO(Category category) {
        System.out.println("Decorator is called.");
        CategoryDTO categoryDTO = delegate.mapToCategoryDTO(category);

        setImageBase64(category, categoryDTO);
        return categoryDTO;
    }

    private void setImageBase64(Category category, CategoryDTO categoryDTO) {
        System.out.println("setImageBase64 is called");
        if (category.getImage() != null) {
            try {
                categoryDTO.setImageBase64(category.getImage().getBase64Image());
            } catch (IOException e) {
                // handle the exception in some way (e.g., log an error message and return null)
                System.err.println("Failed to convert image to Base64: " + e.getMessage());
            }
        }
    }

}

我的问题是装饰器没有生成到映射器实现中,因此 image64 变量保持为空,因为我告诉接口忽略它,因为装饰器将进行该映射,但装饰器的代码永远不会被调用。

如果没有 @Mapper 上的 "componentModel = "spring" 设置,映射器根本无法工作。如果我使用默认设置或只是不提供任何设置,则会收到此错误:

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2023-09-03T00:20:38.784+02:00 ERROR 3652 --- [  restartedMain] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 2 of constructor in hu.maszlag.cms.webshop.category.CategoryService required a bean of type 'webshop.category.adapter.rest.mapper.CategoryMapper' that could not be found.


Action:

Consider defining a bean of type 'webshop.category.adapter.rest.mapper.CategoryMapper' in your configuration.

但我不想手动定义bean。

我也尝试将 @Component 注释放在装饰器上,但它没有什么区别。

我正在使用 Spring boot 3.0.8gradle。这些是 Mapstract 依赖项:

implementation("org.mapstruct:mapstruct:1.5.5.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.5.Final")

这些是生成的mapstruct文件:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-09-03T00:25:22+0200",
    comments = "version: 1.5.5.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-8.1.1.jar, environment: Java 17.0.2 (Oracle Corporation)"
)
@Component
@Primary
public class CategoryMapperImpl extends CategoryMapperDecorator {

    @Autowired
    @Qualifier("delegate")
    private CategoryMapper delegate;

    @Override
    public Category mapToCategory(CategoryDTO categoryDTO)  {
        return delegate.mapToCategory( categoryDTO );
    }

    @Override
    public List<Category> mapToCategoryList(List<CategoryDTO> categoryDTOList)  {
        return delegate.mapToCategoryList( categoryDTOList );
    }

    @Override
    public List<CategoryDTO> mapToCategoryDTOList(List<Category> categoryList)  {
        return delegate.mapToCategoryDTOList( categoryList );
    }
}

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-09-03T00:25:22+0200",
    comments = "version: 1.5.5.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-8.1.1.jar, environment: Java 17.0.2 (Oracle Corporation)"
)
@Component
@Qualifier("delegate")
public class CategoryMapperImpl_ implements CategoryMapper {

    @Override
    public Category mapToCategory(CategoryDTO categoryDTO) {
        if ( categoryDTO == null ) {
            return null;
        }

        Category.CategoryBuilder category = Category.builder();

        category.id( categoryDTO.getId() );
        category.childCategories( mapToCategoryList( categoryDTO.getChildCategories() ) );
        category.name( categoryDTO.getName() );
        category.available( categoryDTO.getAvailable() );
        category.categoryTimestamp( categoryDTO.getCategoryTimestamp() );

        return category.build();
    }

    @Override
    public CategoryDTO mapToCategoryDTO(Category category) {
        if ( category == null ) {
            return null;
        }

        CategoryDTO.CategoryDTOBuilder categoryDTO = CategoryDTO.builder();

        categoryDTO.id( category.getId() );
        categoryDTO.name( category.getName() );
        categoryDTO.childCategories( mapToCategoryDTOList( category.getChildCategories() ) );
        categoryDTO.available( category.getAvailable() );
        categoryDTO.categoryTimestamp( category.getCategoryTimestamp() );

        return categoryDTO.build();
    }

    @Override
    public List<Category> mapToCategoryList(List<CategoryDTO> categoryDTOList) {
        if ( categoryDTOList == null ) {
            return null;
        }

        List<Category> list = new ArrayList<Category>( categoryDTOList.size() );
        for ( CategoryDTO categoryDTO : categoryDTOList ) {
            list.add( mapToCategory( categoryDTO ) );
        }

        return list;
    }

    @Override
    public List<CategoryDTO> mapToCategoryDTOList(List<Category> categoryList) {
        if ( categoryList == null ) {
            return null;
        }

        List<CategoryDTO> list = new ArrayList<CategoryDTO>( categoryList.size() );
        for ( Category category : categoryList ) {
            list.add( mapToCategoryDTO( category ) );
        }

        return list;
    }
}

我浏览了有关装饰的文档,观看了一些 youtube 视频、github 解决方案,并尝试在网络上找到任何内容,甚至咨询了 ChatGPT,但我似乎不明白为什么这不起作用。

java spring spring-boot decorator mapstruct
1个回答
0
投票

根据我从您的问题中收集到的信息,您尝试在

imageBase64
中的映射过程中将
CategoryDTO
的字段值设置为
image.getImageBase64()
对象中
Category
的值。这听起来像是一个实例,您的映射器需要是抽象映射器,并且您可以使用
@AfterMapping
/
@MappingTarget
注释来实现您想要的。

您的新映射器将类似于我在下面提供的代码。

@AfterMapping
注释标记了要在最后一个 return 语句之前的映射方法末尾调用的方法。

@Mapper(componentModel = "spring")
public abstract class CategoryMapper {

    public abstract CategoryDTO mapToCategoryDTO(Category category);

    // ... other abstract mapper methods ...

    @AfterMapping
    protected void imageToBase64(Category category, @MappingTarget CategoryDTO dto) {
        dto.setImageBase64(category.getImage().getBase64Image());
    }
}

希望这能解决您的问题。如果这不是您想要的,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.