2009-08-11

java struts Remove the .do

Remove the .do

The first step is to remove the redundant .do from the URL. After all,
MIME Content-type headers mean that web resources do not need 'file
extensions' to indicate their type.

The .do is there to map certain URLs to the Struts ActionServlet,
which is achieved by the following web.xml entries:

<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

The most obvious option is to replace the Servlet mapping's extension
mapping to a prefix mapping like <url-pattern>/do/*</url-pattern> to
get a URL like /do/customer?method=edit&id=42. However, this just
moves the problem around rather than fixing anything. After all, if
your server name is only used for one Struts application, then it is a
shame to have to start all URLs with /somearbitrarysubpath. It is,
however, the clue to a better solution.

In the Servlet 2.4 specification, section SRV.11.1 describes how the
following four mapping rules are used in order:

1. exact match, e.g. /access/login
2. longest path prefix match, beginning / and ending /*, e.g. /access/* or /*
3. extension match, beginning *., e.g. *.css
4. default servlet, specified by the single character pattern /

The trick is to only use the third and fourth rules: use the third
rule to map static files by their extensions, and the fourth rule to
map all other requests to the Struts action Servlet.

<!-- Extension mappings to static content -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<!-- Default mapping to Struts action Servlet -->
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

Now you can use /customer?method=edit&id=42 instead of
/customer.do?method=edit&id=42

Unfortunately, this solution only works in Tomcat and JBoss: the
extension mappings use the Servlet name 'default', which Tomcat uses
for serving static files. This is a good enough reason to avoid this
technique for some applications, but does not matter for others. The
other disadvantage is that you have to add an extension mapping for
each extension that your application uses, because you cannot write
<url-pattern>*.*</url-pattern>
Remove the method parameter from DispatchAction URLs

The action mapping is like this (and might be the only one you need):

<action path="/*/*"
type="com.example.web.{1}Action"
parameter="{2}" />

The asterisks match paths like /customer/ or /customer/edit. The first
downside is that if you want to have lower-case URLs then the action
class needs to be called customerAction instead of CustomerAction -
legal but unconventional. Still, I personally think it would be worse
to include upper-case letters in the URL, although that is probably
unavoidable with form parameter names later on.

The second asterisk is something like 'view' or 'edit' - the
DispatchAction method name. The standard approach is to say
parameter="method" in the action mapping and then specify the method
in the URL with method=edit.

With the wildcards in the mapping, you can just get the method name
from the URL and specify it in as the parameter directly by using
parameter="{2}" in the mapping configuration. Then all you need to do
is override the DispatchAction.getMethodName method with:

protected String getMethodName(ActionMapping m, ActionForm f,
HttpServletRequest req, HttpServletResponse res, String p) throws Exception
{
return parameter;
}

This means that that Struts calls the method in your action class
whose name is the parameter value in the action mapping, which in turn
is the second part of the path.

Now you can use /customer/edit&id=42 instead of /customer?method=edit&id=42.

An advantage of this scheme is that you can use the same wildcard
parameters to specify the form name and the JSP path for forwards. For
example, if you use the mapping:

<action
path="/*/*"
type="com.example.web.{1}Action"
name="{1}Form"
scope="request"
validate="false"
parameter="{2}">
<forward name="success" path="/WEB-INF/{1}{2}.jspx"/>
</action>

then the URL path /customer/edit will map to customerForm and customeredit.jspx.

The last time I tried this, this was all we needed for six months.
Only then did we want to add specific roles for each action, so we
stopped using wildcards and listed each action explicitly in
struts-config.xml, using XDoclet to generate the entries.
Remove the ?id=

To get a URL like /customer/edit/42 we need to avoid using a query
string parameter for a single standard parameter. This is a common
case, if you have pages determined by a database primary key, such as
a customer ID in this example.

This time we will appropriate the Struts mapping parameter for this,
and use a normal Action instead of a DispatchAction. This gives us the
Struts mapping:

<action path="/customer/edit/*"
type="com.example.web.CustomerEditAction"
parameter="{1}">
<forward name="success" path="/WEB-INF/customer-edit.jspx"/>
</action>

Then you can use the URL /customer/edit/42 and in the Action, do id =
mapping.getParameter() to get the ID. You can, of course, combine this
with the generic action mapping technique, to get:

<action path="/*/*/*"
type="com.example.web.{1}Action"
parameter="{3}">
<forward name="success" path="/WEB-INF/{1}{2}.jspx"/>
</action>

0 留言: