Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

【Spring fest 2019】徹底解剖Spring MVCアーキテクチャー

6,780 views

Published on

Spring fest 2019 の発表資料です。

Published in: Technology
  • Be the first to comment

【Spring fest 2019】徹底解剖Spring MVCアーキテクチャー

  1. 1. 徹底解剖 Spring MVCアーキテクチャー -DispatcherServletの中身を覗いてきました- 株式会社カサレアル 菊池 真登 2019/12/18 SPRING FEST ❜19 (C) CASAREAL, Inc. All rights reserved. 1
  2. 2. 突然ですが (C) CASAREAL, Inc. All rights reserved. 2 @Controller public class TestController { @PostMapping("/test") public String post(@Validated TestForm testForm, BindingResult result, Model model) { return "test"; } } ごくごく一般的なコントローラー の実装ですよね!?
  3. 3. 気になったことはありませんか? (C) CASAREAL, Inc. All rights reserved. 3 @Controller public class TestController { @PostMapping("/test") public String post(@Validated TestForm testForm, BindingResult result, Model model) { return "test"; } } リクエストURLと メソッドの関係は? 引数のインスタンス はどこからくる? 戻り値って文字列以外でもOK?
  4. 4. 気になったことはありませんか? (C) CASAREAL, Inc. All rights reserved. 4 @Controller public class TestController { @PostMapping("/test") public String post(@Validated TestForm testForm, BindingResult result, Model model) { return "test"; } } 引数に設定可能な 型って何個ある? @ValidatedとBindingResult以外って 順番決まりなさそうだけど。。。
  5. 5. くっ、黒魔術!? (C) CASAREAL, Inc. All rights reserved. 5
  6. 6. このセッションは (C) CASAREAL, Inc. All rights reserved. 6 Spring MVCのアーキテクチャーを ソースコードリーディングをもとに解き明か していく45分間です Spring MVCのアーキテクチャーをより詳しく 知りたい方が対象です 内部構造を詳細に把握することでトラブルに 強くなれます
  7. 7. 自己紹介:菊池真登 (C) CASAREAL, Inc. All rights reserved. 7  研修トレーナー@カサレアル  登壇実績:Spring / Java SE  開発歴[Spring]:2012年から  開発歴[Angular]:2015年頃に 1.5年間AngularJSに触れて冬眠
  8. 8. 株式会社カサレアル (C) CASAREAL, Inc. All rights reserved. 8 他社にはない色々なプログラミング言語の 研修を提供しています!
  9. 9. 目次 (C) CASAREAL, Inc. All rights reserved. 9 1.Spring MVC のアーキテクチャー 2.URLとコントローラーメソッド 3.コントローラーメソッドの引数 4.コントローラーメソッドの戻り値 5.全体を俯瞰
  10. 10. 目次 (C) CASAREAL, Inc. All rights reserved. 10 1.Spring MVC のアーキテクチャー 2.URLとコントローラーメソッド 3.コントローラーメソッドの引数 4.コントローラーメソッドの戻り値 5.全体を俯瞰
  11. 11. Spring MVC のアーキテクチャー (C) CASAREAL, Inc. All rights reserved. 11 Dispatcher Servlet Handler Mapping Handler Adapter View Resolver Model Controller view name View 1 2 3 4 56
  12. 12. 関係を詳しく見ると (C) CASAREAL, Inc. All rights reserved. 12 Dispatcher Servlet DIコンテナ Handler Mapping Handler Adapter DispatcherServletはDIコンテナをもつ そのDIコンテナがHandlerMappingや HandlerAdapterなどをもつ
  13. 13. 処理実行時は (C) CASAREAL, Inc. All rights reserved. 13 Dispatcher Servlet Handler Adapter Handler Mapping DispatcherServletの初期化処理で DIコンテナからインスタンスを取り出し フィールドに直接保持する
  14. 14. DispatcherServlet#initHandlerMappings() - 一部 (C) CASAREAL, Inc. All rights reserved. 14 private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings); } } インスタンスを取り出し、 フィールド変数に代入
  15. 15. HandlerMapping (C) CASAREAL, Inc. All rights reserved. 15 Dispatcher Servlet Handler Mapping Controller 1 リクエストURLとコントローラー クラスのメソッドとの関係とは?
  16. 16. HandlerAdapter – 実行前 (C) CASAREAL, Inc. All rights reserved. 16 Dispatcher Servlet Handler Adapter Controller 2 3 コントローラークラスのメソッド の引数はどのようにして解決?
  17. 17. HandlerAdapter – 実行後 (C) CASAREAL, Inc. All rights reserved. 17 Dispatcher Servlet Handler Adapter Controller view name 2 3 4 コントローラークラスのメソッドの 戻り値はどのように処理される?
  18. 18. 目次 (C) CASAREAL, Inc. All rights reserved. 18 1.Spring MVC のアーキテクチャー 2.URLとコントローラーメソッド 3.コントローラーメソッドの引数 4.コントローラーメソッドの戻り値 5.全体を俯瞰
  19. 19. Spring MVC のアーキテクチャー (C) CASAREAL, Inc. All rights reserved. 19 Dispatcher Servlet Handler Mapping Handler Adapter View Resolver Model Controller view name View 1 2 3 4 56
  20. 20. HandlerMapping (C) CASAREAL, Inc. All rights reserved. 20 Dispatcher Servlet Handler Mapping Controller 1 リクエストURLとコントローラー クラスのメソッドとの関係とは?
  21. 21. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 21  HandlerMapping  RequestMappingInfo  MappingRegistry  HandlerMethod  AbstractHandlerMethodMapping
  22. 22. ざっくりとした関係図 (C) CASAREAL, Inc. All rights reserved. 22 HandlerMapping MappingRegistry RequestMappingInfo RequestMappingInfo RequestMappingInfo HandlerMethod HandlerMethod HandlerMethod
  23. 23. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 23  HandlerMapping  RequestMappingInfo  MappingRegistry  HandlerMethod  AbstractHandlerMethodMapping
  24. 24. 関係図 (C) CASAREAL, Inc. All rights reserved. 24 HandlerMapping
  25. 25. HandlerMapping (C) CASAREAL, Inc. All rights reserved. 25  インタフェース  getHandlerメソッドが一つだけ定義されている  戻り値が現在のリクエストを処理するメソッドになる HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
  26. 26. RequestMappingHandlerMapping (C) CASAREAL, Inc. All rights reserved. 26  HandlerMappingを実装したクラス  コントローラークラス用  @EnableWebMvcアノテーションで有効化  Spring Bootではアノテーションは不要  一般的には下記の説明 Bean定義されているContorllerから @RequestMappingアノテーションを読み取り、 URLと合致するControllerのメソッドを探す
  27. 27. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 27  HandlerMapping  RequestMappingInfo  MappingRegistry  HandlerMethod  AbstractHandlerMethodMapping
  28. 28. HandlerMapping 関係図 (C) CASAREAL, Inc. All rights reserved. 28 RequestMappingInfo RequestMappingInfo RequestMappingInfo
  29. 29. RequestMappingInfo (C) CASAREAL, Inc. All rights reserved. 29  @RequestMappingの設定値を保持する  コントローラーメソッドの定義情報を表すクラス  コントローラーメソッドの数だけ作られる  1メソッド : 1Info  アプリ実行中はMappingRegistryに保持される
  30. 30. RequestMappingInfo (C) CASAREAL, Inc. All rights reserved. 30 public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> { @Nullable private final String name; private final PatternsRequestCondition patternsCondition; private final RequestMethodsRequestCondition methodsCondition; @RequestMappingの value:URL method:リクエストメソッド の設定値を保持する
  31. 31. RequestMappingInfo 続き (C) CASAREAL, Inc. All rights reserved. 31 private final ParamsRequestCondition paramsCondition; private final HeadersRequestCondition headersCondition; private final ConsumesRequestCondition consumesCondition; private final ProducesRequestCondition producesCondition; private final RequestConditionHolder customConditionHolder; params headers consumes produces の設定値を 保持する
  32. 32. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 32  HandlerMapping  RequestMappingInfo  MappingRegistry  HandlerMethod  AbstractHandlerMethodMapping
  33. 33. 関係図 (C) CASAREAL, Inc. All rights reserved. 33 HandlerMapping MappingRegistry RequestMappingInfo RequestMappingInfo RequestMappingInfo
  34. 34. MappingRegistry (C) CASAREAL, Inc. All rights reserved. 34  リクエストURLとRequestMappingInfoを紐づける  AbstractHandlerMethodMapping内にあるクラス  アクセス修飾子なしのクラス  実行中はAbstractHandlerMethodMappingのフィールドに インスタンスが保持される
  35. 35. MappingRegistry (C) CASAREAL, Inc. All rights reserved. 35 class MappingRegistry { private final Map<RequestMappingInfo,HandlerMethod> mappingLookup; private final MultiValueMap<String, RequestMappingInfo > urlLookup; @RequestMappingアノテーションのvalue属性すなわち URL を key にして、 RequestMappingInfo が保持される RequestMappingInfo を key に HandlerMethod が保持される *見やすさのため、実際のソースコードとは異なります
  36. 36. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 36  HandlerMapping  RequestMappingInfo  MappingRegistry  HandlerMethod  AbstractHandlerMethodMapping
  37. 37. 関係図 (C) CASAREAL, Inc. All rights reserved. 37 HandlerMapping MappingRegistry RequestMappingInfo RequestMappingInfo RequestMappingInfo HandlerMethod HandlerMethod HandlerMethod
  38. 38. HandlerMethod (C) CASAREAL, Inc. All rights reserved. 38  コントローラーメソッドの化身  メソッドのリフレクションをもつ  フレームワーク内部でコントローラメソッドを 取り扱うためのもの
  39. 39. ここで、 RequestMappingInfoとHandlerMethod の関係を整理しましょう (C) CASAREAL, Inc. All rights reserved. 39
  40. 40. 【例】コントローラークラスの定義 (C) CASAREAL, Inc. All rights reserved. 40 @Controller public class TestController { @GetMapping("/") public String index() { return "index"; } @GetMapping("/test") public String get() { return "get"; } @PostMapping("/test") public String post() { return "post"; } } ・URL:”/”、メソッド: GET ・URL:”/test”、メソッド GET ・URL:”/test”、メソッド POST の3つを定義する
  41. 41. MappingRegistryのurlLookupの状態 (C) CASAREAL, Inc. All rights reserved. 41 “/” “/test” Patterns:”/” Methods:”GET” Patterns:”/test” Methods:”GET” Patterns:”/test” Methods:”POST” key : URL value : RequestMappingInfo
  42. 42. MappingRegistryのmappingLookupの状態 (C) CASAREAL, Inc. All rights reserved. 42 Patterns:”/” Methods:”GET” Patterns:”/test” Methods:”GET” Patterns:”/test” Methods:”POST” key : RequestMappingInfo value : HandlerMethod TestController#index() TestController#get() TestController#post()
  43. 43. まとめると (C) CASAREAL, Inc. All rights reserved. 43 Patterns:”/” Methods:”GET” Patterns:”/test” Methods:”GET” Patterns:”/test” Methods:”POST” TestController#index() TestController#get() TestController#post() “/” “/test” URL RequestMappingInfo HandlerMethod
  44. 44. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 44  HandlerMapping  RequestMappingInfo  MappingRegistry  HandlerMethod  AbstractHandlerMethodMapping
  45. 45. 継承関係 (C) CASAREAL, Inc. All rights reserved. 45 RequestMappingHandlerMapping AbstractHandlerMethodMapping HandlerMapping
  46. 46. AbstractHandlerMethodMapping (C) CASAREAL, Inc. All rights reserved. 46  HandlerMappingの処理の根幹  RequestMappingHandlerMappingが継承  lookupHandlerMethodメソッドが肝! ざっくりいうと下記の二段構成 1. リクエストURLと完全一致するものを探す 2. なければ全検索でパターンマッチを行う
  47. 47. リクエストURLと完全一致するものを探す (C) CASAREAL, Inc. All rights reserved. 47
  48. 48. AbstractHandlerMethodMapping#lookupHandlerMethod() (C) CASAREAL, Inc. All rights reserved. 48 protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } リクエストURLをKeyに RequestMappingInfoを取り出す
  49. 49. MappingRegistry#getMappingsByUrl() (C) CASAREAL, Inc. All rights reserved. 49 class MappingRegistry { private final MultiValueMap<String, RequestMappingInfo> urlLookup public List<RequestMappingInfo> getMappingsByUrl(String urlPath) { return this.urlLookup.get(urlPath); }
  50. 50. 【例】リクエストURLが / の場合 (C) CASAREAL, Inc. All rights reserved. 50 “/” Patterns:”/” Methods:”GET” このRequestMappingInfo が選択される key : URL value : RequestMappingInfo
  51. 51. 【例】リクエストURLが /test の場合 (C) CASAREAL, Inc. All rights reserved. 51 “/test” Patterns:”/test” Methods:”GET” Patterns:”/test” Methods:”POST” これらのRequestMappingInfo が選択される key : URL value : RequestMappingInfo
  52. 52. リクエストメソッドなど細かい判定を行う (C) CASAREAL, Inc. All rights reserved. 52  リクエストメソッド等細かな条件で絞り込む  RequestMappingInfo#getMatchingCondition()で行う  @RequestMappingのすべての属性値が判定対象  method  params  headers  consumes  produces
  53. 53. AbstractHandlerMethodMapping#lookupHandlerMethod() (C) CASAREAL, Inc. All rights reserved. 53 protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); List<RequestMappingInfo> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } リクエストメソッドなどの より細かな精査を行う
  54. 54. RequestMappingInfo#getMatchingCondition() (C) CASAREAL, Inc. All rights reserved. 54 public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request); if (methods == null) { // リクエストメソッドの判定 return null; } ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request); if (params == null) { // リクエストパラメータの判定 return null; } 先ほどのaddMatchingMappings メソッドの呼び出しで 最終的に呼ばれるメソッド
  55. 55. getMatchingCondition - 続き (C) CASAREAL, Inc. All rights reserved. 55 HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request); if (headers == null) { // リクエストヘッダーの判定 return null; } ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request); if (consumes == null) { // Content-Typeの判定 return null; }
  56. 56. getMatchingCondition - 続き (C) CASAREAL, Inc. All rights reserved. 56 ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request); if (produces == null) { // Acceptの判定 return null; } PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request); if (patterns == null) { // リクエストURLのパターン判定 return null; }
  57. 57. getMatchingCondition - 続き (C) CASAREAL, Inc. All rights reserved. 57 RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request); if (custom == null) { // カスタム判定 return null; } return new RequestMappingInfo(this.name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }
  58. 58. (C) CASAREAL, Inc. All rights reserved. 58
  59. 59. なければ全検索でパターンマッチを行う @RequestMappingにワイルドカード を指定した場合など (C) CASAREAL, Inc. All rights reserved. 59
  60. 60. (C) CASAREAL, Inc. All rights reserved. 60 protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { addMatchingMappings( this.mappingRegistry.getMappings().keySet(), matches, request); } 全RequestMappingInfoから パターンマッチを行う AbstractHandlerMethodMapping#lookupHandlerMethod()
  61. 61. 【例】コントローラークラスの定義 (C) CASAREAL, Inc. All rights reserved. 61 @Controller public class TestController { @GetMapping("/**") public String index() { return "index"; } @GetMapping("/test") public String get() { return "get"; } } ・URL:”/**”、メソッド: GET ・URL:”/test”、メソッド GET の2つを定義する
  62. 62. MappingRegistryのurlLookupの状態 (C) CASAREAL, Inc. All rights reserved. 62 “/**” “/test” Patterns:”/**” Methods:”GET” Patterns:”/test” Methods:”GET” key : URL value : RequestMappingInfo
  63. 63. MappingRegistryのmappingLookupの状態 (C) CASAREAL, Inc. All rights reserved. 63 Patterns:”/**” Methods:”GET” Patterns:”/test” Methods:”GET” TestController#index() TestController#get() key : RequestMappingInfo value : HandlerMethod
  64. 64. リクエストURL : “/” メソッド : “GET” (C) CASAREAL, Inc. All rights reserved. 64 Patterns:”/**” Methods:”GET” Patterns:”/test” Methods:”GET” TestController#index() TestController#get() “/**” “/test” リクエストURL : / は完全一致しない 全RequestMappingInfo からパターンマッチ を行う
  65. 65. パターンマッチは前述の RequestMappingInfo#getMatchingCondition() を使う (C) CASAREAL, Inc. All rights reserved. 65
  66. 66. RequestMappingInfo#getMatchingCondition() – 再掲 (C) CASAREAL, Inc. All rights reserved. 66 public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request); if (methods == null) { return null; } // 中略 PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request); if (patterns == null) { return null; } @GetMapping("/**") メソッド : GET、にマッチ @GetMapping("/**") URL : /**、にマッチ
  67. 67. 【例】コントローラークラスの定義 (C) CASAREAL, Inc. All rights reserved. 67 @Controller public class TestController { @GetMapping("/**") public String index() { return "index"; } @GetMapping("/test") public String get() { return "get"; } } ゆえに、indexメソッドが選択 される
  68. 68. (C) CASAREAL, Inc. All rights reserved. 68
  69. 69. このようにして選択されたHandlerMethodを HandlerAdapterが実行する (C) CASAREAL, Inc. All rights reserved. 69
  70. 70. 目次 (C) CASAREAL, Inc. All rights reserved. 70 1.Spring MVC のアーキテクチャー 2.URLとコントローラーメソッド 3.コントローラーメソッドの引数 4.コントローラーメソッドの戻り値 5.全体を俯瞰
  71. 71. Spring MVC のアーキテクチャー (C) CASAREAL, Inc. All rights reserved. 71 Dispatcher Servlet Handler Mapping Handler Adapter View Resolver Model Controller view name View 1 2 3 4 56
  72. 72. Handler Adapter – 実行前 (C) CASAREAL, Inc. All rights reserved. 72 Dispatcher Servlet Handler Adapter Controller 2 3 コントローラークラスのメソッド の引数はどのようにして解決?
  73. 73. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 73  HandlerMethodArgumentResolver  HandlerAdapter  ServletInvocableHandlerMethod
  74. 74. 関係図 (C) CASAREAL, Inc. All rights reserved. 74 HandlerAdapter HandlerMethodArgumentResolver ServletInvocableHandlerMethod (リクエストごとに作られる)
  75. 75. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 75  HandlerMethodArgumentResolver  HandlerAdapter  ServletInvocableHandlerMethod
  76. 76. 関係図 (C) CASAREAL, Inc. All rights reserved. 76 HandlerMethodArgumentResolver
  77. 77. HandlerMethodArgumentResolver (C) CASAREAL, Inc. All rights reserved. 77  コントローラーメソッドの引数の調達役  インタフェースであり、これを多数のクラスが実装  Form  BindingResult  Servlet API など、各クラス分実装クラスが存在する  Spring SecurityやSpring Data Commons 等にも実装クラス が存在する  メソッドで UserDetails 等を取得するのもこの機能
  78. 78. HandlerMethodArgumentResolver (C) CASAREAL, Inc. All rights reserved. 78 public interface HandlerMethodArgumentResolver { boolean supportsParameter(MethodParameter parameter); Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; }
  79. 79. HandlerMethodArgumentResolver (C) CASAREAL, Inc. All rights reserved. 79  supportsParameter()  メソッド引数の型や付加したアノテーションなどか らサポート対象かを判定するメソッド  resolveArgument()  メソッド引数のインスタンスを調達するメソッド 以後、ArgumentResolver と省略した場合は、 こちらを示しています。
  80. 80. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 80  HandlerMethodArgumentResolver  HandlerAdapter  ServletInvocableHandlerMethod
  81. 81. 関係図 (C) CASAREAL, Inc. All rights reserved. 81 HandlerAdapter HandlerMethodArgumentResolver
  82. 82. HandlerAdapter (C) CASAREAL, Inc. All rights reserved. 82  HandlerMappingで選択されたHandlerMethodを実行する  インタフェース  handle()メソッド内で HandlerMethod を実行する
  83. 83. RequestMappingHandlerAdapter (C) CASAREAL, Inc. All rights reserved. 83  @RequestMappingで定義されたHandlerMethodを サポートするHandlerAdapterの実装クラス  HandlerMethodArgumentResolverのインスタンスを保持  ServletInvocableHandlerMethodに HandlerMethodArgumentResolverのインスタンスを 渡して委譲する
  84. 84. 継承関係 (C) CASAREAL, Inc. All rights reserved. 84 RequestMappingHandlerAdapter HandlerAdapter
  85. 85. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 85  HandlerMethodArgumentResolver  HandlerAdapter  ServletInvocableHandlerMethod
  86. 86. 関係図 (C) CASAREAL, Inc. All rights reserved. 86 HandlerAdapter HandlerMethodArgumentResolver ServletInvocableHandlerMethod (リクエストごとに作られる)
  87. 87. ServletInvocableHandlerMethod (C) CASAREAL, Inc. All rights reserved. 87  HandlerMethodを継承している  RequestMappingHandlerAdapterから借り受けた HandlerMethodArgumentResolverを使って 引数のインスタンスを調達する  コントローラーメソッドを実行する
  88. 88. 継承関係 (C) CASAREAL, Inc. All rights reserved. 88 ServletInvocableHandlerMethod HandlerMethod
  89. 89. 関係図 (C) CASAREAL, Inc. All rights reserved. 89 RequestMapping HandlerAdapter ServletInvocable HandlerMethod リクエストごとに new this.argumentResolvers setHandlerMethodArgumentResolvers これ使って! 任せとけ!
  90. 90. InvocableHandlerMethod#invokeForRequest() (C) CASAREAL, Inc. All rights reserved. 90 public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } return doInvoke(args); } 引数のインスタンス を調達 メソッドの実行!
  91. 91. InvocableHandlerMethod#getMethodArgumentValues () (C) CASAREAL, Inc. All rights reserved. 91 protected Object[] getMethodArgumentValues( NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } 引数なしの場合は そのままreturn 引数リストを取得
  92. 92. InvocableHandlerMethod#getMethodArgumentValues() (C) CASAREAL, Inc. All rights reserved. 92 Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(); } try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { } } 引数一つずつを処理 サポート可能な引数 か判定 サポート可能ならイ ンスタンスを調達
  93. 93. 【例】コントローラークラスの定義 (C) CASAREAL, Inc. All rights reserved. 93 @Controller public class TestController { @PostMapping("/test") public String post(@Validated TestForm testForm, BindingResult result, Model model) { return "test"; } } ・Form ・BindingResult ・Model の三つの引数を設定
  94. 94. 動作概要 (C) CASAREAL, Inc. All rights reserved. 94 [0] [1] [2] TestForm BindingResult Model BindingResult担当 Form担当 Model担当 引数リスト ArgumentResolverたち
  95. 95. 引数リスト[0] (C) CASAREAL, Inc. All rights reserved. 95 [0] TestForm BindingResult担当 Form担当 Model担当 引数リスト 俺の出番 だな! サポート不可。。
  96. 96. 引数リスト[1] (C) CASAREAL, Inc. All rights reserved. 96 [1] BindingResult BindingResult担当 Form担当 Model担当 引数リスト 任せて! サポート不可。。
  97. 97. 引数リスト[2] (C) CASAREAL, Inc. All rights reserved. 97 [2] Model BindingResult担当 Form担当 Model担当 引数リスト いっきまーす! 出来ません!
  98. 98. 引数に指定可能な型は? (C) CASAREAL, Inc. All rights reserved. 98  指定可能な型が多すぎる https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring- framework-reference/web.html#mvc-ann-arguments  Spring Security、Spring Data Commons等を追加すると さらに増える  丸暗記ではなく取捨選択が重要  ソースコード上からも指定可能な型を確認できる
  99. 99. RequestMappingHandlerAdapter#getDefaultArgumentResolvers() (C) CASAREAL, Inc. All rights reserved. 99 private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false));
  100. 100. RequestMappingHandlerAdapter#getDefaultArgumentResolvers() (C) CASAREAL, Inc. All rights reserved. 100 resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver());
  101. 101. RequestMappingHandlerAdapter#getDefaultArgumentResolvers() (C) CASAREAL, Inc. All rights reserved. 101 // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
  102. 102. RequestMappingHandlerAdapter#getDefaultArgumentResolvers() (C) CASAREAL, Inc. All rights reserved. 102 // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }
  103. 103. お腹いっぱいですね。( ;∀;) (C) CASAREAL, Inc. All rights reserved. 103
  104. 104. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 104  Form  BindingResult  Servlet API  RequestBody のArgumentResolverをご紹介します。
  105. 105. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 105  Form  BindingResult  Servlet API  RequestBody
  106. 106. ServletModelAttributeMethodProcessor (C) CASAREAL, Inc. All rights reserved. 106  根幹は親クラスのModelAttributeMethodProcessor  Formクラスのインスタンスの生成と値の入力を行う  @Validatedがついていたら入力検証も行う  処理の最後にインスタンスのModelへの追加を行う
  107. 107. ModelAttributeMethodProcessor#supportsParameter() (C) CASAREAL, Inc. All rights reserved. 107 public boolean supportsParameter(MethodParameter parameter) { return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); } Number型やCharSequence型といった単純な型でない、 自作したFormクラスであればサポート対象
  108. 108. ModelAttributeMethodProcessor#resolveArgument() (C) CASAREAL, Inc. All rights reserved. 108 try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { throw ex; } if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } bindingResult = ex.getBindingResult(); } この処理が インスタンス生成の始まり
  109. 109. ModelAttributeMethodProcessor#constructAttribute() (C) CASAREAL, Inc. All rights reserved. 109 protected Object constructAttribute(Constructor<?> constructor, String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception { if (constructor.getParameterCount() == 0) { // A single default constructor -> clearly a standard JavaBeans arrangement. return BeanUtils.instantiateClass(constructor); } デフォルトコンストラクタの場合 はすぐにインスタンス化
  110. 110. 引数ありコンストラクタの場合は (C) CASAREAL, Inc. All rights reserved. 110 https://github.com/spring-projects/spring-framework/blob/master/spring- web/src/main/java/org/springframework/web/method/annotation/ModelAttrib uteMethodProcessor.java#L260 こちらをご参照ください
  111. 111. ModelAttributeMethodProcessor#resolveArgument() (C) CASAREAL, Inc. All rights reserved. 111 if (bindingResult == null) { // Bean property binding and validation; // skipped in case of binding failure on construction. WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } } Form、BindingResult のインスタンスを保持する WebDataBinderを生成 フォームのsetterでパラメータの代入 Bean Validation実行 ・入力検証でエラー ・引数にBindingResultがない は例外スロー
  112. 112. ModelAttributeMethodProcessor#resolveArgument() (C) CASAREAL, Inc. All rights reserved. 112 // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; Form、BindingResultのインスタンスをModelにセット この時、Form、BindingResultの順番で追加される
  113. 113. ModelAttributeMethodProcessor#isBindExceptionRequired () (C) CASAREAL, Inc. All rights reserved. 113 protected boolean isBindExceptionRequired(MethodParameter parameter) { int i = parameter.getParameterIndex(); Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes(); boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1])); return !hasBindingResult; } コントローラーメソッドで、 Form、BindingResult の順で引数を設定しないとエラーになるのは ここでチェックしている
  114. 114. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 114  Form  BindingResult  Servlet API  RequestBody
  115. 115. ErrorsMethodArgumentResolver (C) CASAREAL, Inc. All rights reserved. 115  ServletModelAttributeMethodProcessorでModelに 追加されたBindingResultを返すだけ  Modelの最後の要素を取得する  ServletModelAttributeMethodProcessorで、 Form、BindingResultの順でModelに追加  引数はForm、BindingResultの順で、 設定しないといけない は、このためと考えられる
  116. 116. ErrorsMethodArgumentResolver#resolveArgument() (C) CASAREAL, Inc. All rights reserved. 116 ModelMap model = mavContainer.getModel(); String lastKey = CollectionUtils.lastElement(model.keySet()); if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) { return model.get(lastKey); } Modelの最後の要素のkeyを取得して valueを取得
  117. 117. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 117  Form  BindingResult  Servlet API  RequestBody
  118. 118. ServletRequestMethodArgumentResolver ServletResponseMethodArgumentResolver (C) CASAREAL, Inc. All rights reserved. 118  ServletRequestMethodArgumentResolver  ServletRequest、HttpSession、Principal等が該当する  Spring Security の Authentication も該当  ServletResponseMethodArgumentResolver  ServletResponseなどが該当する 比較的単純な処理なのでソースコードの掲載は省略
  119. 119. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 119  Form  BindingResult  Servlet API  RequestBody
  120. 120. RequestResponseBodyMethodProcessor (C) CASAREAL, Inc. All rights reserved. 120  JSONをJavaオブジェクトに変換  インスタンスの生成や値の代入はJacksonに委譲  supportsParameterメソッドの判定条件は@RequestBody がついているか否か  @ValidatedがついていたらBean Validationを実行する
  121. 121. RequestResponseBodyMethodProcessor#resolveArgument() (C) CASAREAL, Inc. All rights reserved. 121 public Object resolveArgument( MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); Jacksonに JSON ⇒ Javaオブジェクト の変換処理を委譲
  122. 122. RequestResponseBodyMethodProcessor#resolveArgument() - 続き (C) CASAREAL, Inc. All rights reserved. 122 if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(..); } } if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } Bean Validation を実行 引数にBindingResultが ない場合は例外スロー BindingResultだけ Modelに追加
  123. 123. (C) CASAREAL, Inc. All rights reserved. 123
  124. 124. 目次 (C) CASAREAL, Inc. All rights reserved. 124 1.Spring MVC のアーキテクチャー 2.URLとコントローラーメソッド 3.コントローラーメソッドの引数 4.コントローラーメソッドの戻り値 5.全体を俯瞰
  125. 125. Spring MVC のアーキテクチャー (C) CASAREAL, Inc. All rights reserved. 125 Dispatcher Servlet Handler Mapping Handler Adapter View Resolver Model Controller view name View 1 2 3 4 56
  126. 126. Handler Adapter – 実行後 (C) CASAREAL, Inc. All rights reserved. 126 Dispatcher Servlet Handler Adapter Controller view name 2 3 4 コントローラークラスのメソッドの 戻り値はどのように処理される?
  127. 127. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 127  HandlerMethodReturnValueHandler  HandlerAdapter(ここでも重要)  ServletInvocableHandlerMethod (ここでも重要)  ModelAndViewContainer
  128. 128. 関係図 (C) CASAREAL, Inc. All rights reserved. 128 HandlerAdapter HandlerMethodReturnValueHandler ServletInvocableHandlerMethod (リクエストごとに作られる)
  129. 129. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 129  HandlerMethodReturnValueHandler  HandlerAdapter(ここでも重要)  ServletInvocableHandlerMethod (ここでも重要)  ModelAndViewContainer
  130. 130. 関係図 (C) CASAREAL, Inc. All rights reserved. 130 HandlerMethodReturnValueHandler
  131. 131. HandlerMethodReturnValueHandler (C) CASAREAL, Inc. All rights reserved. 131  コントローラーメソッドの戻り値を処理する  戻り値の文字列をSpringにView名として認識させる  RESTの場合は JSON への変換処理を呼び出す  インタフェースであり、これを多数のクラスが実装  String  Map  ResponseBody など、各クラス分実装クラスが存在する  Viewに処理を委譲するか、そのままレスポンスするか のフラグの更新も担う
  132. 132. HandlerMethodReturnValueHandler (C) CASAREAL, Inc. All rights reserved. 132 public interface HandlerMethodReturnValueHandler { boolean supportsReturnType(MethodParameter returnType); void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; }
  133. 133. HandlerMethodReturnValueHandler (C) CASAREAL, Inc. All rights reserved. 133  supportsReturnType  メソッドの戻り値の型や付加したアノテーションな どからサポートするかを判定するメソッド  handleReturnValue  メソッドの戻り値を処理するメソッド 以後、ReturnValueHandler と省略した場合は、 こちらを示しています。
  134. 134. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 134  HandlerMethodReturnValueHandler  HandlerAdapter(ここでも重要)  ServletInvocableHandlerMethod (ここでも重要)  ModelAndViewContainer
  135. 135. 関係図 (C) CASAREAL, Inc. All rights reserved. 135 HandlerAdapter HandlerMethodReturnValueHandler
  136. 136. RequestMappingHandlerAdapter (C) CASAREAL, Inc. All rights reserved. 136  HandlerMethod用のHandlerAdapterの実装クラス  HandlerMethodArgumentResolverのインスタンスを保持  ServletInvocableHandlerMethodに HandlerMethodArgumentResolverのインスタンスを渡し て委譲する  HandlerMethodReturnValueHandlerのインスタンスを保持
  137. 137. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 137  HandlerMethodReturnValueHandler  HandlerAdapter(ここでも重要)  ServletInvocableHandlerMethod (ここでも重要)  ModelAndViewContainer
  138. 138. 関係図 (C) CASAREAL, Inc. All rights reserved. 138 HandlerAdapter HandlerMethodReturnValueHandler ServletInvocableHandlerMethod (リクエストごとに作られる)
  139. 139. ServletInvocableHandlerMethod (C) CASAREAL, Inc. All rights reserved. 139  HandlerMethodを継承している  RequestMappingHandlerAdapterから借り受けた HandlerMethodArgumentResolverを使って引数のインス タンスを調達する  コントローラーメソッドを実行する  HandlerMethodReturnValueHandlerを使って、戻り値の 処理を行う
  140. 140. 関係図 (C) CASAREAL, Inc. All rights reserved. 140 RequestMapping HandlerAdapter ServletInvocable HandlerMethod this.returnValueHandlers setHandlerMethodReturnValueHandlers これも使って! 任せとけ! リクエストごとに new
  141. 141. 押さえておきたい登場人物 (C) CASAREAL, Inc. All rights reserved. 141  HandlerMethodReturnValueHandler  HandlerAdapter(ここでも重要)  ServletInvocableHandlerMethod (ここでも重要)  ModelAndViewContainer
  142. 142. ModelAndViewContainer (C) CASAREAL, Inc. All rights reserved. 142  ModelとView名を保持するクラス  DispatcherServletがViewを見つけ出す橋渡し役でもある  Viewに処理を委譲する or そのままレスポンスする を振り分けるフラグをもつ  requestHandledフィールドが該当
  143. 143. ServletInvocableHandlerMethod#invokeAndHandle() (C) CASAREAL, Inc. All rights reserved. 143 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 中略 mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); HandlerMethod実行後 の戻り値を受け取る
  144. 144. ServletInvocableHandlerMethod#invokeAndHandle() (C) CASAREAL, Inc. All rights reserved. 144 try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } } 戻り値の調整を 行う
  145. 145. 【例】コントローラークラスの定義 (C) CASAREAL, Inc. All rights reserved. 145 @Controller public class TestController { @GetMapping("/") public String index(Model model) { model.addAttribute("name", "masato"); model.addAttribute("age", 1); return "test"; } @GetMapping("/test") public Map test2() { return Map.of("name", "masato", "age", 1); } } 戻り値の型が違うが どちらも最終的に “test.html” を出力
  146. 146. 動作概要 (C) CASAREAL, Inc. All rights reserved. 146 Map担当 String担当 ResponseBody担当 戻り値の型 ReturnValueHandlerたち ?
  147. 147. (C) CASAREAL, Inc. All rights reserved. 147 Map担当 String担当 ResponseBody担当 戻り値の型 String 戻り値の型がString 俺の出番 だな! サポート不可。。
  148. 148. (C) CASAREAL, Inc. All rights reserved. 148 Map担当 String担当 ResponseBody担当 戻り値の型 Map 戻り値の型がMap 任せとけ! サポート不可。。
  149. 149. (C) CASAREAL, Inc. All rights reserved. 149 Map担当 String担当 ResponseBody担当 戻り値の型 ResponseBody 戻り値の型がResponseBody 任せとけ! できない!
  150. 150. 戻り値に指定可能な型は? (C) CASAREAL, Inc. All rights reserved. 150  指定可能な型が多すぎる https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring- framework-reference/web.html#mvc-ann-return-types  丸暗記ではなく取捨選択が重要  ソースコード上からも指定可能な型を確認できる
  151. 151. RequestMappingHandlerAdapter#getDefaultReturnValueHandlers() (C) CASAREAL, Inc. All rights reserved. 151 private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(); // Single-purpose return value types handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(), this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager)); handlers.add(new StreamingResponseBodyReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
  152. 152. RequestMappingHandlerAdapter#getDefaultArgumentResolvers() (C) CASAREAL, Inc. All rights reserved. 152 handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); // Annotation-based return value types handlers.add(new ModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // Multi-purpose return value types handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor());
  153. 153. RequestMappingHandlerAdapter#getDefaultArgumentResolvers() (C) CASAREAL, Inc. All rights reserved. 153 // Custom return value types if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler( getModelAndViewResolvers())); } else { handlers.add(new ModelAttributeMethodProcessor(true)); } return handlers; }
  154. 154. いくつか絞ってご紹介します (C) CASAREAL, Inc. All rights reserved. 154
  155. 155. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 155  Viewに委譲するパターン  String  Map  Viewに委譲しないパターン(REST)  ResponseBody  ResponseEntity
  156. 156. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 156  Viewに委譲するパターン  String  Map  Viewに委譲しないパターン(REST)  ResponseBody  ResponseEntity
  157. 157. ViewNameMethodReturnValueHandler (C) CASAREAL, Inc. All rights reserved. 157  戻り値を文字列にした場合が該当する  戻り値をModelAndViewContainerが持つView名として セットする
  158. 158. (C) CASAREAL, Inc. All rights reserved. 158 public boolean supportsReturnType(MethodParameter returnType) { Class<?> paramType = returnType.getParameterType(); return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType)); } ViewNameMethodReturnValueHandler#supportsReturnType() 意外にもサポート対象の型に CharSequenceだけでなく voidも含まれる
  159. 159. ViewNameMethodReturnValueHandler#handleReturnValue() (C) CASAREAL, Inc. All rights reserved. 159 public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue instanceof CharSequence) { String viewName = returnValue.toString(); mavContainer.setViewName(viewName); if (isRedirectViewName(viewName)) { mavContainer.setRedirectModelScenario(true); } } 戻り値をViewName としてセット
  160. 160. なぜvoidもサポート対象なのか? (C) CASAREAL, Inc. All rights reserved. 160  明示しない場合、リクエストURLが論理View名となる  http://localhost:8080/test とアクセスした場合、 論理View名に “test” を指定した動作になる
  161. 161. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 161  Viewに委譲するパターン  String  Map  Viewに委譲しないパターン(REST)  ResponseBody  ResponseEntity
  162. 162. MapMethodProcessor (C) CASAREAL, Inc. All rights reserved. 162  戻り値をMap型にした場合が該当する  戻り値をModelAndViewContainerの Modelにすべて追加する
  163. 163. (C) CASAREAL, Inc. All rights reserved. 163 public boolean supportsReturnType(MethodParameter returnType) { return Map.class.isAssignableFrom(returnType.getParameterType()); } MapMethodProcessor#supportsReturnType() 戻り値がMap型がサポート対象
  164. 164. (C) CASAREAL, Inc. All rights reserved. 164 public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue instanceof Map){ mavContainer.addAllAttributes((Map) returnValue); } } MapMethodProcessor#handleReturnValue() 戻り値のMapの値をすべて Modelに追加している
  165. 165. 【例】コントローラークラスの定義 (C) CASAREAL, Inc. All rights reserved. 165 @Controller public class TestController { @GetMapping("/") public String index(Model model) { model.addAttribute("name", "masato"); model.addAttribute("age", 1); return "test"; } @GetMapping("/test") public Map test2() { return Map.of("name", "masato", "age", 1); } } 二つのメソッドは最終的に 同じ挙動になる
  166. 166. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 166  Viewに委譲するパターン  String  Map  Viewに委譲しないパターン(REST)  ResponseBody  ResponseEntity
  167. 167. RequestResponseBodyMethodProcessor (C) CASAREAL, Inc. All rights reserved. 167  @ResponseBodyが付加されている場合が該当する  クラス、メソッドどちらにも付加可能  @RestControllerを付加している場合も該当  @Controllerと@ResponseBodyを合成したもの  戻り値のJavaオブジェクトをJSONに変換して レスポンス
  168. 168. (C) CASAREAL, Inc. All rights reserved. 168 public boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation( returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); } RequestResponseBodyMethodProcessor#supportsReturnType() メソッド、もしくはクラスに @ResponseBodyが付加されてい る場合サポート対象
  169. 169. (C) CASAREAL, Inc. All rights reserved. 169 public void handleReturnValue( @Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved. writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); } RequestResponseBodyMethodProcessor#handleReturnValue() そのままレスポンスする設定 JSONに変換してレスポンスする処理
  170. 170. いくつか絞ってご紹介 (C) CASAREAL, Inc. All rights reserved. 170  Viewに委譲するパターン  String  Map  Viewに委譲しないパターン(REST)  ResponseBody  ResponseEntity
  171. 171. HttpEntityMethodProcessor (C) CASAREAL, Inc. All rights reserved. 171  戻り値をHttpEntity型もしくはResponseEntity型 にした場合が該当  ResponseEntity型の場合レスポンスステータスをセット  戻り値のbodyにセットしたJavaオブジェクトをJSONに 変換してレスポンス
  172. 172. (C) CASAREAL, Inc. All rights reserved. 172 public boolean supportsReturnType(MethodParameter returnType) { return (HttpEntity.class.isAssignableFrom(returnType.getParameterType()) && !RequestEntity.class.isAssignableFrom(returnType.getParameterType())); } HttpEntityMethodProcessor#supportsReturnType() HttpEntity型およびResponseEntity型 がサポート対象
  173. 173. (C) CASAREAL, Inc. All rights reserved. 173 public void handleReturnValue( @Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); // 中略 if (responseEntity instanceof ResponseEntity) { int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue(); outputMessage.getServletResponse().setStatus(returnStatus); // JSONに変換する処理の呼び出し } HttpEntityMethodProcessor#handleReturnValue() そのままレスポンスする設定 レスポンスステータスの設定
  174. 174. (C) CASAREAL, Inc. All rights reserved. 174
  175. 175. 目次 (C) CASAREAL, Inc. All rights reserved. 175 1.Spring MVC のアーキテクチャー 2.URLとコントローラーメソッド 3.コントローラーメソッドの引数 4.コントローラーメソッドの戻り値 5.全体を俯瞰
  176. 176. Spring MVC のアーキテクチャー 再掲 (C) CASAREAL, Inc. All rights reserved. 176 Dispatcher Servlet Handler Mapping Handler Adapter View Resolver Model Controller view name View 1 2 3 4 56
  177. 177. DispatcherServlet#doDispatch() が Spring MVC の骨格です (C) CASAREAL, Inc. All rights reserved. 177
  178. 178. Spring MVC のアーキテクチャー (C) CASAREAL, Inc. All rights reserved. 178 Dispatcher Servlet Handler Mapping Controller 1
  179. 179. (C) CASAREAL, Inc. All rights reserved. 179 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine handler for the current request. mappedHandler = getHandler(processedRequest); DispatcherServlet#doDispatch() HandlerMapping呼び出し
  180. 180. (C) CASAREAL, Inc. All rights reserved. 180 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; } DispatcherServlet#getHandler() HandlerMapping呼び出し
  181. 181. Spring MVC のアーキテクチャ (C) CASAREAL, Inc. All rights reserved. 181 Dispatcher Servlet Handler Adapter Controller view name 2 3 4
  182. 182. (C) CASAREAL, Inc. All rights reserved. 182 // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); DispatcherServlet#doDispatch() HandlerAdapterの取り出し コントローラーメソッドの実行
  183. 183. Spring MVC のアーキテクチャー (C) CASAREAL, Inc. All rights reserved. 183 Dispatcher Servlet View Resolver Model View 56
  184. 184. (C) CASAREAL, Inc. All rights reserved. 184 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); DispatcherServlet#doDispatch() Viewの解決とレンダリング処理
  185. 185. RESTの場合 (C) CASAREAL, Inc. All rights reserved. 185
  186. 186. (C) CASAREAL, Inc. All rights reserved. 186 private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); // 中略 return mav; } requestHandledがtrueの場合は、 DispatcherServletに ModelAndViewを返さない RequestMappingHandlerAdapter#getModelAndView()
  187. 187. まとめ (C) CASAREAL, Inc. All rights reserved. 187  基本となるインタフェースと実装クラスを 抑えれば読解は可能  引数のインスタンスの生成順序を正確に 把握できるのでトラブルに強くなれる  initModelやinterceptorなど紹介していない 処理の呼び出しも是非読んでいただきたい
  188. 188. 今後 (C) CASAREAL, Inc. All rights reserved. 188  RequestMappingInfoの生成過程など、 より詳しく知るためにはDIコンテナの 生成過程を知る必要がある  機会があればアプリの起動時の処理を紹介 したい
  189. 189. ご清聴ありがとうございました! (C) CASAREAL, Inc. All rights reserved. 189

×