MVC 1.0 是一个在 JSR 371 下的新 Java EE 8 规范。它被设计成在 JAX-RS API (直到某个时刻,Servlet API 还在讨论中)顶层的一个面向行为的框架,并希望这是一个选择,是面向组件的 JSF, 而不是一个替代品。总而言之,MVC 1.0 是一个不同标准的 Java EE 方法,它被用来构建 Java EE平台(相当接近 Spring MVC )的 Web 应用程序。如果你想要控制你的逻辑,你就会想提供一个 URI 空间来进行完全控制,而 MVC 1.0 可能就是个对的选择。
其它翻译版本 (1) 加载中假设你现在已经知道,一般而言,模型指的是应用程序的数据,视图则是应用程序的数据展现,还有控制器是用来管理输入、应用逻辑调用、模型的更新以及决定哪个视图应该被渲染的系统响应。在 MVC 1.0 中, 它们之间的关联如图1所示。
图 1: MVC 1.0
MVC 1.0 增强的模型基本上就是一个定义在javax.mvc.Models中的HashMap, 如下所示。最常见的情况是,你会通过控制器 (JAX-RS 类)来操作这个map,以提供一个名字和对象之间的映射:
// MVC 1.0 - javax.mvc.Models 的源代码 public interface Models extends Map<String, Object>, Iterable<String> { }
这个 HashMap 将会被控制器逻辑操作,并且会对视图进行更新。
注意: 如果你决定编写一个 MVC 1.0 实现, 那你就必须提供一个针对Models接口的实现。 |
尽管 Models 模型必须被所有的视图引擎支持, 不过MVC还带来了另外一种基于CDI (@Named)的模型。CDI 模型相比Models而言更受推荐, 但它也是可选的! 如你稍后将会看到的, MVC 1.0的官方实现也支持 CDI 模型。其它的实现并不会被强制要有 CDI 模型!
存储于模型中的数据应该通过视图(模板)显示出来。为此,视图可以使用特定于视图类型的占位符。每一个占位符都将会被来自于模型的对应数据替换。例如,如果你提供了其中有一个作者属性的Book对象给下面的这些视图,那你就会得到相同的结果 (最常见的就是你将使用EL来指代来自于模型的对应数据):
JSP 视图: ${book.author}
Facelets 视图: #{book.author}
Handlebars 视图: {{book.author}}
Thymeleaf 视图: #{book.author}
如你在图1中所看到的,视图是有如下所示的 ViewEngine 来呈现的:
// MVC 1.0 - source code of javax.mvc.engine.ViewEngine public interface ViewEngine { String VIEW_FOLDER = "javax.mvc.engine.ViewEngine.viewFolder"; String DEFAULT_VIEW_FOLDER = "/WEB-INF/views/"; boolean supports(String view); void processView(ViewEngineContext context) throws ViewEngineException; }
ViewEngine 的任务就是混合模型和视图,而如你将会看到的, 官方的 MVC 1.0 实现带来了几个视图引擎 (首要的视图引擎是针对 JSP 和 Facelets 的)。换言之,视图引擎能够从不同的模型那里获取到数据,然后将需要的视图渲染(产生HTML标记)出来。开发者也可以编写他们自己的引擎,而且如你在本文中将会看到的,这并不是一项非常困难的任务。
控制器是应用程序的“大脑”; 它负责结合数据模型和视图,以此来服务于用户的请求(显示应用程序的页面)。在 MVC 1.0 中, 控制器是以 JAX-RS 风格来实现的。对于初学者和有经验的Java EE开发者而言,这都大大降低了其学习曲线。一旦你了解了如何编写JAX-RS资源, 那也就了解了如何编写 MVC 1.0 的控制器,反之亦然。而这两者之前有几个重要的不同和类似之处。
MVC 1.0 和 JAX-RS 主要的不同如下:
MVC 控制器就是一个在类型/方法上带有注解(javax.mvc.annotation.Controller)的JAX-RS 资源。
@Controller 可以在类型或者方法级别使用。如果你有一个 JAX-RS 资源,它里面带有一个实际上是MVC控制器的方法子集,这就非常有用。这是一个混合型的类,既可以扮演JAX-RS的角色,也可以作为一个 MVC 控制器。
MVC 类必须只能是被CDI管理的bean(并非 JAX-RS 本地类, EJB, 被管理的BEAN, 等等这些)。这一限制对于混合型的类也是如此。这样的类必须是被CDI管理的bean。
由MVC控制器返回的字符串会被理解成一个视图路径而非文本内容(例如,指向一个JSP页面)。因此要对这个方面留点心,因为JAX-RS资源可能会返回内容文本,而 MVC 控制器不会。
对于一个响应而言,默认的媒体类型被假定成 text/html, 不过另外还是可以使用 @Produces 来声明其它类型,就像 JAX-RS 中一样。
返回为空(void)的MVC控制器必须用 @View 注解在类型/方法上进行装饰,以指定要显示的视图。
我们可以通过 javax.mvc.Viewable 类来封装一个视图路径,以及与其处理过程相关的额外的信息。
方法 toString() 会在其它(译者注:被返回的)Java类型上被调用,而其执行结果会被理解成一个视图路径。
返回非空类型的 MVC 控制器可能也会用 @View 来装饰。在这种情况下, @View 指向的是这个控制器的默认视图。默认视图只会在返回非空类型的控制器返回为空(null)时被使用到。
MVC 1.0 和 JAX-RS 之间主要的类似之处如下:
所有在JAX-RS资源中可以被注入的参数类型在MVC控制器中也是可以使用的。
资源类实例默认的声明周期,在JAVA-RS和MVC中都是每个请求期间内(借助CDI,实现可能会支持其他的生命周期)。
在其它生命周期中适应于JAX-RS类的注意事项,同样适用于MVC类。
MVC 1.0 带来了如下这些注解:
@Controller (javax.mvc.annotation.Controller): 应用于类级别, 它定义了一个MVC控制器。应用于方法级别,它定义了一个混合型的类(控制器和JAX-RS资源)。
@View (javax.mvc.annotation.View): 应用于类级别, 它指向所用返回为空(void)的控制器方法的视图。应用于方法级别,它会指向返回为空(void)的控制器方法的视图, 或者指向返回为非空类型的控制器方法返回为null时的视图 (也就是默认视图)。
@CsrfValid (javax.mvc.annotation.CsrfValid): 只可以被应用在方法上,并且在调用控制器之前需要有一个CSRF令牌必须被验证通过。验证失败的话就会借助 ForbiddenException发出信号。
@RedirectScoped (javax.mvc.annotation.RedirectScoped): 可以被应用于类型,方法或者域级别; 它所指向的那个特定的bean在重定向作用域中。
当我们说到 基于动作 同 基于组件的比较时,指的是 MVC 1.0 同 JSF 的比较。显而易见,JSF是一项成熟的技术,发不过几次重要的版本,而MVC1.0只是一个在Java EE 8上才刚刚新登场的规范而已。他们都是基于MVC的,风格迥异。好吧,我们不会在 MVC 1.0 和 JSF的比较上太过坚持, 不过你最好把如下图标中所示的一些事实牢记于心。首先,将模型、视图和控制在MVC1.0和JSF之间做一个区分很重要。图2不言自明。
图 2: JSF MVC
基于动作的MVC | 基于组件的MVC |
Web页面的设计需要开发者自己掌握(他们可以从大返回的技术面上做选择,诸如 HTML5, JS, UI 库,等等)。 | 组件通过框架在页面中庸HTML/JS代码来渲染。页面作者将使用框架提供的UI组件的集合,而且可以选择原生的HTML。 |
手动处理请求参数 | 自动处理请求参数 |
跟踪验证/会话的任务的重担要开发者自己承担。 | 框架会按照开发者的配置来完成对会话和验证的管理。 |
视图不保持,请求无状态。 | 状态在多个请求间默认会(在客户端或者服务端上)被维持, 但JSF也支持无状态。 |
以请求为中心 | 以页面为中心 |
对可重复使用的行为支持有线 | 组件实现了行为的可重复使用。 |
表1:基于动作 vs 基于组件
如图3中所示, MVC 1.0 设计近观:
图 3: MVC 1.0 风格
用户的每一个请求都将会被MVC基于指定路径来分配给合适的控制器(控制器类的一个实例会被初始化并且每次请求都会初始化)。控制器是用开发者来编写的,并且在同一个应用程序中可以有多个。依赖于进入的请求路径,这些可以是纯粹的 MVC 控制器或者同时是MVC 控制器和 JAX-RS 资源的混合型类。此外,路径也许指向的是应该对该请求进行处理的动作方法。一般,这个动作方法将对模型进行操作,并且指向应该向用户显示的视图。视图会反应当前的模型数据 (大多数一般是通过 EL)。JSF的设计就像图 4 中所描述的这样:
图 4: JSF-MVC 风格
在JSF中,控制器是一个叫做 FacesServlet 的 servlet,而用户不能对其进行修改/扩展。这个servlet负责处理所有应该经历JSF生命周期或者指向JSF资源的用户请求。FacesServlet 在请求类型之间做了区分,并据此来进行任务的委托。JSF的生命周期是一个基于两个阶段的复杂的"机械化的“声明周期:执行然后渲染。执行阶段有如下五个过程: 视图重置,应用请求值,处理验证,更新模型值,然后调用应用程序。渲染阶段包含一个过程,叫做渲染响应。这个过程负责渲染出显示给用户看到视图。每个请求都会经历从执行阶段到渲染阶段的所有或者部分阶段。
注意: 在 JSF 和 MVC 做出选择所要依赖的因素有许多,不过你可以牢记于心的是,它们都提供了额外的模板引擎并且可以无状态的进行使用。综合起来比较一个MVC框架,JSF实际的表现要更好。论及代码,有大量的类似之处。不过,还是有大量的基础性的差异,也不不能让你立刻见得那么明显。还有就是最后但不是最重要的,你的考虑到自己关于JSF和MVC的专业知识掌握程序。 |
MVC 1.0 的参考实现被命名为 Ozark。你可以从图5中了解到Ozark主要的功能特性:
图 5: MVC 1.0 参考实现 (Ozark)
Ozark 有两个模型: 模型的首要实现以及可选的CDI模型。此外, Ozark 还有3中视图引擎 : ServletViewEngine, JspViewEngine, 以及 FaceletsViewEngine。当然,你还可以通过实现 ViewEngine 或者扩展现有的视图引擎来实现更多的视图。
这一节我们从一个非常简单的Hello World示例开始。基本上,我们将会有一个HTML开始页面/视图(index.html),它里面会包含一个指向Ozark控制器的动作的链接(路径)。这个动作只会简单的返回另外一个页面/视图 (hello.html)。
首先,我们需要配置 Ozark 应用程序(就像是一个 JAX-RS 应用程序), 意思就是对应用程序响应请求的基础URI进行一下设置。稍后我们会对此进行仔细的研究,而现在我们先用最简单的方式来完成这个任务:
@ApplicationPath("resources") public class HelloApplication extends Application { }
此外我们可以编写HTML页面,非常地简单。 index.html 页面的相关代码如下:
<a href="resources/hello" rel="external nofollow" >Hello World!</a>
href 属性的值呈现的是一个指向我们的控制器/动作的路径。这个路径包含了基础的URI(resources)以及一个相对URI(hello) ,它们之间用一个斜线 "/" 分隔开来:
import javax.mvc.annotation.Controller; import javax.ws.rs.GET; import javax.ws.rs.Path; @Controller @Path("hello") public class HelloController { @GET public String helloAction() { return "/hello.html"; } }
第5行: 在这一行,你能看到有一个@Controller注解。这个注解是 MVC 1.0 特有的,而且是标识出这是一个 MVC 1.0 控制器的地标。你也能将它用在方法上,只对 helloAction() 方法加上这个注解,不过那样做对于混合型类 (Ozark 控制器/JAX-RS 资源) 而言更常见。
第6行: 此外,我们使用@Path注解来指向相对于控制器的URI。因为我们只有一个方法(动作), 所以我们可以说类级别上的@Path最后会被解析到 helloAction() 方法。当控制器中有多个方法(动作)时,就需要在方法(动作)级别上也使用@Path了。每个方法(动作)都会拥有它自己的路径。
第9行: 因为我们倾向于通过HTML的<a/>标识来抵达helloAction(), 我们会声明这个动作能够处理的只能是GET请求。HTML的 <a/> 会触发GET请求。
第11行: 在这一行有一个有趣的细节。这里,我们声明了在这个动作的影响完成之后视图应该被渲染显示给用户。在我们这里,视图被命名为 hello.html。因为视图被存储在和index.html相同的目录 (在webapp目录中), 我们需要为其路径带上一个斜线 "/"作为前缀。没有这个斜线的话,hello.html就不会被定位/发现。这是稍后会讨论的另外一个主题了。因此,hello.html简单得令人有点尴尬:
<h1>Hello World! (HTML page)</h1>
整个应用程序被叫做 HelloWorld。
在这个示例中,我们使用了 HTML 页面,不过如我们早前所说, Ozark 也已经为 Servlet, JSP, 以及 Facelets 都声明了视图引擎。这就意味着 HelloWorld 应用程序可以使用 JSP, Facelets, 或者 Servlet 重写,方法非常容易。
在 JSP 的方式中,我们只是简单地吧 index.html 和 hello.html 重命名为 index.jsp 和 hello.jsp。这样就行了!只是为了好玩,你可以先下面这样编写index.jsp (整个应用程序被叫做 HelloWorldJSP):
<body> <% String str = "resources/hello";%> <a href="<%=str%>" rel="external nofollow" >Hello World!</a> </body>
在 Servlet 的方式中, 需要展示给用户的内容是由 HelloServlet 返回的.
@WebServlet("/HelloServlet") public class HelloServlet extends HttpServlet { ... protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) { ... out.println("<h1>Hello World! (Servlet)</h1>"); ... } } ... }
控制器只是简单地指向这个 Servlet:
@GET public String helloAction() { return "/HelloServlet"; }
整个应用程序被叫做 HelloWorldServlet。
现在,最后一种情况留给了 Facelets。你的意识到 Facelets 的支持默认是不启用的。使用Facelets的 MVC应用程序需要打包一个web.xml部署描述符,带有如下代码所示的 *.xhtml 扩展名映射 (你也可以使用前缀映射 (例如. /faces/*):
<servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class> javax.faces.webapp.FacesServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml*</url-pattern> </servlet-mapping>
如果你的应用程序是以一个Facelet开始的, 那么你就需要也对欢迎页面进行一下配置。 例如:
<welcome-file-list> <welcome-file>index.xhtml</welcome-file> </welcome-file-list>
如果你的应用程序并不是以一个Facelets页面开始的(例如,是从一个JSP/HTML页面开始的), 那么你就不需要这个部分了,并且仍然将Facelets用于剩下的页面,除了开始页面之外。
注意: 遵照 MVC 1.0 规范,值得注意的是如果你选择使用Facelets作为MVC应用程序的视图技术,一般的 POST回传就不会被MVC运行时处理了。 |
完整的应用程序被叫做HelloWorldFacelets。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务