Spring Bootキャンプシリーズ、Spring Boot + ModelMapper編です。
今回の目的
Spring BootアプリでModelMapperを利用してBeanのコピーを行います。
本記事では、独自に開発したStarterを使用します。
StarterはMaven Centralで公開しており、誰でも利用できます。
https://github.com/yoshikawaa/modelmapper-spring-boot-starter
今回使用するライブラリ
- spring-boot-starter:2.2.0.M4
- modelmapper-spring-boot-starter:0.1.0
- spring-boot-starter-test:2.2.0.M4
- lombok:1.18.8
ModelMapperとは
Java Beanのコピーを隠蔽し、コードをすっきりさせます。
public Todo convert(TodoForm form) {
Todo todo = new Todo();
todo.setTodoId(form.getTodoId());
todo.setTodoTitle(form.getTodoTitle());
todo.setFinished(form.isFinished());
todo.setCreatedAt(form.getCreatedAt());
return todo;
}
これが以下のようにすっきりします。
public Todo convert(TodoForm form) {
ModelMapper modelMapper = new ModelMapper();
return modelMapper.map(form, Todo.class);
}
ModelMapperの使い方はこちらの記事が詳しいので、ぜひ確認してみてください!
Spring Bootへの対応
本題に入りますが、ModelMapper公式ではSpring Boot Starterが提供されていません。
Related Projectとして個人が開発したStarterがアナウンスされています。
ただ、このStarterだとちょっと気になる点がありました。
- サポートバージョンの古さ
- modelmapper:0.7.5
- spring-boot-starter:1.2.5.RELEASE
- カスタマイズの貧弱さ
- Property MappingとConverter以外をカスタマイズする場合、Starterのメリットがない
作ってみました!
ということで、自分でStarterを作ってMaven Centralで公開してみました!
今回の記事では、こちらのStarterを利用していきます。
modelmapper-spring-boot-starter
Spring BootでModelMapperを利用するためのスターターです。
Spring BootのAuto Configurationの仕組みを利用することで、Spring BootアプリでModelMapperを使用するためのBean定義を自動的に行ってくれます。開発者は依存関係にmodelmapper-spring-boot-starterを追加するだけでOKです。
pom.xml
<dependencies>
<dependency>
<groupId>io.github.yoshikawaa.modelmapper.spring.boot</groupId>
<artifactId>modelmapper-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies>
src/main/java/*/Application.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
メインクラスはSpring Bootアプリのデフォルトから何も変更していません。
ModelMapperを使用するクラス
ここではControllerで使用してみます。
ModelMapperを使う以外の要素は極力省略しています。
src/main/java/*/web/TodoController.java
@Controller
@RequestMapping("todo")
public class TodoController {
// (1)
@Autowired
private ModelMapper modelMapper;
@Autowired
private TodoService todoService;
@PostMapping
public String create(TodoForm form) {
// (2)
Todo todo = modelMapper.map(form, Todo.class);
todoService.create(todo);
// ommitted.
}
}
(1) ModelMapperは自動的にセットアップされるので、インジェクションするだけで利用できます。
(2) ModelMapperのmap
メソッドでBeanをコピーします。
カスタムマッピング
ModelMapperのデフォルト設定では、コピー元と先で名前が同じプロパティを自動的にコピーします。
名前が異なるプロパティをコピーするには、カスタムマッピングが必要になります。
src/main/java/*/ModelMapperConfig.java
@Configuration
public class ModelMapperConfig {
@Bean
TypeMapConfigurer<TodoForm, Todo> typeMap() {
return new TypeMapConfigurer<TodoForm, Todo>() {
@Override
public void configure(TypeMap<TodoForm, Todo> typeMap) {
typeMap.addMapping(TodoForm::getTodoTitle, Todo::setTitle);
}
};
}
}
カスタムマッピングのため、TypeMapConfigurer<コピー元, コピー先>のBeanを定義します。
TypeMapConfigurerはBean定義すれば、自動的に適用されます。
Note.
@Bean
ではなく@Component
で定義してもOKです。
@Component
の場合は、1カスタムマッピング=1クラスになります。
カスタムマッピングの検証
カスタムマッピングを定義したら、プロパティに抜け漏れがないことを検証することをお勧めします。
src/main/resources/application.yml
modelmapper:
validate-enabled: true
ModelMapperのセットアップでvalidate
メソッドが実行され、プロパティのマッピングに抜け漏れがあればエラーになります。
振る舞いの変更
ModelMapperではコピー時の振る舞いをカスタマイズすることで、より柔軟なコピーが可能です。
ここでは、コピー元プロパティがnull
の時はコピーしないよう変更してみます。
src/main/resources/application.yml
modelmapper:
skip-null-enabled: true
ModelMapperのセットアップでsetSkipNullEnabled
メソッドが実行され、コピー元プロパティがnull
の時はコピーされなくなります。
ModelMapperの設定を確認する
セットアップされたModelMapperの設定を確認するには、TRACEレベルのログを出力すればOKです。
ログ出力するレベルは、Spring Bootのlogging
プロパティを利用して設定します。
src/main/resources/application.yml
logging:
level:
io.github.yoshikawaa.modelmapper.spring.boot.autoconfigure: trace
JUnitテストケース
JUnitテストケースでは、使用するテスト用Auto-Configにより設定が異なるので注意しましょう。
@SpringBootTest
を利用する場合
すべてのBeanを読み込みます。
この場合は、ModelMapperを利用するために意識することはありません。
@WebMvcTest
などを利用する場合
軽量化のため限定的にBeanを読み込むため、独自のAuto-Configや@Configuration
クラス等は読み込まれません。
この場合は、ModelMapperを利用するための設定が必要になります。
-
@ImportAutoConfiguration
-> ModelMapperのAuto-Configを読み込みます。 -
@Import
-> カスタムマッピングを読み込みます。
src/test/java/*/web/TodoControllerTest.java
@WebMvcTest
@ImportAutoConfiguration(ModelMapperAutoConfiguration.class) // (1)
@Import(ModelMapperConfig.class) // (2)
class TodoControllerTest {
@Autowired
MockMvc mvc;
@Test
void testCreate() throws Exception {
// execute & assert
mvc.perform(post("/todo").param("todoTitle", "sample"))
.andExpect(status().isOk());
}
(1) @ImportAutoConfiguration
でModelMapperのセットアップを行うModelMapperAutoConfiguration
クラスを読み込みます。
(2) @Import
でカスタムマッピングの@Configuration
クラスを読み込みます。配列で複数読み込むことも可能です。
Note.
カスタムマッピングを@Component
で作った場合、@Import
の代わりに@ComponentScan
で読み込めばOKです。
まとめ
今回は満足なStarterがなかったので、それなりに手を加えることになりましたが、Starterの作り方も分かったので良かったですw
ModelMapperを実開発で使う場合には、全体的な振る舞いをどこに集約するか、カスタムマッピングをどう管理するか、あたりが課題になると思うので、Starterを使うことによりルール付けされると良いですね。
今回作成したStarterは、記事に書いた以外にもModelMapperの豊富なカスタマイズをサポートしています。
利用される方はStarterのリファレンスとModelMapperのリファレンスを確認してみてください!