Java EE の MVC1.0 (Model-View-Controller API 1.0 - JSR 371)、Early Draft Review 2 時点のまとめです。
Jersey MVC と同じように JAX-RS の上に乗っかる形となっているため、ほとんどは JAX-RS と同じで、コントローラメソッドに @Controller
付けて戻り値としてViewのパスを返すぐらいな感じです。
コントローラの定義
コントローラは @Controller
アノテーションで定義します。
クラスに付けるとクラス中の全てのリソースメソッドがコントローラになります。
@Path("/") @Controller public class WelcomeController { @GET public String welcome() { return "welcome.html"; } }
メソッドに付与すると、そのメソッドがコントローラメソッドになり、他は JAX-RS のリソースメソッドになります。
@Path("/") @RequestScoped public class VetController { @Inject private Models models; @GET @Controller public String showVetList() { Vets vets = new Vets(vets.findAll()); models.put("vets", vets); return "vets/vetList.html"; } @GET @Path("json") @Produces({MediaType.APPLICATION_JSON}) public Vets showResourcesVetList() { return new Vets(vets.findAll()); } }
パラメータの受け取り方や HTTP メソッドの指定は JAX-RS と同じです。
こちらを参照ください。
View の指定
コントローラからの戻り値の文字列はViewのパスになります。
@GET public String welcome() { return "welcome.html"; }
この場合はデフォルトでは WEB_INF/views/welcome.html
の View が使われます。
文字列の他に void
String
Viewable
Response
を返すことができます。
その他のオブジェクトを戻り値にした場合には toString()
の結果が View のパスとして使われます。
戻り値が void
の場合は @View
アノテーションで View テンプレートのパスを指定します。
@GET @View("welcome.html") public void welcome() { }
Viewable
を使ってパスを指定することもできます。
@GET public Viewable welcome() { return new Viewable("welcome.html"); }
Viewable
はモデルのインスタンスやエンジンを指定することができます。
Viewable(String view, Models models,
Class<? extends ViewEngine> viewEngine)
JAX-RS の Response も使えます。
@GET public Response welcome() { return Response.status(Response.Status.OK) .entity("welcome.html").build(); }
Model
Models
を インジェクトし、View で使うデータを put することで View 側からアクセスできます。
@Path("hello") @Controller public class HelloController { @Inject private Models models; @GET public String hello() { models.put("greeting", new Greeting("Hello there!"); return "hello.html"; } }
Models は単なる Map のインスタンスになります。
public interface Models extends Map<String, Object>, Iterable<String> {}
View エンジンが CDI をサポートしている場合は、以下のような CDI ビーンに @Named
で名前を付けておき、
@Named("greeting") @RequestScoped public class Greeting { // ... }
コントローラで値を設定することで、View からアクセスできます。
@Path("hello") @Controller public class HelloController { @Inject private Greeting greeting; @GET public String hello() { greeting.setMessage("Hello there!"); return "hello.jsp"; } }
View
View は色々なエンジンが使えます。 Ozark の M02 では以下のような Extension が提供されています。
- Thymeleaf Extension
- Mustache Extension
- AsciiDoc Extension
- JSR 223 Extension
- Velocity Extension
- Freemarker Extension
- Handlebars Extension
JSP の場合は以下のように EL 式で Model にアクセスします。
<html> <head> <title>Hello</title> </head> <body> <h1>${greeting.message}</h1> </body> </html>
View エンジンの選択は以下を参照ください。
リダイレクト
リダイレクトする場合は Response で seeOther を返します。
@GET @Controller public Response redirect() { return Response.seeOther(URI.create("see/here")).build(); }
または以下のように redirect:
のプレフィックスを付けます。
@GET @Controller public String redirect() { return "redirect:see/here"; }
以下のように @RedirectScoped
アノテーションされたクラスがあった場合
@RedirectScoped public class MyBean { }
post()
でリダイレクトした先の get()
の View で myBean の内容が引き継げます。
@Controller @Path("submit") public class MyController { @Inject private MyBean myBean; @POST public String post() { myBean.setValue("Redirect about to happen"); return "redirect:/submit"; } @GET public String get() { return "mybean.html"; } }
例外マッパ
JAX-RS と同じように ExceptionMapper を定義します。
ExceptionMapper は以下の定義になっています。
public interface ExceptionMapper<E extends Throwable> { Response toResponse(E var1); }
実装は以下のような感じになります。
例外マッパを扱いたい例外に応じて定義します。
@Provider @ApplicationScoped public class AppExceptionMapper implements ExceptionMapper<Throwable> { @Inject private Models models; @Override public Response toResponse(Throwable e) { models.put("message", e.getMessage()); return Response.status(Response.Status.BAD_REQUEST).entity("error.html").build(); } }
@Provider
を付けることで JAX-RS が例外マッパとして拾ってくれます。
CDI 1.2 の場合は @ApplicationScoped
などを指定して CDI 管理対象とすることで、Models
など CDI でインジェクトできます。
例えば Thymeleaf テンプレートでは以下のようにModel の値にアクセスできます。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <h2>Something happened...</h2> <p th:text="${message}">Exception message</p> </body> </html
この後で出てくる ParamConverter
についてもそうですが、現時点では例外マッパに優先度(HK2のpriority)を指定できないため、ExceptionMapper<Exception>
などとすると、組込の ExceptionMapper
が優先されてしまう問題があります(直接HK2で扱えば解決できますが、あまり現実的ではありません)。
Validation Exceptions
Bean Validation の扱いなども JAX-RS と同じです。
Entity に対する Bean Validation などは以下のように @Valid
と @BeanParam
を付けることで Bean Validation が動きます。
@Path("owners") @Controller @RequestScoped public class OwnerController { @EJB private OwnerRepository repository; @Inject private BindingResult bindingResult; @Inject private Models models; @POST @Path("new") @ValidateOnExecution(type = ExecutableType.NONE) public String processCreationForm(@Valid @BeanParam Owner owner) { if (bindingResult.isFailed()) { models.put("owner", owner); models.put("bindingResult", bindingResult); return "owners/createOrUpdateOwnerForm.html"; } repository.save(owner); return "redirect:/owners/" + owner.getId(); } }
通常は ValidationException が発生した場合はメソッドの呼び出しがおこなれませんが、@ValidateOnExecution(type = ExecutableType.NONE)
のようにしておくことで、コントローラメソッド が呼び出されます。
この際、Validation の結果は BindingResult をインジェクトしておけば、そこから内容がとれます。JAX-RS と同じですね。
MvcContext
MvcContext をインジェクトすることで、設定、セキュリティやパスの情報にアクセスできます。
JSP などで以下のようにコンテキストパスを得たりすることができます。
<link rel="stylesheet" type="text/css" href="${mvc.contextPath}/my.css">
以下のようなインターフェースになっています。
public interface MvcContext { Configuration getConfig(); String getContextPath(); String getApplicationPath(); String getBasePath(); Csrf getCsrf(); Encoders getEncoders(); }
Ozark では以下のような実装になっています。
@Named("mvc") @ApplicationScoped public class MvcContextImpl implements MvcContext { // ... }
まとめ
まだ Early Draft Review 2 なので今後変更になる可能性がありますが、JSR 371 Model-View-Controller API 1.0 の基本について見てきました。
次回は Spring PetClinic Sample Application を MVC1.0 で実装してみます。