HttpServletResponseに直接書き込みたい、という要望があったので、その方法を調べました。
やり方
Controllerの引数にHttpServletResponseを定義し、そこに書き込むだけです。
@Controller
public class HelloController {
@RequestMapping(value="/", method=RequestMethod.GET)
public void hello(HttpServletResponse response) throws Exception {
response.getWriter().write("hogehoge");
}
}
レスポンスを書き込むパターンとView名を返却するパターンを分岐したい
戻り値をStringにします。
レスポンスに直接書き込んだ場合はnullを返却します。
@Controller
public class HelloController {
@RequestMapping(value="/{param}", method=RequestMethod.GET)
public String hello(HttpServletResponse response, @PathVariable String param) throws Exception {
if(Objects.equals("param", param)){
response.getWriter().write("hogehoge");
return null;
}
return "hello";
}
}
nullを返却したらエラーが出たと文句を言われた
nullを返却したらエラーログが出たんだけど、と報告がありました。
ログを確認すると、JSPを探しに行ったけど見つからない、みたいな内容でした。
ということでソースコードを確認してみると以下のような感じになっていました。
@Controller
public class HelloController {
@Inject
HttpServletResponse response
@RequestMapping(value="/{param}", method=RequestMethod.GET)
public String hello(@PathVariable String param) throws Exception {
if(Objects.equals("param", param)){
response.getWriter().write("hogehoge");
return null;
}
return "hello";
}
}
確かにこのように実装するとJSPを探してエラーになるみたいでした。つまり、Viewのrender処理が実行されている。
ちなみに、HttpServletResponseってInjectできるんだー、と思いましたが話の本筋ではないのでスルー。
フレームワークの中身を確認してみる
render処理が実行されないための条件は?
DispatcherServletを確認すると、HandlerAdapterを実行してModelAndViewを取得しています。
このとき、戻り値がnullであればViewによるrender処理は実行されません。Responseを直接書き込んでいる場合は、この挙動になることを想定しています。
(doDispatchやprocessDispatchResultなど)
では、HandlerAdapterの戻り値がnullになる条件は?
RequestMappingHandlerAdapterの処理を見てみると、ModelAndViewContainerのisRequestHandledがtrueになった場合にnullを返却しています。
(getModelAndViewなど)
では、これがtrueになるための条件は?
いろんな条件があるのですが、今回のケースに関して言うと、Controllerメソッドの引数にHttpServletResponseがあること、Controllerからの戻り値がnullであることが条件となります。
エラーが発生していたソースでは、フィールドに定義されたHttpServletResponseを利用しているため、HandlerAdapterの戻り値がnullにならなかった、ということです。
参考までに、View名がnullのときの動作
DispatcherServletのapplyDefaultViewNameからの処理を追えば、リクエストされたURLをView名として適用していることがわかります。
解決方法
引数にHttpServletResponseを追加しましょう。
そんなことできないよ~、という方は以下のようにControllerの戻り値の型をModelAndViewにしましょう。
@Controller
public class HelloController {
@Inject
HttpServletResponse response
@RequestMapping(value="/{param}", method=RequestMethod.GET)
public ModelAndView hello(@PathVariable String param) throws Exception {
if(Objects.equals("param", param)){
response.getWriter().write("hogehoge");
return null;
}
return new ModelAndView("hello");
}
}
なぜこれで上手くいくのかは、ModelAndViewMethodReturnValueHandlerを読めばきっとわかります。