15. Web MVC framework を適当に訳してみる(Spring 3)
Spring 3 Referrence - 15. Web MVC frameworkを参考にする。15.1 Spring MVC Framework の紹介Spring Web Model-View-Controller Frameworkは、DispatcherServletを基本に実装されている。DispatcherServletは、Servletへの要求を適切なHandlerに割り振る。もっとも基本的な設定では、@Controller と @RequestMapping のアノテーションで決まる。※ここ重要RESTfulなWebServiceも作成できる。その際、@PathVariable で、引数を引き渡すことができる。SpringMVCでは、どんなオブジェクトでも使用できる。特別な実装も必要としない。Springは、実に高度な柔軟性を持っていて、どんな型でも、データバインディング可能また、Springは、Viewに関しても非常に高い柔軟性を持っている。一般的に、ModelAndViewは、ViewNameとModel(Data)から構成されている。Viewの名前解決も設定ファイルで柔軟に設定可能。Bean名とかPropertyファイルとか。独自のViewResolverを使うこともできるModel(MVCのなかのM)は、Mapをベースに実装されている。JSPとかVelocityなど(FreeMarkerとかも!)のテンプレートエンジンも自由自在。Mapベースで、JSPやVelocityに対応した変換も勝手にやってくれる15.1.1 SpringMVCの特徴・役割の分離がはっきりしている。coltroller, validator, command object, form object, model object, DispatcherServlet, handler mapping, view resolver. とこれら専門的なObjectがそれぞれの役割を果たしている・強力で簡単な設定。JavaBeansを介して、簡単に参照可能。(@Autowired とかでできるし、Bean定義ファイルで参照も簡単)・@RequestParam, @RequestHeader, @PathVariable をつかって、引数を簡単にひも付できる。 - @RequestParam は、GET Methodで、 hoge.do?id=bar で、@RequestParam(id) long id ; みたいな -@RequestHeaderは、@RequestHeader("User-Agent") String ua: で、UserAgentを取得・・・みたいな -@PathVariable は、@RequestMapping("/hoge/{id}"} で、 @PathVariable("id) Integer id; みたいな・再利用性が高く、ビジネスロジックを複製したりする必要はない・カスタマイズ可能なデータバインディングとバリデート機能(入力値検査機能)・Handler mapping と View Resolverのカスタマイズが可能・柔軟性の高いモデル変換。このため、さまざまなViewテクノロジーに対応している・JSPとかのタグが豊富(すごく適当15.1.2 他のMVCモデルFrameworkもプラグインで使用可能(略)15.2 DispatcherServletSpring MVC Framework は、他のMVCFrameworkと一緒で、リクエスト駆動。中心となるServletがあり、それが、Contorollerへの割り付けなど様々な処理を行う。Spring IoC (DI的な)が使える。(適当すぎて・・)簡単なweb.xmとしては、------------------------------------------------<web-app> <servlet> <servlet-name>example</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>example</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping></web-app>-------------------------------------------------この例では、 .form なファイルが、exampleという名前のDispatcherServletで処理される。で、デフォルトでは、WEB-INF直下にある [servlet-name]-servlet.xml を読む。(applicationContext.xmlの内容ですな)<servlet-name>golfer</servlet-name> なら /WEB-INF/golfer-servlet.xml になる。変更は、プロパティの設定で可能。WebApplicationContextは、平のApplicationContext を拡張したもの。SpringのDispatcherServletは、特別なbeanを使って、リクエストの処理やレンダリング処理を行う。これらのBeanは、SpringFrameworkで提供されている。・controllers - MVCのCを担当する・handler mappings・view resolvers - ViewのためのViewNameの解決・locale resolvers - 国際化対応。・Theme resolver - (※よくわからない・・・)・multipart file resolver - ファイルのアップロード用・handler exception resolver - 例外が発生した場合に、どのViewを表示するか?といった割り付け、処理を行う15.3 Controllerの実装15.3.1 @Controllerによる controllerの定義@Controllerというステレオタイプアノテーションで定義@Controllerは、[servlet-name]-servlet.xmlの中で、<context:component-scan base-package="hoge.bar"/>と書いてあると、hoge.bar 以下のディレクトリを自動的に走査して、DispatcherServlet(?)に登録する。15.3.2 @RequestMappingによるリクエスト処理@RequestMappingアノテーションは、class level と method level で記述できる。--------------------------------------------------------------------------------------@Controller@RequestMapping("/appointments")public class AppointmentsController { @Autowired private final AppointmentBook appointmentBook; @RequestMapping(method = RequestMethod.GET) public Map<String, Appointment> get() { return appointmentBook.getAppointmentsForToday(); } @RequestMapping(value="/{day}", method = RequestMethod.GET) public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) { return appointmentBook.getAppointmentsForDay(day); } @RequestMapping(value="/new", method = RequestMethod.GET) public AppointmentForm getNewForm() { return new AppointmentForm(); } @RequestMapping(method = RequestMethod.POST) public String add(@Valid AppointmentForm appointment, BindingResult result) { if (result.hasErrors()) { return "appointments/new"; } appointmentBook.addAppointment(appointment); return "redirect:/appointments"; }}-----------------------------------------------------------------------------------------------------Classレベルで記述すると、そのControllerはのBaseURL的になる。Methodレベルで記述すると、クラスレベルの記述+パス (とGET、POSTなどのHTTPMedthod指定)・ GET /appointments → get() が呼び出されるClassレベルの@RequestMappingがない場合は、Methodレベルの@RequestMappingの絶対パスになる。------------------------------------------------------------------------------------------------------@Controllerpublic class ClinicController { private final Clinic clinic; @Autowired public ClinicController(Clinic clinic) { this.clinic = clinic; } @RequestMapping("/") public void welcomeHandler() { } @RequestMapping("/vets") public ModelMap vetsHandler() { return new ModelMap(this.clinic.getVets()); }}------------------------------------------------------------------------------------------この場合は、・「/」にアクセスすると、welcomHandler() に。(GET、POSTと何が来ても)・「/vets」にアクセスすると、vetsHandler() に。15.3.2.1 URI Templates@PathVariable------------------------------------------------------------------@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)public String findOwner(@PathVariable String ownerId, Model model) { Owner owner = ownerService.findOwner(ownerId); model.addAttribute("owner", owner); return "displayOwner";}------------------------------------------------------------------こうすると、/owners/nobita にアクセスがあると、findOwner には、onwerId に差し込まれる。パラメータ名を指定することも可能public String findOwner(@PathVariable("ownerId") String ownerId, Model model) {public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {と自由自在に割り当て可能。@PathVariable を複数も可能--------------------------------------------------------------------------------------@RequestMapping(value="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { Owner owner = ownerService.findOwner(ownderId); Pet pet = owner.getPet(petId); model.addAttribute("pet", pet); return "displayPet";}--------------------------------------------------------------------------------------@Controller@RequestMapping("/owners/{ownerId}")public class RelativePathUriTemplateController { @RequestMapping("/pets/{petId}") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted }}--------------------------------------------------------------------------------------こんな風に、ClassレベルのアノテーションとMethodレベルのアノテーションを組み合わせることも可能※@PathVariableは、int, long, Date等も可能。Springは、自動的にこの変換をしてくれる。もし引数の型が違ったら(Longの引数のはずのところにStringの「hoge」とかが入ったら)、TypeMissmatchExceptionを発行する。→ 後述で、ここをカスタマイズする方法を述べる(15.3.2.12)15.3.2.2 Advanced @RequestMapping options/owners/*/pets/{petId} といった指定も可能。( * とか正規表現的なものも)パラメータの内容でフィルタリングも可能--------------------------------------------------------------------------------------@Controller@RequestMapping("/owners/{ownerId}")public class RelativePathUriTemplateController { @RequestMapping(value = "/pets/{petId}", params="myParam=myValue") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted }}--------------------------------------------------------------------------------------headerの内容でフィルタリングすることも可能--------------------------------------------------------------------------------------@Controller@RequestMapping("/owners/{ownerId}")public class RelativePathUriTemplateController {@RequestMapping(value = "/pets", method = RequestMethod.POST, headers="content-type=text/*") public void addPet(Pet pet, @PathVariable String ownerId) { // implementation omitted }}--------------------------------------------------------------------------------------※この二つはそれほど使わない気もしますが。(私はたぶん使わない)15.3.2.3 Supported handler method arguments and return types引数としていろいろ使える。そして、どの順番でも任意。( @PathVariable String hoge, Model mode とか)組み合わせは自由引数としてサポートしてるのは、・Request or response objects (たとえば、ServletRequest or HttpServletRequest)・Session object たとえば HttpSession。 ※thread-safeじゃないので、複数同時リクエストが発生する環境下では、synchronizeOnSession フラグを trueにすべし・org.springframework.web.context.request.WebRequest とか org.springframework.web.context.request.NativeWebRequest.・java.util.Locale・java.io.InputStream / java.io.Reader・java.io.OutputStream / java.io.Writer・java.security.Principal ※要調査・@PathVariable でとれるもの。(15.3.2.2 で解説済み)・@RequestParam でとれるもの(15.3.2.4 で解説予定) ?hoge=bar で、@RequestParam("hoge") String hoge に barがはいるようなもの・@RequestHeader でとれるもの。 @RequestHeader("User-Agent") String ua とか @RequestHeader("host") String ip とか・@RequestBody 出力の際。テンプレートを使わないで、XML出力したり、JSON出力したり。(詳細は、15.3.2.5にて)・HttpEntity<?> Header情報にアクセスできる?(15.3.2.7にて詳細)・java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap これに入れるとViewに値を渡せる。・Command とか form object @ModelAttributeで解説・・・かな?・org.springframework.validation.Errors / org.springframework.validation.BindingResult Validationに失敗とか。・org.springframework.web.bind.support.SessionStatus @SessionAttributes を使って@ModelAttributeとBindingResultとModelとかは順番がある。--------------------------------------------------------------@RequestMapping(method = RequestMethod.POST)public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { … }--------------------------------------------------------------BindigResultの後に、Modelが来る必要がある。(順番を変えちゃいけないのはこの例だけかな)出力としてサポートしているのは、・ModelAndViewオブジェクト・Modelオブジェクト・Mapオブジェクト・Viewオブジェクト・String : view name を返す。・void : method名で、viewが決まる場合とか。(規約的な動き、HogeControllerなら、デフォルトでは、 hoge.jspとかを見に行くようになってるはず)・@ResponseBody アノテーション 15.2.3.6 にて詳細・HttpEntity<?> 、 ResponseEntity<?> 15.3.2.4 Binding request parameters to method parameters with @RequestParamパラメータ名をバインディング----------------------------------------------------------------------------------@Controller@RequestMapping("/pets")@SessionAttributes("pet")public class EditPetForm { // ... @RequestMapping(method = RequestMethod.GET) public String setupForm(@RequestParam("petId") int petId, ModelMap model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute("pet", pet); return "petForm"; } // ...----------------------------------------------------------------------------------この場合、petId は、入力必須になるけれども、@RequestParam(value="petId", required=false) とすれば、petIdがなくてもOK15.3.2.5 Mapping the request body with the @RequestBody annotation----------------------------------------------------------------------------------@RequestMapping(value = "/something", method = RequestMethod.PUT)public void handle(@RequestBody String body, Writer writer) throws IOException { writer.write(body);}----------------------------------------------------------------------------------リクエストをそのまま出力する例15.3.2.6 Mapping the response body with the @ResponseBody annotation----------------------------------------------------------------------------------@RequestMapping(value = "/something", method = RequestMethod.PUT)@ResponseBodypublic String helloWorld() { return "Hello World";}----------------------------------------------------------------------------------Hellow Worldと出力する簡単な例(HTTP Streamにて)String型で返す時は、HttpMessageConverter にて変換している。他に変換したい場合は、MessageConverterにて詳細を読むべし15.3.2.7 Using HttpEntity<?>HttpEntity は、ReponseBody と RequestBody に似ている。----------------------------------------------------------------------------------@RequestMapping("/something")public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException { String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader")); byte[] requestBody = requestEntity.getBody(); // do something with request header and body HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.set("MyResponseHeader", "MyValue"); return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);}----------------------------------------------------------------------------------※よーわからん15.3.2.8 Providing a link to data from the model with @ModelAttribute@ModelAttributeは、二つのレベルで使用する。MethodレベルとParameterレベル※Methodレベルがよくわからない・・・・・どういうこっちゃ?----------------------------------------------------------------------------------@Controller@RequestMapping("/owners/{ownerId}/pets/{petId}/edit")@SessionAttributes("pet")public class EditPetForm { // ... @ModelAttribute("types") public Collection<PetType> populatePetTypes() { return this.clinic.getPetTypes(); } @RequestMapping(method = RequestMethod.POST) public String processSubmit( @ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) { new PetValidator().validate(pet, result); if (result.hasErrors()) { return "petForm"; } else { this.clinic.storePet(pet); status.setComplete(); return "redirect:owner.do?ownerId=" + pet.getOwner().getId(); } }----------------------------------------------------------------------------------15.3.2.9 Specifying attributes to store in a session with @SessionAttributes----------------------------------------------------------------------------------@Controller@RequestMapping("/editPet.do")@SessionAttributes("pet")public class EditPetForm { // ...}----------------------------------------------------------------------------------うーん・・・・※要調査15.3.2.11 Mapping request header attributes with the @RequestHeader annotation--------------------------------------------------------------------Host localhost:8080Accept text/html,application/xhtml+xml,application/xml;q=0.9Accept-Language fr,en-gb;q=0.7,en;q=0.3Accept-Encoding gzip,deflateAccept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7Keep-Alive 300--------------------------------------------------------------------なリクエストの場合に、@RequestMapping("/displayHeaderInfo.do")public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { //...}で、encodingには、 gzip, deflate がkeepaAliveには、300がはいる。Hostもよく使うかも(アクセス元の制限とかをアプリ内で実装する場合とか)15.3.2.12 Customizing WebDataBinder initialization二つの方法がある1) @InitBinder アノテーションController内で設定が可能。@Controllerpublic class MyFormController { @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } // ...}Dateクラスを整形する設定になる2)WebBindingInitializer 設定を外部ファイルに出した方法。WebBindingInitializer インタフェースを実装したorg.springframework.samples.petclinic.web.ClinicBindingInitializerを使っている・・・がどうやって使うかいまいちわからない・・・ orz