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. 気になったことはありませんか?
(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. 気になったことはありませんか?
(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以外って
順番決まりなさそうだけど。。。
6. このセッションは
(C) CASAREAL, Inc. All rights reserved. 6
Spring MVCのアーキテクチャーを
ソースコードリーディングをもとに解き明か
していく45分間です
Spring MVCのアーキテクチャーをより詳しく
知りたい方が対象です
内部構造を詳細に把握することでトラブルに
強くなれます
7. 自己紹介:菊池真登
(C) CASAREAL, Inc. All rights reserved. 7
研修トレーナー@カサレアル
登壇実績:Spring / Java SE
開発歴[Spring]:2012年から
開発歴[Angular]:2015年頃に
1.5年間AngularJSに触れて冬眠
9. 目次
(C) CASAREAL, Inc. All rights reserved. 9
1.Spring MVC のアーキテクチャー
2.URLとコントローラーメソッド
3.コントローラーメソッドの引数
4.コントローラーメソッドの戻り値
5.全体を俯瞰
10. 目次
(C) CASAREAL, Inc. All rights reserved. 10
1.Spring MVC のアーキテクチャー
2.URLとコントローラーメソッド
3.コントローラーメソッドの引数
4.コントローラーメソッドの戻り値
5.全体を俯瞰
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. 関係を詳しく見ると
(C) CASAREAL, Inc. All rights reserved. 12
Dispatcher
Servlet DIコンテナ
Handler
Mapping
Handler
Adapter
DispatcherServletはDIコンテナをもつ
そのDIコンテナがHandlerMappingや
HandlerAdapterなどをもつ
13. 処理実行時は
(C) CASAREAL, Inc. All rights reserved. 13
Dispatcher
Servlet
Handler
Adapter
Handler
Mapping
DispatcherServletの初期化処理で
DIコンテナからインスタンスを取り出し
フィールドに直接保持する
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. HandlerMapping
(C) CASAREAL, Inc. All rights reserved. 15
Dispatcher
Servlet
Handler
Mapping
Controller
1
リクエストURLとコントローラー
クラスのメソッドとの関係とは?
16. HandlerAdapter – 実行前
(C) CASAREAL, Inc. All rights reserved. 16
Dispatcher
Servlet
Handler
Adapter
Controller
2 3
コントローラークラスのメソッド
の引数はどのようにして解決?
17. HandlerAdapter – 実行後
(C) CASAREAL, Inc. All rights reserved. 17
Dispatcher
Servlet
Handler
Adapter
Controller
view name
2 3
4
コントローラークラスのメソッドの
戻り値はどのように処理される?
18. 目次
(C) CASAREAL, Inc. All rights reserved. 18
1.Spring MVC のアーキテクチャー
2.URLとコントローラーメソッド
3.コントローラーメソッドの引数
4.コントローラーメソッドの戻り値
5.全体を俯瞰
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. HandlerMapping
(C) CASAREAL, Inc. All rights reserved. 20
Dispatcher
Servlet
Handler
Mapping
Controller
1
リクエストURLとコントローラー
クラスのメソッドとの関係とは?
21. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 21
HandlerMapping
RequestMappingInfo
MappingRegistry
HandlerMethod
AbstractHandlerMethodMapping
22. ざっくりとした関係図
(C) CASAREAL, Inc. All rights reserved. 22
HandlerMapping
MappingRegistry
RequestMappingInfo
RequestMappingInfo
RequestMappingInfo
HandlerMethod
HandlerMethod
HandlerMethod
23. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 23
HandlerMapping
RequestMappingInfo
MappingRegistry
HandlerMethod
AbstractHandlerMethodMapping
29. RequestMappingInfo
(C) CASAREAL, Inc. All rights reserved. 29
@RequestMappingの設定値を保持する
コントローラーメソッドの定義情報を表すクラス
コントローラーメソッドの数だけ作られる
1メソッド : 1Info
アプリ実行中はMappingRegistryに保持される
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. 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. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 32
HandlerMapping
RequestMappingInfo
MappingRegistry
HandlerMethod
AbstractHandlerMethodMapping
33. 関係図
(C) CASAREAL, Inc. All rights reserved. 33
HandlerMapping
MappingRegistry
RequestMappingInfo
RequestMappingInfo
RequestMappingInfo
34. MappingRegistry
(C) CASAREAL, Inc. All rights reserved. 34
リクエストURLとRequestMappingInfoを紐づける
AbstractHandlerMethodMapping内にあるクラス
アクセス修飾子なしのクラス
実行中はAbstractHandlerMethodMappingのフィールドに
インスタンスが保持される
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. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 36
HandlerMapping
RequestMappingInfo
MappingRegistry
HandlerMethod
AbstractHandlerMethodMapping
37. 関係図
(C) CASAREAL, Inc. All rights reserved. 37
HandlerMapping
MappingRegistry
RequestMappingInfo
RequestMappingInfo
RequestMappingInfo
HandlerMethod
HandlerMethod
HandlerMethod
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. 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. 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. まとめると
(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. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 44
HandlerMapping
RequestMappingInfo
MappingRegistry
HandlerMethod
AbstractHandlerMethodMapping
45. 継承関係
(C) CASAREAL, Inc. All rights reserved. 45
RequestMappingHandlerMapping
AbstractHandlerMethodMapping
HandlerMapping
46. AbstractHandlerMethodMapping
(C) CASAREAL, Inc. All rights reserved. 46
HandlerMappingの処理の根幹
RequestMappingHandlerMappingが継承
lookupHandlerMethodメソッドが肝!
ざっくりいうと下記の二段構成
1. リクエストURLと完全一致するものを探す
2. なければ全検索でパターンマッチを行う
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. 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. 【例】リクエストURLが / の場合
(C) CASAREAL, Inc. All rights reserved. 50
“/”
Patterns:”/”
Methods:”GET”
このRequestMappingInfo
が選択される
key : URL value : RequestMappingInfo
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. リクエストメソッドなど細かい判定を行う
(C) CASAREAL, Inc. All rights reserved. 52
リクエストメソッド等細かな条件で絞り込む
RequestMappingInfo#getMatchingCondition()で行う
@RequestMappingのすべての属性値が判定対象
method
params
headers
consumes
produces
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. 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. 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. 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. 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());
}
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. 【例】コントローラークラスの定義
(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つを定義する
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. 【例】コントローラークラスの定義
(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メソッドが選択
される
70. 目次
(C) CASAREAL, Inc. All rights reserved. 70
1.Spring MVC のアーキテクチャー
2.URLとコントローラーメソッド
3.コントローラーメソッドの引数
4.コントローラーメソッドの戻り値
5.全体を俯瞰
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. Handler Adapter – 実行前
(C) CASAREAL, Inc. All rights reserved. 72
Dispatcher
Servlet
Handler
Adapter
Controller
2 3
コントローラークラスのメソッド
の引数はどのようにして解決?
73. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 73
HandlerMethodArgumentResolver
HandlerAdapter
ServletInvocableHandlerMethod
74. 関係図
(C) CASAREAL, Inc. All rights reserved. 74
HandlerAdapter
HandlerMethodArgumentResolver
ServletInvocableHandlerMethod
(リクエストごとに作られる)
75. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 75
HandlerMethodArgumentResolver
HandlerAdapter
ServletInvocableHandlerMethod
77. HandlerMethodArgumentResolver
(C) CASAREAL, Inc. All rights reserved. 77
コントローラーメソッドの引数の調達役
インタフェースであり、これを多数のクラスが実装
Form
BindingResult
Servlet API
など、各クラス分実装クラスが存在する
Spring SecurityやSpring Data Commons 等にも実装クラス
が存在する
メソッドで UserDetails 等を取得するのもこの機能
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. HandlerMethodArgumentResolver
(C) CASAREAL, Inc. All rights reserved. 79
supportsParameter()
メソッド引数の型や付加したアノテーションなどか
らサポート対象かを判定するメソッド
resolveArgument()
メソッド引数のインスタンスを調達するメソッド
以後、ArgumentResolver と省略した場合は、
こちらを示しています。
80. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 80
HandlerMethodArgumentResolver
HandlerAdapter
ServletInvocableHandlerMethod
81. 関係図
(C) CASAREAL, Inc. All rights reserved. 81
HandlerAdapter
HandlerMethodArgumentResolver
82. HandlerAdapter
(C) CASAREAL, Inc. All rights reserved. 82
HandlerMappingで選択されたHandlerMethodを実行する
インタフェース
handle()メソッド内で HandlerMethod を実行する
83. RequestMappingHandlerAdapter
(C) CASAREAL, Inc. All rights reserved. 83
@RequestMappingで定義されたHandlerMethodを
サポートするHandlerAdapterの実装クラス
HandlerMethodArgumentResolverのインスタンスを保持
ServletInvocableHandlerMethodに
HandlerMethodArgumentResolverのインスタンスを
渡して委譲する
89. 関係図
(C) CASAREAL, Inc. All rights reserved. 89
RequestMapping
HandlerAdapter
ServletInvocable
HandlerMethod
リクエストごとに
new
this.argumentResolvers
setHandlerMethodArgumentResolvers
これ使って! 任せとけ!
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. 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. 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. 【例】コントローラークラスの定義
(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. 動作概要
(C) CASAREAL, Inc. All rights reserved. 94
[0] [1] [2]
TestForm BindingResult Model
BindingResult担当
Form担当 Model担当
引数リスト
ArgumentResolverたち
95. 引数リスト[0]
(C) CASAREAL, Inc. All rights reserved. 95
[0]
TestForm
BindingResult担当
Form担当 Model担当
引数リスト
俺の出番
だな!
サポート不可。。
96. 引数リスト[1]
(C) CASAREAL, Inc. All rights reserved. 96
[1]
BindingResult
BindingResult担当
Form担当 Model担当
引数リスト
任せて!
サポート不可。。
97. 引数リスト[2]
(C) CASAREAL, Inc. All rights reserved. 97
[2]
Model
BindingResult担当
Form担当 Model担当
引数リスト
いっきまーす!
出来ません!
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. 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. 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());
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. 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. 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. 引数ありコンストラクタの場合は
(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. 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. 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. 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
の順で引数を設定しないとエラーになるのは
ここでチェックしている
124. 目次
(C) CASAREAL, Inc. All rights reserved. 124
1.Spring MVC のアーキテクチャー
2.URLとコントローラーメソッド
3.コントローラーメソッドの引数
4.コントローラーメソッドの戻り値
5.全体を俯瞰
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. Handler Adapter – 実行後
(C) CASAREAL, Inc. All rights reserved. 126
Dispatcher
Servlet
Handler
Adapter
Controller
view name
2 3
4
コントローラークラスのメソッドの
戻り値はどのように処理される?
127. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 127
HandlerMethodReturnValueHandler
HandlerAdapter(ここでも重要)
ServletInvocableHandlerMethod (ここでも重要)
ModelAndViewContainer
128. 関係図
(C) CASAREAL, Inc. All rights reserved. 128
HandlerAdapter
HandlerMethodReturnValueHandler
ServletInvocableHandlerMethod
(リクエストごとに作られる)
129. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 129
HandlerMethodReturnValueHandler
HandlerAdapter(ここでも重要)
ServletInvocableHandlerMethod (ここでも重要)
ModelAndViewContainer
131. HandlerMethodReturnValueHandler
(C) CASAREAL, Inc. All rights reserved. 131
コントローラーメソッドの戻り値を処理する
戻り値の文字列をSpringにView名として認識させる
RESTの場合は JSON への変換処理を呼び出す
インタフェースであり、これを多数のクラスが実装
String
Map
ResponseBody
など、各クラス分実装クラスが存在する
Viewに処理を委譲するか、そのままレスポンスするか
のフラグの更新も担う
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. HandlerMethodReturnValueHandler
(C) CASAREAL, Inc. All rights reserved. 133
supportsReturnType
メソッドの戻り値の型や付加したアノテーションな
どからサポートするかを判定するメソッド
handleReturnValue
メソッドの戻り値を処理するメソッド
以後、ReturnValueHandler と省略した場合は、
こちらを示しています。
134. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 134
HandlerMethodReturnValueHandler
HandlerAdapter(ここでも重要)
ServletInvocableHandlerMethod (ここでも重要)
ModelAndViewContainer
135. 関係図
(C) CASAREAL, Inc. All rights reserved. 135
HandlerAdapter
HandlerMethodReturnValueHandler
136. RequestMappingHandlerAdapter
(C) CASAREAL, Inc. All rights reserved. 136
HandlerMethod用のHandlerAdapterの実装クラス
HandlerMethodArgumentResolverのインスタンスを保持
ServletInvocableHandlerMethodに
HandlerMethodArgumentResolverのインスタンスを渡し
て委譲する
HandlerMethodReturnValueHandlerのインスタンスを保持
137. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 137
HandlerMethodReturnValueHandler
HandlerAdapter(ここでも重要)
ServletInvocableHandlerMethod (ここでも重要)
ModelAndViewContainer
138. 関係図
(C) CASAREAL, Inc. All rights reserved. 138
HandlerAdapter
HandlerMethodReturnValueHandler
ServletInvocableHandlerMethod
(リクエストごとに作られる)
139. ServletInvocableHandlerMethod
(C) CASAREAL, Inc. All rights reserved. 139
HandlerMethodを継承している
RequestMappingHandlerAdapterから借り受けた
HandlerMethodArgumentResolverを使って引数のインス
タンスを調達する
コントローラーメソッドを実行する
HandlerMethodReturnValueHandlerを使って、戻り値の
処理を行う
140. 関係図
(C) CASAREAL, Inc. All rights reserved. 140
RequestMapping
HandlerAdapter
ServletInvocable
HandlerMethod
this.returnValueHandlers
setHandlerMethodReturnValueHandlers
これも使って! 任せとけ!
リクエストごとに
new
141. 押さえておきたい登場人物
(C) CASAREAL, Inc. All rights reserved. 141
HandlerMethodReturnValueHandler
HandlerAdapter(ここでも重要)
ServletInvocableHandlerMethod (ここでも重要)
ModelAndViewContainer
142. ModelAndViewContainer
(C) CASAREAL, Inc. All rights reserved. 142
ModelとView名を保持するクラス
DispatcherServletがViewを見つけ出す橋渡し役でもある
Viewに処理を委譲する or そのままレスポンスする
を振り分けるフラグをもつ
requestHandledフィールドが該当
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. 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. 【例】コントローラークラスの定義
(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. 動作概要
(C) CASAREAL, Inc. All rights reserved. 146
Map担当
String担当 ResponseBody担当
戻り値の型
ReturnValueHandlerたち
?
147. (C) CASAREAL, Inc. All rights reserved. 147
Map担当
String担当 ResponseBody担当
戻り値の型
String
戻り値の型がString
俺の出番
だな!
サポート不可。。
148. (C) CASAREAL, Inc. All rights reserved. 148
Map担当
String担当 ResponseBody担当
戻り値の型
Map
戻り値の型がMap
任せとけ!
サポート不可。。
149. (C) CASAREAL, Inc. All rights reserved. 149
Map担当
String担当 ResponseBody担当
戻り値の型
ResponseBody
戻り値の型がResponseBody
任せとけ!
できない!
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. 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. 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. 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;
}
163. (C) CASAREAL, Inc. All rights reserved. 163
public boolean supportsReturnType(MethodParameter returnType) {
return Map.class.isAssignableFrom(returnType.getParameterType());
}
MapMethodProcessor#supportsReturnType()
戻り値がMap型がサポート対象
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. 【例】コントローラークラスの定義
(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. いくつか絞ってご紹介
(C) CASAREAL, Inc. All rights reserved. 166
Viewに委譲するパターン
String
Map
Viewに委譲しないパターン(REST)
ResponseBody
ResponseEntity
167. RequestResponseBodyMethodProcessor
(C) CASAREAL, Inc. All rights reserved. 167
@ResponseBodyが付加されている場合が該当する
クラス、メソッドどちらにも付加可能
@RestControllerを付加している場合も該当
@Controllerと@ResponseBodyを合成したもの
戻り値のJavaオブジェクトをJSONに変換して
レスポンス
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. (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. いくつか絞ってご紹介
(C) CASAREAL, Inc. All rights reserved. 170
Viewに委譲するパターン
String
Map
Viewに委譲しないパターン(REST)
ResponseBody
ResponseEntity
171. HttpEntityMethodProcessor
(C) CASAREAL, Inc. All rights reserved. 171
戻り値をHttpEntity型もしくはResponseEntity型
にした場合が該当
ResponseEntity型の場合レスポンスステータスをセット
戻り値のbodyにセットしたJavaオブジェクトをJSONに
変換してレスポンス
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. (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()
そのままレスポンスする設定
レスポンスステータスの設定
175. 目次
(C) CASAREAL, Inc. All rights reserved. 175
1.Spring MVC のアーキテクチャー
2.URLとコントローラーメソッド
3.コントローラーメソッドの引数
4.コントローラーメソッドの戻り値
5.全体を俯瞰
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
178. Spring MVC のアーキテクチャー
(C) CASAREAL, Inc. All rights reserved. 178
Dispatcher
Servlet
Handler
Mapping
Controller
1
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. (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. Spring MVC のアーキテクチャ
(C) CASAREAL, Inc. All rights reserved. 181
Dispatcher
Servlet
Handler
Adapter
Controller
view name
2 3
4
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. Spring MVC のアーキテクチャー
(C) CASAREAL, Inc. All rights reserved. 183
Dispatcher
Servlet
View
Resolver Model
View
56
184. (C) CASAREAL, Inc. All rights reserved. 184
processDispatchResult(processedRequest,
response, mappedHandler, mv, dispatchException);
DispatcherServlet#doDispatch()
Viewの解決とレンダリング処理