Struts2 概述
Struts2 是目前较为普及和成熟的基于MVC设计模式的web应用程序框架,它不仅仅是Struts1 的升级版本,更是一个全新的Struts架构。最初,是以WebWork框架和Struts框架为基础,通过提供增强和改进的Struts框架,进而实现简化web技术人员开发工作的目标。不久之后,Webwork框架和Struts社区联合创造了现在流行的Struts2框架。
Struts2 框架的优点
了解了这几个主要的优点,会促使你考虑使用Struts2 :
- POJO表单及POJO操作 - Struts2 去除掉了Struts框架中的Action Forms部分。在Struts2框架下,你可以用任何一POJO来接收表单输入,同样的,你可以把任一POJO视为一个Action类。
- 标签支持 - Struts2 改进了标签表单,而新的标签可让开发人员减少代码编写量。
- AJAX支持 - Struts2 被认可接收进Web 2.0技术,并创建了功能非常类似于标准的Struts2 标签的AJAX标签,把AJAX支持整合进其结果中。
- 易于整合 - Struts有多种整合方式可使用,现在与其他类型的框架,如Spring、Tiles、SiteMesh之类的,整合更为容易了。
- 模板支持 - 支持使用模板生成视图。
插件支持 - 有大量的插件可用于Struts2,而使用插件可以增强和扩大 - Struts2 核心行为。
性能分析 - Struts2 为调试和配置应用程序提供综合的性能分析,此外,Struts也以嵌入调试工具的形式提供集成调试。 - 易于修改标签 - 在Struts2 中,可使用Freemarker的模板对标签标记进行调整,而修改标签不需要JSP或是Java知识,基本的HTML、XML和CSS知识就足够了。
- 促进减少配置 - Struts2 使用各种设置的默认值促进减少配置,而你不需要再配置什么除非是偏离了Struts2 设定的默认设置。
- 视图技术 - Struts2 为多种视图选项(JSP、Freemarker、Velocity、XSLT等)提供支持。
以上是使 Struts2 成为准企业框架的十大优点。
Struts2 框架的缺点
尽管Struts2 有一大列的优点,但我们还是要提到关于它的一些仍需不断改进的缺点:
- 更大的学习曲线 - 使用Struts MVC,你必须要熟悉JSP、Servlet APIs标准以及一个大型、复杂的框架。
- 文档缺乏 - 相比于Servlet和JSP APIs标准,Struts的在线资源较少,许多初学者会发现Apache在线文档混乱并缺乏整理。
- 不够透明 - 相比于使用正常的基于Java的Web应用程序,使用Struts的应用程序有许多是进行在后台,这使得框架不易于理解。
最后说明一点,一个好的框架应该提供各种类型的应用程序都可以使用的通用行为,Struts2 是最好的Web框架之一,并频繁用于RIA(Rich Internet Applications)的发展。
安全漏洞
- 曝出高危安全漏洞
Struts2曝出2个高危安全漏洞,一个是使用缩写的导航参数前缀时的远程代码执行漏洞,另一个是使用缩写的重定向参数前缀时的开放式重定向漏洞。这些漏洞可使黑客取得网站服务器的“最高权限”,从而使企业服务器变成黑客手中的“肉鸡”。
- 解决措施
Apache Struts团队已发布了最新的Struts 2.3.15.1,修复了上述漏洞,建议采用Struts 2.0至Struts 2.3的网站开发者尽快升级至最新版。
- 带来影响
据乌云平台漏洞报告,淘宝、京东、腾讯等大型互联网厂商均受此影响,而且漏洞利用代码已经被强化,可直接通过浏览器的提交对服务器进行任意操作并获取敏感内容。Struts漏洞影响巨大,受影响站点以电商、银行、门户、政府居多,而且一些自动化、傻瓜化的利用工具开始出现,填入地址可直接执行服务器命令,读取数据甚至直接关机等操作。瑞星安全专家介绍,本次曝出的2个漏洞是由于缩写的导航和重定向前缀“action:”、 “redirect:”、 “redirectAction:”造成的。瑞星安全专家表示,由于这些参数前缀的内容没有被正确过滤,导致黑客可以通过漏洞执行命令,获取目标服务器的信息,并进一步取得服务器最高控制权。届时,被攻击网站的数据库将面临全面泄密的威胁,同时黑客还可以通过重定向漏洞的手段,对其他网民进行钓鱼攻击或挂马攻击。
default.properties
此文件一般在:struts-2.3.37\src\core\src\main\resources\org\apache\struts2下,此文件中定义了大部分常用的常量,例如:
### 规定编码策略
struts.i18n.encoding=UTF-8
### 这是通过ActionMapper映射所发挥的作用(上图),使用逗号分隔,可以是空字符串(这样可以直接用struts.xml中定义的action name或目录列表来匹配特定的action类),如下:
struts.action.extension=action,,
### 设置为true对开发者来说更加友好,它总是会重新加载国际化问题和struts.xml的配置信息(修改配置文件后不用重启服务器),并增加各种调试信息和问题来及时呈现这样的错误何时会发生。(相当于设置了struts.configuration.xml.reload=true,struts.i18n.reload=true)(可在struts.xml或struts.properties中设置为true来覆盖默认的false)
struts.devMode = false
### 设置标准的用户界面主题(配合标签库使用),simple是最简单的主题,只是最基本的html元素,xhtml是对此的扩展,提供了布局功能,label显示名称以及验证框架(下文介绍)和国际化框架的集成,局限:用表格进行布局,每一行只能放一个表单项
struts.ui.theme=xhtml
xhtml模板表单示例
<form id="login" name="login" action="/LoginDemo/login.action" method="post">
<table class="wwFormTable">
<tbody><tr errorfor="login_username">
<td align="center" valign="top" colspan="2"><span class="errorMessage">用户名不能为空!</span></td>
</tr>
<tr>
<td class="tdLabel"><label for="login_username" class="errorLabel">用户名:</label></td>
<td><input type="text" name="username" value="" id="login_username"></td>
</tr>
<tr>
<td class="tdLabel"><label for="login_password" class="label">密码:</label></td>
<td><input type="text" name="password" value="" id="login_password"></td>
</tr>
<tr>
<td colspan="2"><div align="right"><input type="submit" value="提交" id="login_submit" name="submit">
</div></td>
</tr>
</tbody></table></form>
验证框架
public String execute(){
ActionContext context = ActionContext.getContext();
if("zhl".equals(user.getUsername())&&"123".equals(user.getPassword())){
context.getSession().put("user", user);
return SUCCESS;
}else {
addActionError(getText("oneofthem.error"));
return INPUT;
}
}
//simple validation
public void validate(){
if("".equals(user.getUsername())){
addFieldError("username", getText("username.required"));
}else if("".equals(user.getPassword())) {
addFieldError("password", getText("password.required"));
}
}
如上,这个validate()方法写在Action里面其比默认执行的execute()优先执行,若验证通过(即执行了addFieldError),则直接返回到请求的原始页面,并给出相关提示(密码错误等,效果如上模板所示),password.required为设置的局部常量,在此Action所在的包下面xx.properties中,通过ActionSupport.getText(key)获得。
struts-default.xml
此文件一般在:struts-2.3.37\src\core\src\main\resources下,其中声明了一个name为struts-default的package,在声明struts.xml的action标签中默认extends继承这个包。在里面声明了许多bean,constant,result-type,interceptor(拦截器,struts2的核心)。
struts-plugin.xml
该文件出现在lib包中的以格式为struts2-Xxx-plugin-2.x.xx.jar包中,其中定义了一些常量。
struts.xml
默认都在classes目录下,其内容都是通过http://struts.apache.org/dtds/struts-2.3.dtd中定义的格式来声明的,dtd(document tag definition文档类型定义),顺便提一下XSD 简介 XML Schema Definition是基于 XML 的 DTD 替代者。
常量声明:
<constant name="struts.enable.DynamicMethodInvocation" value="true"/>
外部配置文件声明:
<include file="example.xml"/>
由于struts2默认加载WEB-INFO/classes/下的struts.xml文件,所以其它文件可以通过include包含在一个配置文件之中,可以提高配置文件的可读性。
声明结构(==意为同一级,->意为包含):constant==include==package->action或interceptors
下面这个说明了标签声明的顺序(由上至下),特别是设置包下的默认拦截器时必须放置在interceptors的后面,不然会报错。
(result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-
class-ref?, global-results?, global-exception-mappings?, action*)
例如:
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<constant name="struts.devMode" value="true" />
<constant name="struts.ui.theme" value="simple"/>
<package name="default" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="privilege" class="com.livejq.interceptor.PrivilegeInterceptor" />
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="privilege"/>
</interceptor-stack>
</interceptors>
<action name="login" class="com.livejq.action.LoginAction">
<result>
<param name="location">/main.jsp</param>
</result>
<result name="input">
<param name="location">/taglib.jsp</param>
</result>
</action>
<action name="blog_*" class="com.livejq.action.BlogAction" method="{1}">
<result>/success.jsp</result>
<result name="login" type="dispatcher">
<param name="location">/login.jsp</param>
</result>
<interceptor-ref name="myStack"/>
</action>
</package>
<!-- Add packages here -->
<include file="example.xml"/>
</struts>
内建拦截器和自定义拦截器
在struts-default.xml中以name-class的形式定义了许多拦截器,可在action中使用extends="struts-default"继承这些内建拦截器即可使用。
自定义的拦截器在struts.xml中的声明形式如下:
<interceptors>
<interceptor name="privilege" class="com.livejq.interceptor.PrivilegeInterceptor" />
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="privilege"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack"/>
struts2中的拦截器是AOP(Aspect-Oriented-Programming)面向切面编程的一种实现策略,是可插拔的,需要某个功能就加入某个拦截器;这些拦截器对应各个功能按一定顺序排列组合在一起就形成了拦截器链,而这些拦截器链所组成的集合就是拦截器栈,拦截器栈可嵌套,即拦截器栈中亦可出现拦截器栈。
由于struts2自己默认使用了defaultStack默认的拦截器栈,所以自己定义的拦截器会将其屏蔽,得手动将其加入自己定义的拦截器栈中一起作为自定义的拦截器。default-interceptor-ref的使用使得在一个包中的所有action共同使用一个自定义的拦截器栈。
实例说明:
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
public class PrivilegeInterceptor extends AbstractInterceptor{
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext context = invocation.getInvocationContext();
Object user = context.getSession().get("user");
if(user!=null){
return invocation.invoke();
}else{
context.put("msg", "无效的用户,请登录!");
return Action.LOGIN;
}
}
}
我们自定义的拦截器继承的AbstractInterceptor抽象类实现了Interceptor这个接口中的三个方法(init()、destroy()、String intercept(ActionInvocation invocation),方法体中什么也没做),实现自定义拦截器也可直接实现Interceptor这个接口中的所有方法(不过一般不这么做,因为我们只是想实现其中的String intercept(ActionInvocation invocation)方法,至于其余两个暂时还用不到,主要作用就是为自定义的拦截器初始化所需要用到的一些资源并在用完之后自动清理掉)。
重点:
再来看看这个intercept方法中,我们判断此为合法用户后通过ActionInvocation执行了invoke()方法。ActionInvocation代表着一种可执行的状态,亦可说是一系列可执行的请求(包括拦截器、Action和返回的结果视图),其通过不断地执行invoke()方法返回一个字符串给ActionProxy(上面有模拟视图),由代理来进行下一步的处理(按一定的顺序根据返回的字符串,是执行下一个拦截器呢(如果有的话),还是执行Action呢,还是直接给出结果视图)
使用通配符
<action name="login_*" class="com.livejq.action.LoginAction">
<result>{1}.jsp</result>
</action>
和
<action name="login_*" class="com.livejq.action.LoginAction" method=”{1}”>
<result>main.jsp</result>
</action>
效果相同。
之后可通过login_main返回一个main.jsp页面
action中也可以使用通配符:
<action name="*_*" class="com.livejq.action.{1}" method=”{2}”>
<result>main.jsp</result>
</action>
之后可直接通过以类名_方法名的形式来向action发送请求
struts.properties
默认与struts.xml放在一起,其中通过key=value键值对来定义常量。
web.xml
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>LoginDemo</display-name>
<welcome-file-list>
<welcome-file>taglib.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.i18n.encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
其中主要作用是声明一个struts2的核心过滤器StrutsPrepareAndExecuteFilter,亦可以定义常量,只不过其位置必须被
上文由上到下指定了struts2框架搜索struts2常量的顺序,需要注意的是,如果在多个常量配置文件中出现了相同的struts2常量,则后面的常量配置文件中的常量值往往会覆盖前面出现的常量值。
字段(属性)驱动与模型驱动
- 字段驱动(FieldDriven)
1.直接使用基本数据类型完成数据传递(适合进行少量属性的传值)
直接在Action中声明变量属性并设置getter和setter方法(前提:此属性名称需跟表单中的name中的值完全相同)
2.使用域对象完成数据传递(适合具有多个属性时)
在domain包中声明一个我们常说的bean对象,也就是一个存储数据的模型,然后也是在Action中声明一个bean对象,并设置其getter和setter方法(多个bean时也是如此)。在JSP表单中,若为取值,则必须为“对象.属性名”(如:User.username),若为传值,则可以“模型对象.属性名”(如:user.username)或直接是“属性名”。
- 模型驱动(ModelDriven)
使用模型驱动,则Action类必须实现接口ModelDriven中的getModel()方法,此方法返回一个域对象。
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.ModelDriven;
public class LoginAction extends ActionSupport implements ModelDriven<User> {
/**
*
*/
private static final long serialVersionUID = 1L;
User user = new User();
@Override
public User getModel() {
return user;
}
public String execute(){
return SUCCESS;
}
}
由上可得,模型驱动也是不适合传递多个domain对象的。
Action访问Servlet API三种方法
1. 通过ActionContext类访问
ActionContext context = ActionContext.getContext();
if("zhl".equals(user.getUsername())&&"123".equals(user.getPassword())){
context.getSession().put("user", user);
return SUCCESS;
}
context.put("user", user)相当于request.setAttribute(key,value)。
此方式通过构造一个中间对象Map映射表(类似于代理)很好的与Servlet中的映射对接,从而可以很好地与Servlet解耦,方便了后续的模块单元测试。
2.通过特点接口访问
通过ActionContext可以访问Servlet API,但无法获得实例。So Struts2提供了一系列接口:
ServletRequestAware
ServletResponseAware
SessionAware
ServletContextAware
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.interceptor.ServletRequestAware;
public class LoginAction extends ActionSupport implements ServletRequestAware{
/**
*
*/
private static final long serialVersionUID = 1L;
HttpServletRequest request;
@Override
public void setServletRequest(HttpServletRequest request) {
// TODO Auto-generated method stub
this.request = request;
}
之后就可以用request.setAttribute(key,value)了。
3.通过ServletActionContext类访问
ServletActionContext类有提供静态方法直接返回一个Servlet API,具体如下:
static HttpServletRequest getRequest()
static HttpServletResponse getResponse()
static ServletContext getServletContext()
static PageContext getPageContext()
评论区