LoginSignup
5

More than 3 years have passed since last update.

Spring Boot キャンプ : Spring Boot + Dozer編

Posted at

Spring Bootキャンプシリーズ、Spring Boot + Dozer編です。

今回の目的

Spring BootアプリでDozerを利用してBeanのコピーを行います。

今回使用するライブラリ

  • spring-boot-starter:2.2.0.M4
  • dozer-spring-boot-starter:6.5.0
  • spring-boot-starter-test:2.2.0.M4
  • lombok:1.18.8

Dozerとは

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) {

    Mapper dozerMapper = DozerBeanMapperBuilder.buildDefault();
    return dozerMapper.map(form, Todo.class);
}

Dozerの使い方はTERASOLUNAガイドが詳しいので、ぜひ確認してみてください!

dozer-spring-boot-starter

Spring BootでDozerを利用するためのスターターです。

Spring BootのAuto Configurationの仕組みを利用することで、Spring BootアプリでDozerを使用するためのBean定義を自動的に行ってくれます。開発者は依存関係にdozer-spring-boot-starterを追加するだけでOKです。

pom.xml

    <dependencies>
        <dependency>
            <groupId>com.github.dozermapper</groupId>
            <artifactId>dozer-spring-boot-starter</artifactId>
            <version>6.5.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アプリのデフォルトから何も変更していません。

Dozerを使用するクラス

ここではControllerで使用してみます。
Dozerを使う以外の要素は極力省略しています。

src/main/java/*/web/TodoController.java

@Controller
@RequestMapping("todo")
public class TodoController {

    // (1)
    @Autowired
    private Mapper dozerMapper;

    @Autowired
    private TodoService todoService;

    @PostMapping
    public String create(TodoForm form) {

        // (2)
        Todo todo = dozerMapper.map(form, Todo.class);
        todoService.create(todo);

        // ommitted.
    }
}

(1) Mapperは自動的にセットアップされるので、インジェクションするだけで利用できます。

(2) MapperのmapメソッドでBeanをコピーします。

カスタムマッピング

Dozerのデフォルト設定では、コピー元と先で名前が同じプロパティを自動的にコピーします。
名前が異なるプロパティをコピーするには、カスタムマッピングが必要になります。

Dozerの設定はXMLとJava Configのいずれかで定義することができます。
使いたいほうを使ってもらえればOKですが、XMLには後述の注意点があります。

今回はJava ConfigのSpring Bootに合わせて、Java Configでカスタムマッピングを定義します。

  • マッピング定義XML

src/main/java/*/DozerConfig.java

@Configuration
public class DozerConfig {
    @Bean
    public BeanMappingBuilder mappingDestinationTodo() {
        return new BeanMappingBuilder() {
            @Override
            protected void configure() {
                mapping(type(TodoForm.class), type(Todo.class))
                    .fields(field("todiTitle"), field("title"));
            }
        };
    }
}

カスタムマッピングのため、BeanMappingBuilderのBeanを定義します。
BeanMappingBuilderはBean定義すれば、自動的に適用されます。

フィールド名は文字列指定なので、必ずテストで指定が正しいことを確認しましょうw

Note.
@Beanではなく@Componentで定義してもOKです。
@Componentの場合は、1カスタムマッピング=1クラスになります。

振る舞いの変更

Dozerではコピー時の振る舞いをカスタマイズすることで、より柔軟なコピーが可能です。
ここでは、コピー元プロパティがnullの時はコピーしないよう変更してみます。

src/main/resources/application.yml

dozer:
  mapping-files:
  - classpath:configuration.dozer.xml

src/main/resources/configuration.dozer.xml

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozermapper.github.io/schema/bean-mapping"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping http://dozermapper.github.io/schema/bean-mapping.xsd">
    <configuration>
        <map-null>false</map-null>        
    </configuration>
</mappings>

DozerのセットアップでXMLから設定が読み込まれ、コピー元プロパティがnullの時はコピーされなくなります。

さきほどはJava Configで設定すると言っておきながら、当然のようにXMLを使い始めましたw
なぜこんなことをしているのかというと、、、

Spring Boot + Dozerの注意点

XMLの<configuration>に対応するJava Configがない

はい、ありません。
全体的な振る舞いの設定は仕方なくXMLの<configuration>で行うしかありません。

dozer.mapping-filesプロパティにワイルドカードを使えない

仕方なくXMLを使うにあたり、、、

先のTERASOLUNAガイドのノリで、複数のXMLをワイルドカードで読み込もうとすると、FileNotFoundExceptionになります。
DozerMapper#702を見ると、Springの@ValueとSpring Bootの@ConfigurationPropertiesの挙動の違いにより、Bootではワイルドカードが解決できないようです。

dozer.mapping-filesプロパティではXMLを一つ一つ指定する必要があります。
このため、全体的な振る舞いはXML、個別のマッピングはJava Configで定義するよう整理すると良さそうですね。

リファレンスでもDozerPropertiesのJavadocでもワイルドカードで指定してるんですが、、、テストしてないんじゃね?

XMLの解析に利用されるJAXBがJava 11ではJDKに含まれていない

仕方なくXMLを使うにあたり、、、

DozerはXMLの解析にJAXBを使うようになったため、Java 11以降のJAXBが含まれていないJDKを利用するときは注意が必要です。

Note.
XMLの解析にトラディショナルなDozerのパーサーを利用することもできますが、さらに設定ファイルが増えますw

src/main/resources/dozer.properties

dozer.xml.use-jaxb-mapping-engine = false

システムプロパティや環境変数でもOKですが、動作環境によってセットすべきか判断は現実的ではないですね。

JUnitテストケース

JUnitテストケースでは、使用するテスト用Auto-Configにより設定が異なるので注意しましょう。

@SpringBootTestを利用する場合

すべてのBeanを読み込みます。
この場合は、Dozerを利用するために意識することはありません。

@WebMvcTestなどを利用する場合

軽量化のため限定的にBeanを読み込むため、独自のAuto-Configや@Configurationクラス等は読み込まれません。
この場合は、Dozerを利用するための設定が必要になります。

  • @ImportAutoConfiguration -> DozerのAuto-Configを読み込みます。
  • @Import -> カスタムマッピングを読み込みます。

src/test/java/*/web/TodoControllerTest.java

@WebMvcTest
@ImportAutoConfiguration(DozerAutoConfiguration.class) // (1)
@Import(DozerConfig.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でDozerのセットアップを行うDozerAutoConfigurationクラスを読み込みます。

(2) @Importでカスタムマッピングの@Configurationクラスを読み込みます。配列で複数読み込むことも可能です。

Note.
カスタムマッピングを@Componentで作った場合、@Importの代わりに@ComponentScanで読み込めばOKです。

まとめ

DozerのStarterは古いAPIに引き摺られているせいか、BootのStarterとしてはちょっと最適化されてない感がありますね。

Java Configを使いたい場合もXMLと併用する必要があるのはちょっと、、、ですが、
Dozerを実開発で使う場合には、全体的な振る舞いをどこに集約するか、カスタムマッピングをどう管理するか、あたりが課題になると思うので、ある意味切り分けしやすくて良いかもしれません。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5