|
|
||
Simon Brown's BlogMarch 2006 ArchivesComparing webapp frameworks : WebWorkPosted by simongbrown on March 24, 2006 at 03:02 PM | Permalink | Comments (5)Like Struts, WebWork is a framework that is fairly established within the J2EE webapp space although it's interesting that I've only ever come across two types of WebWork users - those that have never heard of it and those that love it. WebWork, like most other frameworks, is designed around the web MVC pattern and uses the command and controller implementation strategy. What's slightly different about WebWork is that it's built on top of XWork, a separate framework that provides an implementation of the command pattern that is independent of the Servlet API.
In XWork, actions (or commands, depending on your terminology) implement the
Right, let's get on with seeing how a WebWork implementation of the sample application compares to the others. I'm using WebWork 2.1.7 and installing it is a simple matter of copying a few JAR files into the <servlet> <servlet-name>webwork</servlet-name> <servlet-class>com.opensymphony.webwork.dispatcher.ServletDispatcher</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>webwork</servlet-name> <url-pattern>*.action</url-pattern> </servlet-mapping> <taglib> <taglib-uri>webwork</taglib-uri> <taglib-location>/WEB-INF/lib/webwork-2.1.7.jar</taglib-location> </taglib>
Two additional configuration files that we need are called <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">
<validators>
</validators>
The other file ( Home page The home page presents the user a list of recent blog entries and with WebWork, the code that finds these blog entries get wrappeds up inside an XWork action as follows. package action;
import com.opensymphony.xwork.ActionSupport;
import domain.Blog;
import domain.BlogService;
/**
* Action responsible for finding blog entries, ready to be displayed.
*
* @author Simon Brown
*/
public class ViewBlogEntriesAction extends ActionSupport {
/** the blog that owns the blog entries */
private Blog blog;
/**
* Performs the processing associated with this action.
*
* @return a String defining the outcome of this action
*/
public String execute() throws Exception {
BlogService blogService = new BlogService();
this.blog = blogService.getBlog();
return SUCCESS;
}
/**
* Gets the blog that this action is operating upon.
*
* @return a Blog instance
*/
public Blog getBlog() {
return blog;
}
}
This implementation is very straightforward, just looking up the <!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-1.0.dtd">
<xwork>
<include file="webwork-default.xml" />
<package name="default" extends="webwork-default">
<default-interceptor-ref name="defaultStack" />
<action name="viewBlogEntries" class="action.ViewBlogEntriesAction">
<result name="success" type="dispatcher">viewBlogEntries.jsp</result>
</action>
</package>
</xwork>
Here, an action called <%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="webwork" prefix="ww" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
<!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">
<head>
<title><ww:property value="blog.name" /></title>
<link rel="stylesheet" href="screen.css" type="text/css" />
</head>
<body>
<div id="container">
<h1><ww:property value="blog.name" /></h1>
<h2><ww:property value="blog.description" /></h2>
<ww:iterator id="blogEntry" value="blog.blogEntries">
<div class="blogEntry">
<h3><ww:property value="title"/></h3>
<ww:if test="excerpt != null">
<ww:property value="excerpt"/>
<p>
<a href="<ww:url value="'viewBlogEntry.action'">
<ww:param name="'id'" value="id"/>
</ww:url>">Read more</a>
</p>
</ww:if>
<ww:else>
<ww:property value="body"/>
</ww:else>
<p>
Posted on <ww:property value="date" />
</p>
</div>
</ww:iterator>
</div>
</body>
</html>
If you're familiar with something like the Struts bean/logic tags or JSTL, you'll probably pick up the WebWork tags pretty quickly.
Blog entry detail page package action;
import com.opensymphony.xwork.ActionSupport;
import domain.Blog;
import domain.BlogEntry;
import domain.BlogService;
/**
* Responsible for finding a specific blog entry.
*
* @author Simon Brown
*/
public class ViewBlogEntryAction extends ActionSupport {
/** the ID of the blog entry to display */
private String id;
/** the blog that owns the blog entries */
private Blog blog;
/** the blog entry to be displayed */
private BlogEntry blogEntry;
/**
* Performs the processing associated with this action.
*
* @return a String defining the outcome of this action
*/
public String execute() throws Exception {
BlogService blogService = new BlogService();
blog = blogService.getBlog();
blogEntry = blog.getBlogEntry(id);
if (blogEntry == null) {
return "notfound";
} else {
return SUCCESS;
}
}
/**
* Setter for the id property.
*
* @param id a String
*/
public void setId(String id) {
this.id = id;
}
/**
* Gets the blog that this action is operating upon.
*
* @return a Blog instance
*/
public Blog getBlog() {
return blog;
}
/**
* Gets the blog entry to be displayed.
*
* @return a BlogEntry instance
*/
public BlogEntry getBlogEntry() {
return blogEntry;
}
}
Next up is the fragment of XML that needs to be inserted into the <action name="viewBlogEntry" class="action.ViewBlogEntryAction">
<result name="success" type="dispatcher">viewBlogEntry.jsp</result>
<result name="notfound" type="dispatcher">404.jsp</result>
</action>
And finally is the JSP page itself. <%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="webwork" prefix="ww" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
<!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">
<head>
<title><ww:property value="blogEntry.title" /> : <ww:property value="blog.name" /></title>
<link rel="stylesheet" href="screen.css" type="text/css" />
</head>
<body>
<div id="container">
<h1><ww:property value="blog.name" /></h1>
<h2><ww:property value="blog.description" /></h2>
<div class="blogEntry">
<h3><ww:property value="blogEntry.title" /></h3>
<ww:property value="blogEntry.body" />
<p>
Posted on <ww:property value="blogEntry.date" />
</p>
</div>
</div>
</body>
</html>
Summary Comparing webapp frameworks : StripesPosted by simongbrown on March 10, 2006 at 06:06 AM | Permalink | Comments (8)Stripes is a relatively new web application framework that's been built with a couple of things in mind - simplicity and the adoption of new technology. In Stripes, you won't find any complex XML configuration files and, refreshingly, the distribution only requires/includes a small number of dependent JAR files. Stripes is a genuinely lightweight framework and uses Java 5 annotations as it's configuration mechanism.
Like other webapp frameworks, Stripes sticks to the typical model 2 architecture, implemented using the command and controller strategy where the main front controller component delegates request processing responsibility to an action class. However, unlike most other frameworks, you aren't restricted to implementing a specific
Home page
package action;
import domain.*;
import net.sourceforge.stripes.action.*;
/**
* A Stripes ActionBean that is responsible for allowing the
* user to view all recent blog entries.
*
* @author Simon Brown
*/
@UrlBinding("/viewBlogEntries.action")
public class ViewBlogEntriesActionBean implements ActionBean {
private ActionBeanContext context;
public ActionBeanContext getContext() {
return context;
}
public void setContext(ActionBeanContext context) {
this.context = context;
}
@DefaultHandler
@DontValidate
public Resolution viewBlogEntries() {
BlogService blogService = new BlogService();
Blog blog = blogService.getBlog();
context.getRequest().setAttribute("blog", blog);
return new ForwardResolution("/viewBlogEntries.jsp");
}
}
First up, we need to implement an interface called Interface for all classes that respond to user interface events. Implementations receive information about the event (usually a form submission) in two ways. The first is through a ActionBeanContext object which will always be set on the ActionBean prior to any user implemented "handler" methods being invoked. The second is through the setting of JavaBean style properties on the object (and nested JavaBeans). Values submitted in an HTML Form will be bound to a property on the object if such a property is defined. The ActionBeanContext is used primarily to provide access to Servlet APIs such as the request and response, and other information generated during the pre-processing of the request.
In summary, it's a simple interface with a pair of methods to get and set an
So how do requests get to this class? Well, in Stripes you map a URL to an <filter>
<display-name>Stripes Filter</display-name>
<filter-name>StripesFilter</filter-name>
<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<servlet-name>StripesDispatcher</servlet-name>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>StripesDispatcher</servlet-name>
<servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>StripesDispatcher</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
As for the actual processing, there's nothing really new here. We just find the list of blog entries and insert them into the request scope. Since we're using JSP for the view technology, we return a instance of <%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
<!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">
<head>
<title>${blog.name}</title>
<link rel="stylesheet" href="screen.css" type="text/css" />
</head>
<body>
<div id="container">
<h1>${blog.name}</h1>
<h2>${blog.description}</h2>
<c:forEach var="blogEntry" items="${blog.blogEntries}">
<div class="blogEntry">
<h3>${blogEntry.title}</h3>
<c:choose>
<c:when test="${not empty blogEntry.excerpt}">
${blogEntry.excerpt}
<p>
<stripes:link href="/viewBlogEntry.action">
Read more
<stripes:link-param name="id" value="${blogEntry.id}" />
</stripes:link>
</p>
</c:when>
<c:otherwise>
${blogEntry.body}
</c:otherwise>
</c:choose>
<p>
Posted on <fmt:formatDate value="${blogEntry.date}"
timeZone="${blog.timeZone}" type="both"
dateStyle="long" timeStyle="long" />
</p>
</div>
</c:forEach>
</div>
</body>
</html>
The only real difference between this and other JSP implementations is in the use of the
Before we move on, I wanted to mention that
Blog entry detail page
package action;
import net.sourceforge.stripes.action.*;
import domain.BlogService;
import domain.Blog;
import domain.BlogEntry;
/**
* A Stripes ActionBean that is responsible for allowing the user
* to view a single blog entry.
*
* @author Simon Brown
*/
@UrlBinding("/viewBlogEntry.action")
public class ViewBlogEntryActionBean implements ActionBean {
private ActionBeanContext context;
private String id;
public ActionBeanContext getContext() {
return context;
}
public void setContext(ActionBeanContext context) {
this.context = context;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@DefaultHandler
@DontValidate
public Resolution viewBlogEntry() throws IOException {
BlogService blogService = new BlogService();
Blog blog = blogService.getBlog();
context.getRequest().setAttribute("blog", blog);
BlogEntry blogEntry = blog.getBlogEntry(id);
if (blogEntry == null) {
context.getResponse().sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
} else {
context.getRequest().setAttribute("blogEntry", blogEntry);
return new ForwardResolution("/viewBlogEntry.jsp");
}
}
}
As with other implementations, there's a simple flow of code of that finds the blog and, if the specified blog entry is found, forwards the user to the blog entry page. However, the thing that that makes this different from many other frameworks is that any submitted request parameters are automatically mapped by Stripes onto JavaBean properties on your So, moving on, you'd expect that the JSP implementation of the view blog entry page is also similar to those shown before. You'd be right, here it is. <%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
<!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">
<head>
<title>${blogEntry.title} : ${blog.name}</title>
<link rel="stylesheet" href="screen.css" type="text/css" />
</head>
<body>
<div id="container">
<h1>${blog.name}</h1>
<h2>${blog.description}</h2>
<div class="blogEntry">
<h3>${blogEntry.title}</h3>
${blogEntry.body}
<p>
Posted on <fmt:formatDate value="${blogEntry.date}"
timeZone="${blog.timeZone}" type="both"
dateStyle="long" timeStyle="long" />
</p>
</div>
</div>
</body>
</html>
Summary
Comparing webapp frameworks : WicketPosted by simongbrown on March 09, 2006 at 01:53 AM | Permalink | Comments (3)Guillermo Castro has posted a Wicket implementation of the webapp comparison that I started a while ago. It's an interesting read and the contrast with most page/request based webapp frameworks is amazing. In summary, Guillermo says: Wicket, in my opinion, focuses the development efforts in the right place, inside plain Java code, and leaves the graphical presentation where it should be, inside html. At first you might find a little hard to grasp this paradigm shift, as so many developers are being 'forced' to rely on jstl and jsp scriptlets to accomplish logic programming for a page with all the other frameworks. But once you get used to this, I'm sure wicket will provide really fast development times.
From my perspective, the thing that I really like about Wicket is that way that you can build common components, as illustrated by the I've never seen a finer explanation of why Java web development takes forever. One of the reasons that I wanted to compare frameworks with a simple read-only app is because I wanted to see whether the easy stuff was actually easy, and I've taken a lot of flak about this. From a newcomers perspective, Wicket *looks* complicated. Once I (eventually!) get a few more frameworks written up, I'll add some read/write behaviour into the application and hopefully we'll start to see some of Wicket's strengths shine through. My thanks go to Guillermo for volunteering to do this. I have the WebWork and Stripes versions half written up, so I'll make an effort to getting these out as soon as possible. If anybody would like to volunteer to provide a similar implementation in other webapp frameworks, please get in touch and I'll e-mail you the source. If you don't have a website/blog, I'm more than happy to publish it on my blog for you. | ||
|
|