【MapStruct】メソッドがマッピング対象のフィールドとなってしまうのを解決!?カスタム編

MapStructライブラリを使用してオブジェクト間のマッピングを行う際、特定のメソッドがフィールドとして認識され、コンパイル時にエラーが発生する場合の対処法はいくつかあります

その中で、「カスタムしたAccessorNamingStrategy」を使用して解決していきます!

ソースとデスティネーションのオブジェクトクラスを以下としますね

public class SourceObject {
    private String someField;

    // 通常のgetterとsetter
    public String getSomeField() {
        return someField;
    }

    public void setSomeField(String someField) {
        this.someField = someField;
    }

    // MapStructに無視させたいメソッド
    public String customMethod(String value) {
        // メソッドの実装
        return "processed: " + value;
    }
}

public class DestinationObject {
    private String someField;

    public String getSomeField() {
        return someField;
    }

    public void setSomeField(String someField) {
        this.someField = someField;
    }
}


1. カスタムAccessorNamingStrategyの実装

import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy;
import org.mapstruct.ap.spi.MapStructProcessingEnvironment;

import javax.lang.model.element.ExecutableElement;

public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {

    public CustomAccessorNamingStrategy() {
        super();
    }

    @Override
    public boolean isGetterMethod(ExecutableElement method) {
        // メソッド名が "customMethod" の場合はプロパティとして認識しない
        if (method.getSimpleName().toString().equals("customMethod")) {
            return false;
        }
        return super.isGetterMethod(method);
    }

    @Override
    public boolean isSetterMethod(ExecutableElement method) {
        // メソッド名が "customMethod" の場合はプロパティとして認識しない
        if (method.getSimpleName().toString().equals("customMethod")) {
            return false;
        }
        return super.isSetterMethod(method);
    }

    // カスタムメソッドを無視するための追加ロジック
    @Override
    public boolean isFluentSetterMethod(ExecutableElement method) {
        // メソッド名が "customMethod" の場合はプロパティとして認識しない
        if (method.getSimpleName().toString().equals("customMethod")) {
            return false;
        }
        return super.isFluentSetterMethod(method);
    }
}

「DefaultAccessorNamingStrategy」の「isGetterMethod」および「isSetterMethod」メソッドは、JavaBeans規約に基づいてgetterおよびsetterメソッドを特定しますが、戻り値があり引数を1つ持つメソッドは通常のgetterやsetterのパターンに当てはまらないため、通常は対象外となります
ただし、カスタムAccessorNamingStrategyを作成することで、これらのメソッドを特定し、無視することができます

以下に、戻り値があり引数を1つ持つメソッドを無視するためのカスタムAccessorNamingStrategyの例が上記となります


2. @MapperConfigでカスタム設定を使用

import org.mapstruct.MapperConfig;

@MapperConfig(
    uses = CustomAccessorNamingStrategy.class
)
public interface CustomAccessorTypeConfig {
}

3. Mapperインターフェースにカスタム設定を適用

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper(config = CustomAccessorTypeConfig.class)
public interface YourMapper {
    YourMapper INSTANCE = Mappers.getMapper(YourMapper.class);

    DestinationObject toDestinationObject(SourceObject source);
}

4. build.gradleの設定はこんな感じ

plugins {
    id 'java'
    id 'eclipse' // Optional: for Eclipse integration
    id 'idea' // Optional: for IntelliJ IDEA integration
}

group 'com.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.mapstruct:mapstruct:1.5.5.Final' // 最新バージョンを指定してください
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
    compileOnly 'org.projectlombok:lombok:1.18.24' // Optional: if you're using Lombok
    annotationProcessor 'org.projectlombok:lombok:1.18.24' // Optional: if you're using Lombok
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}

tasks.withType(JavaCompile) {
    options.annotationProcessorPath = configurations.annotationProcessor
    options.compilerArgs << "-Amapstruct.defaultComponentModel=spring" // Optional: if you're using Spring
}


このカスタムAccessorNamingStrategyを使用することで、customMethodがgetterやsetterとして認識されることを防ぎます
MapStructはこのメソッドを無視し、マッピングの対象としないようになります

是非参考にしていてください!


マッピング設定をあらかじめ用意しるのもよいですね

import org.mapstruct.MapperConfig;
import org.mapstruct.Mapping;

@MapperConfig
public interface CustomMapperConfig {
    @Mapping(target = "ignoredField", ignore = true)
    void applyIgnoreIgnoredField(SourceObject source, @MappingTarget DestinationObject target);
}

下のmapperに適用してあげる

import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;

@Mapper(config = CustomMapperConfig.class)
public interface YourMapper {
    YourMapper INSTANCE = Mappers.getMapper(YourMapper.class);

    DestinationObject toDestinationObject(SourceObject source);

    default void map(SourceObject source, @MappingTarget DestinationObject target) {
        // applyIgnoreIgnoredFieldを呼び出して特定のフィールドを無視する
        applyIgnoreIgnoredField(source, target);
    }

    void applyIgnoreIgnoredField(SourceObject source, @MappingTarget DestinationObject target);
}

是非フォローしてください

最新の情報をお伝えします