`
Harold_xlp
  • 浏览: 154343 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Struts2的REST插件[转]

阅读更多
此REST插件简化了人们和电脑客户端对REST风格资源的访问。其目标是100%实现和Ruby on Rails REST URL风格相兼容的,并且可以免除使用XML作为资源的开发模式。它基于Convention Plugin来支持对action和result的自动配置。

此Rest风格的action映射强制兼容Ruby-On-Rails的风格映射。如果方法未被指定(通过“!”或者“method:”前缀),那么将基于ReST风格的约定(通过检查URL和HTTP方法)进行猜测。这里特别说明的是,此插件和codebehind plugin一起也可以正常工作,因此,不再需要使用基于XML的配置文件。

此映射支持如下参数:

struts.mapper.idParameterName - 如果设置,则它的值将被作为保存id的参数名,然后id将被从action名称中移除。不管是否指定了相关方法,此映射都会试图从url中移除标识符并将它作为一个参数保存。
struts.mapper.indexMethodName - 在没有id参数的情况下,通过GET请求调用的方法名。默认为index。
struts.mapper.getMethodName - 在具有id参数的情况下,通过GET请求调用的方法名。默认为show。
struts.mapper.postMethodName - 在没有id参数的情况下,通过POST请求调用的方法名。默认为create。
struts.mapper.putMethodName - 在具有id参数的情况下,通过PUT请求调用的方法名。默认为update。
struts.mapper.deleteMethodName - 在具有id参数的情况下,通过DELETE请求调用的方法名。默认为destory。
struts.mapper.editMethodName - 在具有id参数且指定了“edit”视图的情况下,通过GET请求调用的方法名。默认为edit。
struts.mapper.newMethodName - 在没有id参数且指定了“new”视图的情况下,通过GET请求调用的方法名。默认为editNew。


下列URL将调用Action的相关方法如下:

GET: /movies => method="index"
GET: /movies/Thrillers => method="show", id="Thrillers"
GET: /movies/Thrillers;edit => method="edit", id="Thrillers"
GET: /movies/Thrillers/edit => method="edit", id="Thrillers"
GET: /movies/new => method="editNew"
POST: /movies => method="create"
PUT: /movies/Thrillers => method="update", id="Thrillers"
DELETE: /movies/Thrillers => method="destroy", id="Thrillers"
为了模拟HTTP方法的 PUT和DELETE请求,因为HTML并不支持,我们将使用一个叫做“_method”的HTTP参数。

或者,使用下表的表示方式:
HTTP method URI Class.method parameters
GET /movie Movie.index
POST /movie Movie.create
PUT /movie/Thrillers Movie.update id="Thrillers"
DELETE /movie/Thrillers Movie.destroy id="Thrillers"
GET /movie/Thrillers Movie.show id="Thrillers"
GET /movie/Thrillers/edit Movie.edit id="Thrillers"
GET /movie/new Movie.editNew

除了作为一个REST风格的URL映射器以外,此插件还对于multiple content types、通过URL扩展名的可切换性提供了内建的支持。这样,一个单一的资源就能够被作为一个multiple content types暴露出去,而不需要其他额外的工作。

例如,通过暴露一个“orders”资源,客户端可以立即通过如下方式进行访问:

http://my.company.com/myapp/orders/1
http://my.company.com/myapp/orders/1.xml
http://my.company.com/myapp/orders/1.xhtml
http://my.company.com/myapp/orders/1.json

此REST插件自动处理序列号和反序列化以及相应的格式化。

一、特性
完全实现 Ruby on Rails 的REST风格URL
支持免XML开发,不需要注解
内建序列号和反序列化支持,以支持XML和JSON
自动错误处理
针对HTTP响应的类型安全设置
自动有条件的GET支持

二、用法
在配置的包(package)中创建以“Controller”结尾的Java对象。这里的 “Controller”后缀用于区别REST action和普通的Struts2 action。尽管这完全是可选的,以及它们的功能完全相同。现在,加入方法即可处理各种请求。举例说明,下面的资源action将支持使用GET和 PUT请求访问/orders/34资源:

package org.apache.struts2.rest .example;

public class OrdersController implements ModelDriven {

    private OrderManager orderManager;
    private String id;
    private Order model;

    // Handles /orders/{id} GET requests
    public HttpHeaders show() {
        model = orderManager.findOrder(id);
        return new DefaultHttpHeaders("show" )
            .withETag(model.getUniqueStamp())
            .lastModified(model.getLastModified());
    }

    // Handles /orders/{id} PUT requests
    public String update() {
        orderManager.updateOrder(model);
        return "update" ;
    }

    // getters and setters
}

在这个例子中,使用了ModelDriven 接口,来确保只有模型——这里的Order对象,被返回给客户端。否则,OrdersController 对象将被整个序列化。

你也许在想为什么show()方法返回了一个HttpHeaders 对象,而update()方法返回了预期的结果代码字符串。REST插件添加了对action方法的支持,对于那些通过响应有更多控制的action,返回HttpHeaders 对象是一种方式。在本例中,我们想要确保响应包括ETag 头和一个最近修改日期,以便信息可以被客户端正确的缓存。HttpHeaders 对象是一个方便的方式,可以在保证在类型安全的情况下控制响应。

而且,请注意,我们没有在上述任何一个方法中,返回通常的“success”结果码。这允许我们使用Codebehind Plugin插件的特性,当访问扩展名为.xhtml的资源时,以更加直观的方式来选择结果模板去处理。在本例中,我们可以通过为相应的方法创建 /orders-show.jsp 和/orders-update.jsp页面,来提供一个定制化的XHTML 资源视图。

2.1 定制ContentTypeHandlers
如果你需要处理不被默认处理器支持的扩展名时,你可以创建你自己的ContentTypeHandler 实现,并在struts.xml中进行定义即可。

<bean name="yaml"
type="org.apache.struts2.rest.handler.ContentTypeHandler"
class="com.mycompany.MyYamlContentHandler"
/>


如果内建的内容类型处理器无法满足你的需要,那么你可以提供另一个处理器,覆盖对任何扩展名的默认处理方式,并使用它自己的别名进行声明,例如:

<bean name="myXml" type="org.apache.struts2.rest.handler.ContentTypeHandler" class="com.mycompany.MyXmlContentHandler" /> 

然后,只需要告知REST插件使用你自己的处理器,去覆盖特定扩展的处理器即可。在struts.properties文件中,可以做如下配置:

struts.rest.handlerOverride.xml=myXml

2.2 关于struts.xml
配置Struts以使用REST action映射器:

<constant name="struts.mapper.class"
value="rest"
/>


因为REST插件使用Convention plugin,需要在Struts.xml中进行相关设置:

<constant name="struts.convention.action.suffix"
value="Controller"
/>


<constant name="struts.convention.action.mapAllMatches"
value="true"
/>


<constant name="struts.convention.default.parent.package"
value="rest-default"
/>


对于上述例子而言,package设置如下:

<constant name="struts.convention.package.locators" value="example" />

三、示例
此插件是随struts2-rest-showcase 应用程序一起发布的,用以演示一个简单的REST web应用程序。

该示例和不同于一般,控制器将被映射到一个关联的HTTP(PUT,DELETE)方法。

看下图可以让你更容易的理解REST的工作方式:

四、配置
配置可以定制,具体可以查看开发指南文档。要获得更多的配置项信息,可以查看 Convention Plugin的文档。
设置 描述 默认 可能值
struts.rest.handlerOverride.EXTENSION 处理EXTENSION 值的ContentTypeHandler 实现的别名 N/A 任何声明了的ContentTypeHandler实现的别名
struts.rest.defaultExtension 当没有在request中显示指定的时候默认使用扩展类型 xml 任意扩展名
struts.rest.validationFailureStatusCode 校验失败后返回的HTTP状态码 400 任意HTTP的数字状态码

五、安装
复制插件的jar包到应用程序的/WEB-INF/lib 即可。此插件依赖于Convention Plugin,因此,如果没有类似于Maven2那样的支持传递依赖的构建系统的话,你可能也需要加入Convention Plugin的jar包。

六、资源
http://www.b-simple.de/documents - 精短的RESTful Rails 教程(PDF格式, 有多种语言)
RESTful Web Services - 来自 O'Reilly 的一本书
最近开始关注struts2的新特性,从这个版本开始,Struts开始使用 convention-plugin代替codebehind-plugin来实现struts的零配置。
配置文件精简了,的确是简便了开发过程,但是,我们熟悉的配置突然disappear了,真是一下很不适应。跟着潮流走吧,看看该怎样来搞定 convention-plugin。
使用Convention插件,你需要将其JAR文件放到你应用的WEB-INF/lib目录中,你也可以在你Maven项目的POM文件中添加下面包依赖
Xml代码

   1. <dependency> 
   2.   <groupId>org.apache.struts</groupId> 
   3.   <artifactId>struts2-convention-plugin</artifactId> 
   4.   <version>2.1.6</version> 
   5. </dependency> 

<dependency>
  <groupId>org.apache.struts</groupId>
  <artifactId>struts2-convention-plugin</artifactId>
  <version>2.1.6</version>
</dependency>



零配置并不是没有配置,而是通过约定大于配置的方式,大量通过约定来调度页面的跳转而使得配置大大减少。所以,首先应该了解下convention- plugin的约定:
1. 默认所有的结果页面都存储在WEB-INF/content下,你可以通过设置struts.convention.result.path这个属性的值来改变到其他路径。如:
Xml代码

   1. <constant name="struts.convention.result.path" value="/WEB-INF/page" /> 

<constant name="struts.convention.result.path" value="/WEB-INF/page" />


则将路径配置到了WEB-INF/page 下。
2. 默认包路径包含action,actions,struts,struts2的所有包都会被struts作为含有Action类的路径来搜索。你可以通过设置struts.convention.package.locators属性来修改这个配置。如:
Xml代码 复制代码

   1. <constant name="struts.convention.package.locators" value="web,action" /> 

<constant name="struts.convention.package.locators" value="web,action" />


则定义了在项目中,包路径包含web和action的将被视为Action存在的路径来进行搜索。
Com.ustb.web.*/com.ustb.action.*都将被视为含有Action的包路径而被搜索。
3. 接着,Convention从前一步找到的package以及其子package中寻找 com.opensymphony.xwork2.Action 的实现以及以Action结尾的类:
Java代码 复制代码

   1. com.example.actions.MainAction  
   2. com.example.actions.products.Display (implements com.opensymphony.xwork2.Action)  
   3. com.example.struts.company.details.ShowCompanyDetailsAction 

com.example.actions.MainAction
com.example.actions.products.Display (implements com.opensymphony.xwork2.Action)
com.example.struts.company.details.ShowCompanyDetailsAction



4. 命名空间。从定义的.package.locators标示开始到包结束的部分,就是命名空间。举个例子:
Com.ustb.web.user.userAction的命名空间是:”/user”。 Com.ustb.web.user.detail.UserAction的命名空间是:”/user/detail”
5. Convention通过如下规则确定URL的具体资源部分:去掉类名的Action部分。然后将将每个分部的首字母转为小写,用’-’分割,你可以设置 struts.convention.action.name.separator 如
Xml代码 复制代码

   1. <constant name="struts.convention.action.name.separator" value="-" /> 

<constant name="struts.convention.action.name.separator" value="-" />


还是举个例子:
UserAction->user UserDetailAction ->user-detail。结合上面的。对于com.ustb.web.user.detail.UserDetailAction,映射的 url就是/WEB-INF/content/user/detail/user-detail.jsp
6. struts支持.jsp .html .htm .vm格式的文件。
下面是actiong和结果模版的映射关系:
URL Result
File that could match Result Type
/hello success /WEB-INF/content/hello.jsp Dispatcher
/hello success /WEB-INF/content/hello-success.htm Dispatcher
/hello success /WEB-INF/content/hello.ftl FreeMarker
/hello-world input /WEB-INF/content/hello-world-input.vm Velocity
/test1/test2/hello error /WEB-INF/content/test/test2/hello-error.html Dispatcher

以上的内容来自struts2的文档http://struts.apache.org/2.1.6/docs/convention-plugin.html


当然,简单的通过默认的方式来进行配置不能完全满足实际项目的需要。所幸,convention的零配置是非常灵活的。
通过@Action注释
对如下例子:
Java代码 复制代码

   1. package com.example.web;  
   2.  
   3. import com.opensymphony.xwork2.Action;  
   4. import com.opensymphony.xwork2.ActionSupport;  
   5.  
   6. public class HelloAction extends ActionSupport {  
   7.     @Action("action1")  
   8.     public String method1() {  
   9.         return SUCCESS;  
  10.      }  
  11.  
  12.     @Action("/user/action2")  
  13.     public String method2() {  
  14.         return SUCCESS;  
  15. }  
  16. } 

package com.example.web;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionSupport;

public class HelloAction extends ActionSupport {
    @Action("action1")
    public String method1() {
        return SUCCESS;
    }

    @Action("/user/action2")
    public String method2() {
        return SUCCESS;
}
}

方法名 默认调用路径 默认映射路径
method1 /hello!method1.action . /WEB-INF/content/hello.jsp
method2 /hello!method2.action. /WEB-INF/content/hello.jsp

通过@Action注释后
方法名 @Action注释后调用路径 @Action注释 后映射路径
method1 /action1!method1.action. /WEB-INF/content/action1.jsp
method1 /user/action2!method2.action /WEB-INF/content/user/action2.jsp


通过@Actions注释
Java代码 复制代码

   1. package com.example.web;  
   2.  
   3. import com.opensymphony.xwork2.ActionSupport;  
   4. import org.apache.struts2.convention.annotation.Action;  
   5. import org.apache.struts2.convention.annotation.Actions;  
   6.  
   7. public class HelloAction extends ActionSupport {  
   8.   @Actions({  
   9.     @Action("/different/url"),  
  10.     @Action("/another/url")  
  11.    })  
  12.   public String method1() {  
  13.     return “error”;  
  14.    } 

package com.example.web;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;

public class HelloAction extends ActionSupport {
  @Actions({
    @Action("/different/url"),
    @Action("/another/url")
  })
  public String method1() {
    return “error”;
  }


我们可以通过:/different/url!method1.action 或 /another/url!method1.action 来调用method1 方法。
对应的映射路径分别是/WEB-INF/content/different/url-error.jsp; /WEB-INF/content/another/url-error.jsp

可能误导了大家,一个方法被@Action注释后,只是多了一种调用方式,而不是说覆盖了原来的调用方式。比如对于如下例子:
Java代码 复制代码

   1. package com.example.web;  
   2.  
   3. import com.opensymphony.xwork2.ActionSupport;  
   4. import org.apache.struts2.convention.annotation.Action;  
   5. import org.apache.struts2.convention.annotation.Actions;  
   6.  
   7. public class HelloAction extends ActionSupport {  
   8.   @Action("/another/url")  
   9.   public String method1() {  
  10.     return “error”;  
  11.    } 

package com.example.web;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;

public class HelloAction extends ActionSupport {
  @Action("/another/url")
  public String method1() {
    return “error”;
  }


我们调用method1方法可以通过两种方式:
1 /hello!method1.action 映射 url:/WEB-INF/content/hello-error.jsp
2 /another/url!method1.action 映射 url:/WEB-INF/content/another/url-error.jsp
可见,两种方式均可对method1方法进行调用,唯一的区别就是,两种调用的映射是不一样的,所以,想跳转到不同的界面,这是一个非常好的选择。


通过@Namespace 注释
Java代码 复制代码

   1. package com.example.web;  
   2.  
   3. import com.opensymphony.xwork2.ActionSupport;  
   4. import org.apache.struts2.convention.annotation.Action;  
   5. import org.apache.struts2.convention.annotation.Actions;  
   6. @Namespace("/other")  
   7. public class HelloWorld extends ActionSupport {  
   8.  
   9.   public String method1() {  
  10.     return “error”;  
  11.    }  
  12.     @Action("url")  
  13.   public String method2() {  
  14. return “error”;  
  15.    }  
  16.  
  17.     @Action("/different/url")  
  18.   public String method3() {  
  19. return “error”;  
  20.    }  
  21. } 

package com.example.web;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
@Namespace("/other")
public class HelloWorld extends ActionSupport {

  public String method1() {
    return “error”;
  }
@Action("url")
  public String method2() {
return “error”;
  }

@Action("/different/url")
  public String method3() {
return “error”;
  }
}



通过 /other/hello-world!method1.action 访问method1 方法。
通过 /other/url!method2.action 访问method2 方法
通过 /different /url!method3.action 访问method3 方法
与@Action 注释不同的是,该注释覆盖了默认的namespace(这里是’/’),此时再用hello!method1.action 已经不能访问method1 了.
@Results和@Result
1 全局的(global)。
全局results可以被action类中所有的action分享,这种results在action类上使用注解进行声明。
Java代码 复制代码

   1. package com.example.actions;  
   2.  
   3. import com.opensymphony.xwork2.ActionSupport;  
   4. import org.apache.struts2.convention.annotation.Action;  
   5. import org.apache.struts2.convention.annotation.Actions;  
   6. import org.apache.struts2.convention.annotation.Result;  
   7. import org.apache.struts2.convention.annotation.Results;  
   8.  
   9. @Results({  
  10.   @Result(name="failure", location="/WEB-INF/fail.jsp")  
  11. })  
  12. public class HelloWorld extends ActionSupport {  
  13.   public String method1() {  
  14.     return “failure”;  
  15.    }  
  16.     @Action("/different/url")  
  17.   public String method2() {  
  18.     return “failure”;  
  19.    }  
  20.  
  21. } 

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

@Results({
  @Result(name="failure", location="/WEB-INF/fail.jsp")
})
public class HelloWorld extends ActionSupport {
  public String method1() {
    return “failure”;
  }
@Action("/different/url")
  public String method2() {
    return “failure”;
  }

}


当我们访问 /hello -world !method1.action 时,返回 /WEB-INF/fail.jsp
当我们访问 /hello -world !method2.action 时,返回 /WEB-INF/fail.jsp
当我们访问 /different/url!method2.action 时,返回 /WEB-INF/fail.jsp

2 本地的(local)。
本地results只能在action方法上进行声明。
Java代码 复制代码

   1. package com.example.actions;  
   2.  
   3. import com.opensymphony.xwork2.ActionSupport;  
   4. import org.apache.struts2.convention.annotation.Action;  
   5. import org.apache.struts2.convention.annotation.Actions;  
   6. import org.apache.struts2.convention.annotation.Result;  
   7. import org.apache.struts2.convention.annotation.Results;  
   8.  
   9. public class HelloWorld extends ActionSupport {  
  10.     @Action(value="/other/bar",results={@Result(name = "error", location = "www.baidu.com",type="redirect")})  
  11.   public String method1() {  
  12.     return “error”;  
  13.    }  
  14. } 

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

public class HelloWorld extends ActionSupport {
@Action(value="/other/bar",results={@Result(name = "error", location = "www.baidu.com",type="redirect")})
  public String method1() {
    return “error”;
  }
}


当我们调用 /hello -world !method1.action 时,返回 /WEB-INF/content/hello-error.jsp
当我们调用 /other/bar!method1.action 时,返回 www.baidu.com
分享到:
评论

相关推荐

    Struts2-rest插件(有注释)

    以 Convention 插件为基础,Struts 2.1 又新增了 REST 插件,允许 Struts 2 应用对外提供 REST 服务。REST 插件也无需使用 XML 进行配置管理。Struts 2.1 通过 REST 插件完全可以提供让人和机器客户端共同使用的资源...

    struts2的rest风格插件实例

    由于 Struts 2 的 REST 插件还需要将提供 XML、JSON 格式的数据,因此还需要将 xstream-1.2.2.jar、json-lib-2.1.jar、ezmorph-1.0.3.jar 以及 Jakarta-Common 相关 JAR 包复制到 Web 应用的 WEB-INF/lib 路径下。...

    struts2.1.6 convertion,rest两插件的例子

    struts2.1.6 convertion插件(即注释方式配置)的hello...rest插件例子 默认调用 create()方法 struts2.1.6 vistor校验例子 都是我测试例子,写到一块了 有点乱 哪为高手给我说下 rest-plugin有什么好处 项目中有说明文件

    struts2-restDmo,struts2下的rest插件小例子

    直接放到tomcat目录就可以执行,详细的说明请到http://blog.csdn.net/baozhiyao234 看关于REST的说明,开发种经常用到

    Struts2请求转restful所需jar包

    Struts2请求转restful所需jar包 ezmorph-1.0.6.jar json-lib-2.3-jdk15.jar struts2-convention-plugin-2.3.14.jar struts2-rest-plugin-2.3.14.jar xstream-1.4.3.jar

    使用Struts2开发RESTful服务

    本文内容包括:REST简介资源和标识符操作资源的方式Struts2的REST支持RestActionMapper简介为Struts2应用安装REST插件实现支持REST的Action类实现视图层参考资料从V2.1开始,Struts2开始提供Convention插件,它允许...

    Struts2权威指南完整版

    改为使用Convention插件提供“零配置”,Struts 2.1新增了Portlet支持……为了让众多Struts学习者、工作者快速从Struts 2.0的开发升级到Struts 2.1,笔者升级了《Struts 2权威指南》,第二版改写了第一版中所有程序...

    RestFul整合struts所需包

    Struts 2 依然是一个 MVC 框架,...由于 Struts 2 提供了良好的可扩展性,因此允许通过 REST 插件将其扩展成支持 REST 的框架。REST 插件的核心是 RestActionMapper,它负责将 Rails 风格的 URL 转换为传统请求的 URL。

    struts2-json-plugin-2.3.8.jar

    struts插件,放入classpath中即用!

    CVE-2017-9805-POC.py s2-052.py 批量检测脚本

    Apache Struts2的REST插件存在远程代码执行的高危漏洞,其编号为CVE-2017-9805(S2-052)。Struts2 REST插件的XStream组件存在反序列化漏洞,使用XStream组件对XML格式的数据包进行反序列化操作时,未对数据内容进行...

    struts2.1.6+spring2.0+hibernate3.2常用配置包

    spring版本有2.0,2.5的,hibernate版本较多些至3.2,首先选版本就选择最优的,struts2没的选只有2.1.6版的,所以先导入struts2支持,然后是spring选的是2.0,问题就出在struts2中spring的插件上了,没有从MyEclipse...

    RESTful Java Web Services

    希望对学习Struts2 Rest插件的朋友有帮助

    struts2urlplugin:Struts2 插件支持 URL 中的模式匹配,用于动作映射器

    struts2url插件 Struts2 插件支持 URL 中的模式匹配以进行动作映射 Struts 2 的插件,允许开发人员控制 URL 如何映射到他们的操作: 使用正则表达式; 通过路径或命名空间将参数传递给动作; 控制允许的 HTTP ...

    struts2注解详细说明

    从struts2.1版本开始,Convention Plugin作为替换替换Codebehind Plugin来实现Struts2的零配置。• 包命名习惯来指定Action位置• 命名习惯制定结果(支持JSP,FreeMarker等)路径• 类名到URL的约定转换• 包名...

    Struts2权威指南

    相对于2007年发布的Struts 2.0,Struts 2.1改变较...而且新增了REST、Convention和Java Templates,这些都是Struts 2未来的发展方向。不仅如此,Struts 2.1不再支持Ajax主题,而是改为使用Dojo插件来提供对Ajax的支持。

    Struts 2.1 权威指南 part13 pdf

    改为使用Convention插件提供“零配置”,Struts 2.1新增了Portlet支持……为了让众多Struts学习者、工作者快速从Struts 2.0的开发升级到Struts 2.1,笔者升级了《Struts 2权威指南》,第二版改写了第一版中所有程序...

    Struts 2.1 权威指南 part04 pdf

    改为使用Convention插件提供“零配置”,Struts 2.1新增了Portlet支持……为了让众多Struts学习者、工作者快速从Struts 2.0的开发升级到Struts 2.1,笔者升级了《Struts 2权威指南》,第二版改写了第一版中所有程序...

Global site tag (gtag.js) - Google Analytics