最近,Simon Brown整理了一套需求,用于比较Java Web框架的简单博客应用。我对他所列出的实际需求有所保留(特别是没有表单提交!),但既然其他框架 作者 已经参与其中,我也将示例移植到了Seam。我想对这篇文章提出一个重大的警告:Seam绝对不是为博客或Web论坛等应用设计的;使用像PHP或Ruby on Rails这样的工具解决这类问题非常简单,真的没有理由使用Java来解决这类问题(除非Java是你唯一知道的语言)。这里的需求没有任何/对话/和/业务流程/,所以Seam所有复杂的状态管理机制都是多余的。尽管如此,框架需要让简单的事情变得简单,你将看到使用Seam解决这个简单问题需要编写多少Java代码。
首先,我从Seamweb.xml, faces-config.xml, application.xml和build.xml文件中复制了标准Seamfaces-config.xml预订演示,在几个地方更改了名称,并从中删除了所有JSF导航规则。这些内容都不重要,在Seam应用中总是几乎相同。我还复制了Simon的
screen.css样式表。和Simon从一个领域模型开始,其中包含BlogBlogEntry类,在真实的应用程序中,这些类将通过Hibernate或EJB3映射到数据库,还有一个
@Name("blog") @Startup @Scope(APPLICATION) public class BlogService { private Blog blog; @Create public void initBlog() { blog = new Blog(); blog.setName("Webapp framework blog"); blog.setDescription("Comparison of J2EE web application frameworks"); blog.setLocale(new Locale("en", "AU")); blog.setTimeZone(TimeZone.getTimeZone("PST")); blog.addBlogEntry(new BlogEntry(...); blog.addBlogEntry(new BlogEntry(...); blog.addBlogEntry(new BlogEntry(...); } @Unwrap public Blog getBlog() { return blog; } }
BlogService样式表。和Simon从一个领域模型开始,其中包含类,该类实现了一个静态单例,包含一些测试数据。在Seam中实现单例的静态变量和静态初始化器是一种非常丑陋的方式,所以我冒险将这个类重写为一个应用范围的Seam组件。
这就是开始处理Web应用第一页所需的所有内容。
应用的所有页面都有一些常见的头部信息,所以我们将使用一个facelets模板,template.xhtml,来定义这些共同的东西。
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "[=>http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd]"> <html xmlns="[=>http://www.w3.org/1999/xhtml]" [=>xmlns:ui=]"=>http://java.sun.com/jsf/facelets" [=>xmlns:h=]"=>http://java.sun.com/jsf/html" [=>xmlns:f=]"=>http://java.sun.com/jsf/core" [=>xml:lang=]"en" lang="en"> <f:view> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>#{blog.name}</title> <link href="screen.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="container"> <h1>#{blog.name}</h1> <h2>#{blog.description}</h2> <[=>ui:insert] name="content"/> </div> </body> </f:view> </html>
注意,这全都是带有命名空间的纯XML,没有奇怪的
标签。
索引页,index.xhtml,在示例应用中显示了三条最新的博客条目。
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "[=>http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd]"> <[=>ui:composition] xmlns="=>http://www.w3.org/1999/xhtml" [=>xmlns:ui=]"=>http://java.sun.com/jsf/facelets" [=>xmlns:h=]"=>http://java.sun.com/jsf/html" [=>xmlns:f=]"=>http://java.sun.com/jsf/core" template="template.xhtml"> <[=>ui:define] name="content"> <h:dataTable value="#{blog.recentBlogEntries}" var="blogEntry" rows="3"> <h:column> <div class="blogEntry"> <h3>#{blogEntry.title}</h3> <div> #{blogEntry.excerpt==null ? blogEntry.body : blogEntry.excerpt} </div> <p> <h:outputLink value="entry.seam" rendered="#{blogEntry.excerpt!=null}"> <f:param name="blogEntryId" value="#{blogEntry.id}"/> Read more </h:outputLink> </p> <p> Posted on <h:outputText value="#{blogEntry.date}"> <f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/> </h:outputText> </p> </div> </h:column> </h:dataTable> </[=>ui:define]> </[=>ui:composition]>
这当然也是纯XML。
(通常,我无需在模板中显式定义区域设置。JSF和Seam默认使用请求区域设置。但Simon的要求说明我必须使用样式表。.)
现在,如果我们访问 http://localhost:8080/seam-blog/index.seam,这是结果
https://hibernate.com.cn/~gavin/blog_index.png
现在对于博客条目页(通过点击阅读更多
来访问)。假设我已经正确理解了它们,Simon的要求说明如果请求一个不存在的条目,我们应该发送404错误并转发到错误页。在JSF或Seam中,这并不是最自然的事情。通常,JSF应用程序在处理GET请求时使用拉
式MVC。通常,我会编写条目页来处理不存在条目的情况(这很容易)。但是Simon是这里的老板,他的要求需要推
式设计。我们将使用Seam的/page操作/。
@Name("entryAction") public class EntryAction { @In private Blog blog; @In private FacesContext facesContext; @RequestParameter private String blogEntryId; @Out(scope=EVENT, required=false) private BlogEntry blogEntry; public void getBlogEntry() throws IOException { blogEntry = blog.getBlogEntry(blogEntryId); if (blogEntry==null) { HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse(); response.sendError(HttpServletResponse.SC_NOT_FOUND); facesContext.responseComplete(); } } }
这个操作旨在在条目页渲染之前运行。它从单例实例中检索请求的Simon从一个领域模型开始,其中包含并将其注入到事件上下文中。如果没有样式表。与请求参数匹配,它将发送404错误。Simon从一个领域模型开始,其中包含我们需要在
WEB-INF/pages.xml中列出这个页面操作。.
<pages> <page view-id="/entry.xhtml" action="#{entryAction.getBlogEntry}"/> </pages>
现在我们可以编写条目页,entry.xhtml:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "[=>http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd]"> <[=>ui:composition] xmlns="=>http://www.w3.org/1999/xhtml" [=>xmlns:ui=]"=>http://java.sun.com/jsf/facelets" [=>xmlns:h=]"=>http://java.sun.com/jsf/html" [=>xmlns:f=]"=>http://java.sun.com/jsf/core" template="template.xhtml"> <[=>ui:define] name="content"> <div class="blogEntry"> <h3>#{blogEntry.title}</h3> <div>#{blogEntry.body}</div> <p> Posted on <h:outputText value="#{blogEntry.date}"> <f:convertDateTime timezone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/> </h:outputText> </p> </div> </[=>ui:define]> </[=>ui:composition]>
以及404错误页,404.xhtml:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "[=>http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd]"> <[=>ui:composition] xmlns="=>http://www.w3.org/1999/xhtml" [=>xmlns:ui=]"=>http://java.sun.com/jsf/facelets" [=>xmlns:h=]"=>http://java.sun.com/jsf/html" [=>xmlns:f=]"=>http://java.sun.com/jsf/core" template="template.xhtml"> <[=>ui:define] name="content"> <h3>Page not found</h3> </[=>ui:define]> </[=>ui:composition]>
这个页面需要在web.xml:
<error-page> <error-code>404</error-code> <location>/404.seam</location> </error-page>
中列出。
现在如果我点击阅读更多
链接,我就会访问URL http://localhost:8080/seam-blog/entry.seam?blogEntryId=3 并看到以下内容
https://hibernate.com.cn/~gavin/blog_entry.png
https://hibernate.com.cn/~gavin/blog_404.png