`
downpour
  • 浏览: 713818 次
  • 性别: Icon_minigender_1
  • 来自: 上海
博客专栏
00a07ae5-264f-3774-8903-8ad88ce08cb0
Struts2技术内幕
浏览量:116838
4d8508f2-c0dd-3df8-9483-04cc612afbbc
SpringMVC深度探险...
浏览量:229926
社区版块
存档分类
最新评论

LightURL——打造零配置的Struts2开发方式

    博客分类:
  • Java
阅读更多
背景
Struts2已经日益成为Web层比较主流的开发框架,它来源于Webwork2,是一个非常优秀的MVC框架。在Webwork2设计之处,Annotation和Ruby on Rails还没有像现在那么火,所以整个框架在配置方面还是沿用了Web框架惯用的XML作为主要的配置方式。

随着时代的发展,对于Web程序员来说,如何简化配置成了一个很重要的课题。在这方面,Struts2也有一些探索。在Struts2的官方网站上,我们可以找到一些优秀的plugin来做这些工作:

http://cwiki.apache.org/S2PLUGINS/codebehind-plugin.html

http://cwiki.apache.org/S2PLUGINS/smarturls-plugin.html
LightURL的目的是为了吸取这些优秀的plugin的优点,并支持更方便的配置方式。

版本提示

目前LightURL是基于struts2.0.X的版本写出来的。由于struts2.1.X尚属于beta版本,而且与之对应的XWork在IoC这块做了重大的修改,所以,我也暂不打算支持到struts2.1.X。如果使用struts2.1.X结合lighturl发生问题的朋友,请退回到struts的稳定版本。

安装
1. 将struts2-lighturl-plugin.jar加入到classpath下

2. 配置你的web.xml
        
        <!-- Struts Filter -->
    <filter>
        <filter-name>struts</filter-name>
        <filter-class>com.demo2do.lighturl.LightURLFilter</filter-class>
    </filter>

    <!-- Struts URL Definition -->
    <filter-mapping>
        <filter-name>struts</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>    

3. 需要指定你的action package所在位置和一些基本配置

这一步一般在struts.properties中完成。你可以建一个struts.properties的配置文件,并放到classpath下,并指定如下配置

## action package config
lighturl.action.packages=com.demo2do.lighturl.action

## action mapping implementation class
struts.mapper.class=com.demo2do.lighturl.LightURLActionMapper

## default parent package
lighturl.action.default.parent.package=lighturl

## define your entity package (optional)
# lighturl.entity.package=com.demo2do.lighturl.entity


在完成以上的步骤后,lighturl的所有配置即告完成,现在你已经可以使用所有lighturl所提供的特性。

Namespace, ActionName和URL映射

lighturl所提供的最基本的特性是根据你的Action所在的package,确定namespace和actionName,并进行对应的URL映射。

举例说明,你在上面所讲述的struts.properties中,已经指定了你的action package

lighturl.action.packages=com.demo2do.lighturl.action


那么,如果你有以下的Action类,那么lighturl将根据如下的规则来确定每个Action类所对应的namespace,actionName和url映射关系

1. 根据lighturl.action.packages的配置的package到你的Action类的相对package来确定namespace,如果其中有驼峰法命名,那么转化成"-"连接的单词
  • com.demo2do.lighturl.action.Index  ----> /
  • com.demo2do.lighturl.action.user.Search ----> /user
  • com.demo2do.lighturl.action.blog.category.Index ----> /blog/category
  • com.demo2do.lighturl.action.accoutDetail.View ----> /account-detail

2. 将Action类的类名转化成actionName。如果碰到的Action类以"Action"结尾,则去掉末尾的"Action",如果其中有驼峰法命名,那么转化成"-"连接的单词
  • com.demo2do.lighturl.action.Index ----> index
  • com.demo2do.lighturl.action.user.SearchAction ----> search
  • com.demo2do.lighturl.action.blog.CategoryBlog ----> category-blog

3. 将namespace与actionName拼起来,就成构成映射到具体Action类的url
  • http://host:port/app/user/index ---> com.demo2do.lighturl.action.user.Index
  • http://host:port/app/blog/category-blog ---> com.demo2do.lighturl.action.blog.CategoryBlog

上面的这种URL匹配方式,我称之为:package匹配

特殊形式的URL

应该说根据package来进行Action映射,可以解决绝大多数从url到action的映射配置问题。不过有的时候,我们可能需要支持一些特殊形式的url。LightURL在默认情况下,支持下列2种特殊形式的URL

1. 支持将名为Index的Action直接映射到package上

这种匹配我称之为:Namespace匹配。这一个特性很直观。如果你在某个Action的package下面有一个名为Index的Action类,那么如果你直接访问这个package,那么你可以访问到这个类:
  • com.demo2do.lighturl.action.Index ---> http://host:port/app/
  • com.demo2do.lighturl.action.user.Index ---> http://host:port/app/user
  • com.demo2do.lighturl.action.blog.category.Index ---> http://host:port/app/blog/category

2. 支持类似: /entity/${id}形式的URL

这种匹配我通常称之为:entity匹配。这个特性也比较简单,如果你有某个entity,并且在你的Action package下有一个与entity同名的package。同时在这个package下有一个叫View的Action,那么上述形式的URL会被映射到该Action。
  • com.demo2do.lighturl.action.user.View ---> http://host:port/app/user/3456
  • com.demo2do.lighturl.action.blog.View ---> http://host:port/app/blog/1113

你可以通过在struts.properties中指定你entity所在的package来对哪些url可以具备这些特性,如果你输入的url不在你所指定的package中含有entity,那么这个url将无法被识别。
针对有些情况,数据库的主键可能不是数字。此时,你可以通过自己实现com.demo2do.lighturl.config.EntityPrimaryKeyIdentifier的接口来指定你的url中id具备什么特点。默认的实现是将主键识别为数字。

不过一般而言,典型的entity匹配是无法满足我们的要求的。因为我们往往不会针对某个entity直接为action建立package,而是会对某个entity,对应到某个Action,它位于某个具体的package。所以针对这样的需求,我对entity匹配做了一定的扩展:

  • com.demo2do.lighturl.action.user.admin.View ---> http://host:port/app/user/user/3456
  • com.demo2do.lighturl.action.blog.category.View ---> http://host:port/app/blog/category/1113

也就是说,某个具体的package中,名字为View的Action,会对应到类似相同package下增加${id}的url。

使用Annotation来指定映射
除了上述这些基本特性以外,还可以通过Annotaion来指定URL映射。目前情况下,LightURL所支持的Annotation有两种类型:

1. URL完整匹配

URL完整匹配是指如果某个url完整匹配于Annotation中所指定的内容,那么这个URL将被映射到Annotation所在的Action类的method

package com.demo2do.lighturl.action.user;

import com.demo2do.lighturl.annotation.Action;
import com.opensymphony.xwork2.ActionSupport;

/**
 * @author Downpour
 *
 */
public class Search extends ActionSupport {
	
	private static final long serialVersionUID = -1728616675239859226L;
	/* (non-Javadoc)
	 * @see com.opensymphony.xwork2.ActionSupport#execute()
	 */
	@Override
	@Action("/all/search-user")
	public String execute() throws Exception {
		return super.execute();
	}
}


例如,上述的Action类有一个Annotation,那么这个Action和method将被映射到对应的URL:http://host:port/app/all/search-user。
注意,此时,虽然从url上来看,这是一个没有什么规则的url,但是其所对应的namespace和actionName还是根据com.demo2do.lighturl.action.user.Search来进行计算的。

2. URL Template

URL Template是指,url可以匹配Annotaion中指定的某种URL Template,并将其中的可变部分作为参数映射到Action中。

package com.demo2do.lighturl.action.blog;

import com.demo2do.lighturl.annotation.Action;
import com.opensymphony.xwork2.ActionSupport;

/**
 * @author Downpour
 *
 */
public class Category extends ActionSupport {

	private static final long serialVersionUID = -1535992103374733252L;

        private Long id;	
	private int year;	
	private int month;	
	private int day;

	/* (non-Javadoc)
	 * @see com.opensymphony.xwork2.ActionSupport#execute()
	 */
	@Override
	@Action("/blogs/${year}/${month}/${day}")
	public String execute() throws Exception {
		return super.execute();
	}
        
        @Action("/blog/${id}/edit")
	public String edit() throws Exception {
		return super.execute();
	}

        @Action("/blogs/common/*")
	public String common() throws Exception {
		return super.execute();
	}
        // setters and getters
	
}


在上述的例子中,可以发现,在Annotation中所指定的内容是一个URL Template,如果你有一个url,可以匹配上面的URL Template,那么url将匹配到这个Action的method,并且将对应位置的值注入到Action中的同名参数中。

不仅如此,LightURL还能够支持AntPath的URL匹配。这样可以通过书写/blogs/common/*或者/blogs/**来匹配多个不同的URL映射。

针对上面的例子:
http://host:port/app/blogs/2008/08/07将被映射到这个Action的execute,并且2008,08和07分别映射到year,month和day中。
http://host:port/app/blog/2345/edit将被映射到Action的edit方法,并且2345映射到id中作为参数
http://host:port/app/blogs/common/abcd将被映射到Action的common方法。


URL的匹配顺序与重复配置的校验

在上面的例子中,介绍了那么多的url映射到Action中的方式。他们之间可能会出现冲突,有些冲突,LightURL会在系统启动时为你检查出来,并强制要求你纠正它,而有些冲突,则通过优先级匹配的方式进行。

下列冲突将被认为是你必须在系统启动前就进行纠正的:
1. Annotation中定义的URL Template互相之间冲突

例如:/blogs/${year}/${month}/${day}和/blogs/${category}/${id}/edit就是冲突的。

2. Annotation中定义的URL Template与其他Annotation中定义的完全匹配URL冲突

例如:/blogs/${year}/${month}和/blogs/category/index就是冲突的。

3. Annotation中定义的URL Template与形如/entity/${id}的url定义冲突

例如:/user/${id}形式的Annotation定义可能会与系统默认支持的冲突。

在其他情况下,如果你定义的URL映射互相直接有冲突,那么LightURL将根据某个顺序进行URL匹配,并找到第一个匹配的映射方式,然后放弃查找。这个顺序为:
1. 首先进行Namespace匹配,如果url恰好能匹配某个namespace,并且其对应的package下有Index作为Action,那么直接进行匹配。
2. 其次查看所有的Annotation定义中,是否存在完整的URL匹配,如果找到,那么进行直接匹配。
3. 接着进行package匹配,将url分解成相应的namespace和actionName,与已有的配置进行匹配,如果找到,那么直接匹配。
4. 然后进行entity匹配,看看url是否形如:/entity/${id},如果是,那么直接匹配。
5. 最后进行Annotation定义的Url Template匹配

如果所有的五种情况都无法进行匹配,那么这个URL将无法被LightURL识别,继续交由Struts2进行后续处理。


上面所描述的内容都是Url到Action的映射。下面的部分,描述的是如何在Action执行完毕之后,转到相应的结果view。

Codebehind

LightURL支持codebehind。有关codebehind的相关知识,可以参考struts2的相关文档:
http://struts.apache.org/2.x/docs/codebehind-plugin.html

有了codebehind的支持,那么从Action转到类似jsp,ftl或者vm的view层组件就不需要任何配置,只要符合一定的命名规范,就可以直接进行转向。

在这里,LightURL不仅实现了Codebehind的所有功能。而且,LightURL还对Codebehind的规则做出了扩展。

Codebehind在寻址JSP等资源时,顺序按照以下的规则进行:

1. /namespace/actionName-<resultCode>.<ext>

2. /namespace/actionName.<ext>


而LightURL在寻址JSP等资源时,增加了一定的规则,并且按照如下顺序进行:

1. /namespace/actionName-<resultCode>.<ext>

2. /namespace/<resultCode>.<ext>

3. /namespace/actionName.<ext>

4. /namespace/namespace-actionName-<resultCode>.<ext>

5. /namespace/namespace-<resultCode>.<ext>

6. /namespace/namespace-actionName.<ext>

这样一来,大家就有更加宽广的选择范围,进行默认资源的寻址匹配了。

ResultCode的识别

但是在很多情况下,我们需要的是全方位的Result类型的支持。例如,有的时候我们需要返回JSON Result,有的时候,我们可能需要Redirect到一个新的Action。此时,我们不得不为此增加一些配置,或者借助Annotation来完成。

为此,LightURL提供了一种根据ResultCode进行识别的命名方式来匹配你所指定的Result。

以JSON Result为例,你可以这么写:

package com.demo2do.lighturl.action.user;

import com.demo2do.lighturl.entity.User;
import com.opensymphony.xwork2.ActionSupport;

/**
 * @author Downpour
 *
 */
public class Index extends ActionSupport {

	private static final long serialVersionUID = -5017825114664788765L;

	private User user;
	
	/* (non-Javadoc)
	 * @see com.opensymphony.xwork2.ActionSupport#execute()
	 */
	@Override
	public String execute() throws Exception {
		return "j:user";
	}

        // setters and getters
}


在这里,你可以发现,resultCode变成了j:user。那么此时,LightURL会将这个resultCode识别成:请返回JSON Result,并且JSON的Result的root为user对象。

再例如:

package com.demo2do.lighturl.action.user;

import com.demo2do.lighturl.entity.User;
import com.opensymphony.xwork2.ActionSupport;

/**
 * @author Downpour
 *
 */
public class Index extends ActionSupport {

	private static final long serialVersionUID = -5017825114664788765L;

	private User user;
	
	/* (non-Javadoc)
	 * @see com.opensymphony.xwork2.ActionSupport#execute()
	 */
	@Override
	public String execute() throws Exception {
		return "r:/user/add-user";
	}

        // setters and getters
}


在这里,"r:/user/add-user"会被识别成:请返回Redirect Result。Redirect的URL为/user/add-user。

LightURL默认情况下,实现了对Redirect Result和JSON Result的ResultCode识别,并将他们的前缀分别定制为:"r:"("redirect:")或者"j:"("json:")。

当然,你可以根据你自己的情况,实现你自己的ResultCode的识别程序,你只需要实现com.demo2do.lighturl.result.ResultCodeConfig接口即可。并在struts.properties中指定你希望LightURL进行ResultCode匹配的顺序。

lighturl.result.code.config=yourpackage.ResultCodeConfigImpl1,yourpackage.ResultCodeConfigImpl2


这样,LightURL会根据你所指定的顺序,依次进行ResultCode的匹配,直到找到第一个匹配的Result Type为之。

当然,默认情况下,LightURL的匹配顺序为:

codebehind -> 你自定义的ResultCode识别实现 -> json -> redirect
分享到:
评论
76 楼 alafqq 2014-09-17  
没想到08年就有了。。。惊呆了。看来我还在学过时的技术。。
75 楼 alafqq 2014-09-17  
非常好,学习了
74 楼 qiren83 2009-09-01  
很牛 不错 但是看不出比 Struts-convition-plugin 有什么好的改进呀
一直用COV 感觉很好用呀
73 楼 jakoes 2009-09-01  
支持楼主的DIY精神,源码DOWN下来抽时间好好研究一下。
SVN上的源码改成HTTP形式的,还能使用SVN来checkout吗?
72 楼 jackie175525 2009-07-31  
<pre name="code" class="java"><pre name="code" class="java">&lt;%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%&gt;
&lt;%@taglib prefix="s" uri="/struts-tags"%&gt;
&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"&gt;
&lt;html&gt;
  &lt;head&gt;
  &lt;/head&gt;
 
  &lt;body&gt;
    &lt;s:property value="name"/&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre>
 import com.opensymphony.xwork2.ActionSupport;

public class Show extends ActionSupport{
private static final long serialVersionUID = 1L;

private String name ;
public Show() {
System.out.println("Show");
}
@Override
public String execute() throws Exception {
setName("scott");
System.out.println("welcome --- Show");
return "r:/show.jsp";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
</pre>
<p> </p>
<p>怎麼跳到  show.jsp 頁面時 </p>
<p> </p>
<p>報 :The Struts dispatcher cannot be found.  This is usually caused by using Struts tags without the associated filter. Struts tags are only usable when the request has passed through its servlet filter, which initializes the Struts dispatcher needed for this tag. - [unknown location]</p>
<p> </p>
<p>請問這個怎麼配置</p>
<p> </p>
71 楼 downpour 2009-02-09  
talangniao 写道
好,谢谢

什么时候能出支持映射到/web-inf/下的jsp?


不打算支持这个功能。

talangniao 写道
经过测试,

lighturl.filter.allowed.extensions=action,java  


/index
/index.action   上面url可以调用到

/index.java 调用不到


/index.java调用不到是因为struts2自身不支持java为后缀的url,你需要对struts2自身做配置。我只能做到让java为后缀的url去走struts2的FilterDispatcher而已。
70 楼 talangniao 2009-02-09  
经过测试,

lighturl.filter.allowed.extensions=action,java  


/index
/index.action   上面url可以调用到

/index.java 调用不到
69 楼 talangniao 2009-02-09  
好,谢谢

什么时候能出支持映射到/web-inf/下的jsp?
68 楼 downpour 2009-02-09  
talangniao 写道
再问:
web.xml
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

能够这样进行配置吗?
进行这样的使用,/index.do

而象你的配置
<filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

/index 这样就可以进行访问了


LightURL目前可以支持对某些后缀的支持,让这些后缀的url进入到struts2的Filter中去。你可以在struts.properties里面进行配置:

lighturl.filter.allowed.extensions=action,java


那么以这些作为后缀的url,会被识别,并进入到struts2的处理流程。
67 楼 talangniao 2009-02-09  
再问:
web.xml
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

能够这样进行配置吗?
进行这样的使用,/index.do

而象你的配置
<filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

/index 这样就可以进行访问了
66 楼 downpour 2009-02-07  
talangniao 写道
使用struts2原来的方法配置可以指向/WEB-INF下的jsp页面
<action name="Com_*" method="{1}" class="com.article.ArticleActiaon">
<result name="list">/WEB-INF/art//list.jsp</result>
</action>

使用LightURL 应该怎样指向WEB-INF下
好像默认只能在WebRoot 下


LightURL暂时无法支持将JSP指向WEB-INF目录下。
65 楼 talangniao 2009-02-07  
使用struts2原来的方法配置可以指向/WEB-INF下的jsp页面
<action name="Com_*" method="{1}" class="com.article.ArticleActiaon">
<result name="list">/WEB-INF/art//list.jsp</result>
</action>

使用LightURL 应该怎样指向WEB-INF下
好像默认只能在WebRoot 下
64 楼 kjj 2009-02-05  
还有一个问题,比如用struts2 ,过滤器自然url 自然配置成/*
当在浏览器中输入一个url 他都试图根据规则去匹配某些 action,实际中某些url 比如/dwr/类似这种url的不让他匹配直接放行,应该有个配置,不知是我没找到文档的配置,还是没有这项!
struts.convention.exclude.packages 试过,楼主也没介绍,不管用还会出现找不到
引用

  There is no Action mapped for action name dwr.


There is no Action mapped for action name dwr. - [unknown location]
    com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:177)
    com.opensymphony.xwork2.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory
。。。。。。
63 楼 wangneng_001 2009-02-05  
不错的东东,回去试试看。。。
62 楼 downpour 2009-02-05  
kjj 写道
我这两天试了convetion 插件,和楼主的功能很相似,而楼主的lighturl 则可看做类似插件的扩展,不过我再这里有个疑问,在试用中感觉到一个问题,在楼主的介绍里没有看到倘若有这样一个action

  package com.example.action.user
  public class UserAction{
         public String editUser(){
      
         }
         public String listUser(){

         }  
         
         public String deleteUser(){

         }  

  
   
  }


这样一个有多个方法的action ,能否在不使用announced 的前提下,根据一定的规则默认映射到三个url呢
比如
/user/user-edit-user
/user/user-list-user
/user/user-delete-user
补充一点,一个action类里的方法默认映射到一个action,如果使用了announced ,再为该方法增加一个url映射呢


你的这个问题,我希望在下一个版本的LightURL上加上。

目前LightURL只是在resultCode的解析上有一些独到的地方,其他地方和Struts2之前的几个插件还是比较类似的,之后我考虑在URL映射上面再下点功夫,争取把默认的许多URL映射直接映射到方法,这样就不需要在方法上配Annotation了。
61 楼 kjj 2009-02-05  
我这两天试了convetion 插件,和楼主的功能很相似,而楼主的lighturl 则可看做类似插件的扩展,不过我再这里有个疑问,在试用中感觉到一个问题,在楼主的介绍里没有看到倘若有这样一个action

  package com.example.action.user
  public class UserAction{
         public String editUser(){
      
         }
         public String listUser(){

         }  
         
         public String deleteUser(){

         }  

  
   
  }


这样一个有多个方法的action ,能否在不使用announced 的前提下,根据一定的规则默认映射到三个url呢
比如
/user/user-edit-user
/user/user-list-user
/user/user-delete-user
补充一点,一个action类里的方法默认映射到一个action,如果使用了announced ,再为该方法增加一个url映射呢
60 楼 iany 2009-01-21  
如果是包找不到,可以用DOCJAR.COM来查找
59 楼 ember_319 2009-01-20  
相当有意思的东西,只是在代码中加入annotation作为配置来使用,会比较难维护。
可以参照Maven的理念,定制一套默认规则,只要使用者按照规则来,就几乎零配置。

总之,赞一个,希望可以越做越好。
58 楼 downpour 2009-01-19  
目前 LightURL是基于struts2.0.X的版本写出来的。由于struts2.1.X尚属于beta版本,而且与之对应的XWork在IoC这块做了重大的修改,所以,我也暂不打算支持到struts2.1.X。如果使用struts2.1.X结合lighturl发生问题的朋友,请退回到 struts的稳定版本。
57 楼 talangniao 2009-01-19  
换了这个jar包,还是包一样的错误!

相关推荐

Global site tag (gtag.js) - Google Analytics