最近,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的要求说明我必须使用样式表。.)
现在,如果我们访问 https://: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 https://:8080/seam-blog/entry.seam?blogEntryId=3 并看到以下内容
https://hibernate.com.cn/~gavin/blog_entry.png
https://hibernate.com.cn/~gavin/blog_404.png