※当サイトの記事には、広告・プロモーションが含まれます。

Spring Web MVCの@ModelAttributeの挙動が分かり辛い

nazology.net

⇧ amazing...

正式名称はSpring Web MVCらしい

Spring MVCとSpring Web MVCどっちが正しいのかモヤモヤしてたのですが、

spring.pleiades.io

Spring Web MVC は、サーブレット API に基づいて構築されたオリジナルの Web フレームワークであり、最初から Spring Framework に含まれています。正式名称 "Spring Web MVC" は、そのソースモジュール ( spring-webmvc [GitHub] (英語)  ) の名前に由来しますが、より一般的には "Spring MVC" として知られています。

https://spring.pleiades.io/spring-framework/docs/current/reference/html/web.html

⇧ 公式の見解は、「Spring Web MVC」が正式名称と言うことのようです。

Spring Web MVCの@ModelAttributeの挙動が分かり辛い

どうやら、

teratail.com

ja.stackoverflow.com

⇧ 使い方が2つあるのがそもそも混乱を招くのですが...

メソッド間で値が引き継がれない?

何やら、

stackoverflow.com

⇧ Controllerのメソッドの引数に@ModelAttributeを使うと、メソッド間で値が引き継がれないらしい。

メソッドの引数で使用しない方法の挙動は確かに意味がよく分からない...

公式のドキュメントに載っている、

spring.pleiades.io

@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) { 
    // ...
}

https://spring.pleiades.io/spring-framework/docs/current/reference/html/web.html

⇧ とあるように、一番上の例が、return new AccountForm()って感じになってるので、毎回初期化されるん?って思ってしまうんですが、デバッグで動作確認してみるとかなりブラックボックスな挙動でした。

試しに、

ts0818.hatenablog.com

⇧ 上記の記事の時のソースコードを改良して、メソッド間をリレーするような形にしてみて、動作を確認してみました。

変更したのはControllerクラスのみです。

package com.controller;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.dto.FromDto;
import com.dto.GenderDto;
import com.entity.UserDetail;
import com.entity.ex.ExUserAndAddressDetail;
import com.form.ExampleForm;
import com.service.UserDetailServiceImpl;
import com.service.ex.ExUserAndAddressDetailServiceImpl;

@Controller
@RequestMapping("homeController")
public class HomeController {
	
	@Autowired
	private UserDetailServiceImpl userDetailServiceImpl;

	@Autowired
	private ExUserAndAddressDetailServiceImpl exUserAndAddressDetailServiceImpl;
	
	@ModelAttribute
	public ExampleForm initForm() {
		ExampleForm exampleForm = new ExampleForm();
		return exampleForm;
	}
	
	@GetMapping("home")
	public String index(Model model) {
		List<String> list = new ArrayList<>();
		list.add("リスト1");
		list.add("リスト2");
		list.add("リスト3");
		model.addAttribute("strList", list);
		return "home/index";
	}
	
	@GetMapping("find-all")
	public ModelAndView findAll() throws SQLException {
		List<UserDetail> userDetailList = userDetailServiceImpl.findAll();
		ModelAndView mv = new ModelAndView("home/index");
		mv.addObject("userDetailList", userDetailList);
		return mv;
	}
	
	@GetMapping("find-all-ex")
	public ModelAndView findAllEx() throws SQLException {
		List<ExUserAndAddressDetail> exUserAndAddressDetailList = exUserAndAddressDetailServiceImpl.findAll();
		ModelAndView mv = new ModelAndView("home/index");
		mv.addObject("exUserAndAddressDetailList", exUserAndAddressDetailList);
		return mv;
	}
	
	@RequestMapping("init")
	public ModelAndView initialForm(ExampleForm exampleForm) {
		List<FromDto> fromDtoList = new ArrayList<>();
		fromDtoList.add(new FromDto(1, "北海道"));
		fromDtoList.add(new FromDto(2, "東北"));
		fromDtoList.add(new FromDto(3, "東京"));
		fromDtoList.add(new FromDto(4, "名古屋"));
		fromDtoList.add(new FromDto(5, "京都"));
		fromDtoList.add(new FromDto(6, "大阪"));
		fromDtoList.add(new FromDto(7, "九州"));

		List<GenderDto> genderDtoList = new ArrayList<>();
		genderDtoList.add(new GenderDto(0, "男性"));
		genderDtoList.add(new GenderDto(1, "女性"));
		
		ModelAndView mv = new ModelAndView("home/index");
		mv.addObject("fromDtoList", fromDtoList);
		mv.addObject("genderDtoList", genderDtoList);
		mv.addObject("exampleForm", exampleForm);
		return mv;
	}
	
	@RequestMapping("send")
	public ModelAndView send(ExampleForm exampleForm) {
		ModelAndView mv = new ModelAndView("forward:/homeController/search");
		return mv;
	}
	
	@RequestMapping("search")
	public ModelAndView searchRequest(ExampleForm exampleForm) {
		ModelAndView mv = new ModelAndView("forward:/homeController/reset");		
		return mv;
	}
	
	@RequestMapping("reset")
	public ModelAndView resetForm(ExampleForm exampleForm) {
		ModelAndView mv = new ModelAndView("forward:/homeController/init");
		exampleForm = new ExampleForm();
		mv.addObject("exampleForm", exampleForm);
		return mv;		
	}
}
   

⇧ で保存して、デバッグしてみたところ、

 

⇧ とメソッドが呼ばれる度に@ModelAttributeの付いたメソッドが呼ばれて初期化してる意味が確かによく分からない...

う~む、公式の見本が良くないんかな...

公式のドキュメントを再掲するけど、

@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

⇧ これ、何がしたいのか意図がサッパリ分からん...公式のドキュメントでこれだと辛い...

フレームワークは使い方について、知識の無い人間にも分かるように丁寧に記載してくれるとありがたいんですけどね...

毎度モヤモヤ感が半端ない...

今回はこのへんで。