概要
ModelMapperを使うとObject間の変換ロジックをそこに集約できて便利です。
こちらの記事 がModelMapperのメリットをわかりやすく説明しています。
ModelMapperは変換元と変換先のフィールド名が同じならmodelMapper.map(input, Output.class)
とすれば変換できますが、フィールド名や型が違うときはカスタムのConverterを追加する必要があります (参考記事)。
カスタムのConverterを作ったときにわざわざ modelMapper.addConverter(newConverter)
とするのが大変なのでしなくて良い方法を提示します。
動作確認環境
対応
ModelMapperの登録
build.gradleに以下を追加
implementation 'org.modelmapper:modelmapper:2.3.7'
以下でModelMapperをSpringのアプリケーションへDIする。
このとき、List<Converter<?, ?>>
をDIしているのがポイント。Spring BootはListやMapであるクラスをDIすることができる(参考記事)。
ListでDIしたConverterクラス(後述)をすべてModelMapperへ登録する。
import java.util.List;
import org.modelmapper.Converter;
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppBean {
private List<Converter<?, ?>> converters;
public AppBean(List<Converter<?, ?>> converters) {
this.converters = converters;
}
@Bean
public ModelMapper modelMapper() {
var modelMapper = new ModelMapper();
for (Converter<?, ?> converter : converters) {
modelMapper.addConverter(converter);
}
return modelMapper;
}
}
Conveterの登録
ここのクラスが複数になる想定。
org.modelmapper.AbstractConverter
のConvertメソッドを実装すればよい。型引数に<Inputクラス, Outputクラス>
を入れる。
ModelMapperの登録部分へDIする必要があるため@Componentをつけるのを忘れないこと。
import org.modelmapper.AbstractConverter;
import org.springframework.stereotype.Component;
@Component
public class SomethingConverter extends AbstractConverter<Inout, Output> {
@Override
protected Output convert(Input source) {
// your conversion rules
return output;
}
}
呼び出しかた
ここまでくると呼び出せる。
@Service
public class SomethingService {
private final ModelMapper modelMapper;
public SomethingService(ModelMapper modelMapper) {
this.modelMapper = modelMapper;
}
public Output method() {
// something
return modelMapper.map(input, Output.class))
}
(おまけ)テスト
楽に追加する話とはあまり関係がないがModelMapperを使うことで変換ロジックが隠蔽されるためテストもシンプルになる。
@SpringBootTest
class SomethingServiceTest {
@Autowired
private SomethingService target;
@MockBean
private ModelMapper modelMapper;
@Mock
private Input input;
@Mock
private Output output;
@BeforeEach
void setUp() {
when(modelMapper.map(input, Output.class)).thenReturn(output);
}
@Test
void test_success() {
// something
var actual = target.method();
assertEquals(output, actual);
}
}