08 00Struts 2.x基础

1 Struts 2.x简介

前提:Struts 1.x和Struts 2.x没有任何的关系,属于两个独立的版本。那么为什么会出现两个独立的版本呢?主要的原因还是在于Struts 1.x的设计结构上。在成名的MVC开发框架里面,Struts是最早出现的,但是由于最初的设计环境没有考虑到这么复杂,所以来讲到了今天与其它框架的整合会出现非常麻烦的问题,而且也存在有性能问题,同时自己本身的设计结构也出现了混乱,例如:如果要想使用验证框架(无用)那么必须使用第三方扩展包的形式,或者说如果要将对象与实例交由Spring管理,那么也需要以插件的形式出现。

这种种的一切几乎都在暗示着,Struts 1.x无法胜任于今天的开发,但是这个时候Apache也并没有开发Struts 2.x。任何事物的发展都是相生相克的。Struts 1.x发展带了框架的繁荣,那么繁荣之后,在某一个“角落”里面慢慢生长出一个新的框架——WebWork(ww),最早在整个Java行业里面有一个特别著名的开源项目——JIVE论坛,它融合了一个很有意思的特点——使用当前最新的开发技术,而WebWork就正好应用在此处,而后随着Struts 1.x的缺陷越来越明显,那么WebWork的优势越来越突出,长此以往下去,Struts 1.x已经丢失了大量的开发者,很多的开发者都转向了WebWork,它比Struts 1.x优秀在于以下几点:
(1)避免了Struts 1.x之中ActionForm类与Action类必须同时存在的窘境。
(2)在WebWork发展的时候考虑到了与第三方框架的整合,所以它可以轻松的实现与Spring的整合;
(3)WebWork的·配置更加的灵活;
(4)在验证框架部分要比Struts 1.x更加容易(这个也没什么用)。

在2005年的时候,Apache收购了WebWork项目,因为WebWork足够好用,但是没有Struts的名气大,而Struts 1.x本身问题很多,那么需要重新开发,所以Struts 1.x的名气+WebWork的实干=Struts 2.x,而在收购初期,Struts 2.x并没有对WebWork做更多的修改,但是千万要记住从2005年开始JDK 1.5出现了,而JDK 1.5出现所带来的行业内容的巨大的变革在于Annotation的技术应用,在Struts 2.x里面全面支持Annotation的配置以简化程序的编写。

Struts 2.x的基础是WebWork,但是请千万要记住一件事情,WebWork的起源也很早,只不过它凭借着自己超前的结构设计,才可以在今天继续发挥预热,但是Struts 1.x、Struts 2.x、JSF等等,现在随着时间的沉淀,会发现依然不如SpringMVC的设计更加优秀。

所有的框架设计思想只来源一点——反射机制,如果再有其它的核心技术也就是XML+DOM4J。

2 开发第一个Struts 2.x程序(搭建开发环境)

如果要想开发Struts 2.x的话最早的时候是没有开发工具的,从MyEclipse2012开始就支持了Struts 2.x开发,可以直接利用此工具自动进行Struts 2.x开发包的配置。
1、建立一个新的项目——MyStruts2Project
建立的时候一定要建立web.xml文件,因为几乎所有的MVC开发框架都必须有Servlet的支持或者是Filter支持,而Struts 1.x使用的是一个ActionServlet,在Struts 2.x里面使用的是一个Filter。所以考虑到配置问题必须生成web.xml。

2、框架的开发一定是一堆jar文件的配置过程
|——随后会询问你当前要使用的Struts 2.x版本。
|——Struts 1.x有一个特别著名的标志那么就是*.do,所以当Apache将WebWork项目吸收进来之后,那么也提供了多种映射路径,当然,我们现在认为最合适的路径应该使用的是*.action
|——随后将进入到开发包的配置界面;
|——那么此时Struts 2.x的开发环境就搭建完成了。

3 开发第一个Struts 2.x程序(编写ECHO程序)

1、项目建立完成之后会自动在src目录之中建立一个struts.xml文件,这个文件就是struts 2.x的核心配置文件。同时也会在web.xml文件中增加如下配置:

<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>*.action</url-pattern>
  </filter-mapping>

Struts 2.x的所有的处理操作都由过滤器完成的。
2、本次将完成一个ECHO程序,即:由用户在文本框中输入一段文字,而后在文字前增加“ECHO:”的信息返回到页面上进行显示。
范例:开发EchoAction
(1)所有的Struts 2.x应用程序都要求继承自ActionSupport父类。

package org.lks.action;

import com.opensymphony.xwork2.ActionSupport;

@SuppressWarnings("serial")
public class EchoAction extends ActionSupport {
	private String msg;

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}
	
	@Override
	public String execute() throws Exception {  //执行操作
		this.msg = "ECHO: " + this.msg;
		return "echo.page";  //返回路径的映射的key
	}
}

那么现在控制器就完成了。
3、所有的程序都一定要在struts.xml文件里面进行配置。
范例:修改struts.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
	<!-- 所有的Struts 2.x程序都要求设置命名空间 -->
	<package name="root" namespace="/" extends="struts-default"></package>
</struts>    

在此程序之中<package>主要定义页面的可执行路径,如果设置为/(namespace="/"),那么就表示映射到根路径下,但是在配置文件里面有一些是需要父配置文件给予的支持(例如:自动赋值、数据转换等),所以使用extends继承一个父的配置文件(extends=“struts-default”)。每一个<package>下具有多个<action>的设置。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
	<!-- 所有的Struts 2.x程序都要求设置命名空间 -->
	<package name="root" namespace="/" extends="struts-default">
		<!-- 配置程序所需要的Action的名称以及对应的类型 -->
		<action name="EchoAction" class="org.lks.action.EchoAction">
			<!-- 定义跳转路径 -->
			<result name="echo.page">echo.jsp</result>
		</action>
	</package>
</struts>    

那么此时Struts 2.x的Action配置完成。
4、在根路径下开发echo.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>Echo Action</title>
  </head>
  
  <body>
  	<h1><s:property value="msg"/></h1>
    <s:form action="EchoAction.action" method="post">
    	<s:textfield key="msg" label="请输入信息:"/>
    	<s:submit value="send"/>
    </s:form>
  </body>
</html>

此时的echo.jsp负责内容的输入以及内容的显示。
5、在所有标准的MVC设计模式之中,不应该通过路径直接访问一个jsp页面,所有的jsp页面必须经过Servlet进行跳转后显示,所以在这一点上Struts 2.x强烈奉行着这一特征,所以要想正常访问页面,必须要首先访问Action,而后通过Action跳转到一个jsp页面。

如果此时使用了Struts 2.x标签,那么里面的属性跳转后会与标签的相关组件进行自动匹配。但是从实际的开发来看,这样的自动匹配意义不大。

通过以上的开发可以发现,整个Struts 2.x与Struts 1.x相比优秀在以下部分:
(1)避免了ActionForm的强制定义问题;
(2)在配置文件之中定义简单;
(3)在处理过程之中,所有的请求是先提交到过滤器,而后根据struts.xml文件的配置进行相关Action的查找,调用execute()方法后再跳转回指定的jsp页面。整体的处理流程要比Struts 1.x容易;
(4)支持的标签更加简化,但是Struts 1.x的标签严格来讲不如Struts 1.x干净。

4 跳转类型

之前已经完成了一个最为基础的Struts 2.x程序,从整个结构来讲比较清晰比较简单,除了页面里面的代码有些陌生之外(不建议使用)。

在Struts 2.x里面如果要进行跳转的话,那么实际上在Action之中只要返回一个字符串,那么此字符串与配置的路径进行吻合后就可以跳转。

public String execute() throws Exception {  //执行操作
	this.msg = "ECHO: " + this.msg;
	return "echo.page";  //返回路径的映射的key
}
<result name="echo.page">echo.jsp</result>

所以默认使用的是服务器端跳转操作,这样才可以将页面中使用标签定义的文本框的名字与Action中的属性关联上。但是对于在Struts 2.x里面的跳转模式常用的有以下三种:
(1)type="dispatcher",默认的跳转形式,表示使用服务器端跳转;

<result name="echo.page" type="dispatcher">echo.jsp</result>

(2)type=redirect,采用客户端跳转的形式,跳转后的页面路径改变;

<result name="echo.page" type="redirect">echo.jsp</result>

(3)type=redirect-action,跳转到另外一个Action中。

<result name="echo.page" type="redirect-action">echo.jsp</result>

如果现在由一个Action要跳转到另外一个Action,例如:删除数据之后需要重新列表,所以删除操作是在一个Action要跳转到其他的Action进行数据的显示列表,这种情况下就使用redirect-action

5 过滤器

在整个Struts 2.x里面所有的请求处理都是由过滤器完成的,这一点和之前的Struts 1.x是完全不同的,但是现在的过滤器有一个问题,发现所有的页面必须经过Action而后跳转JSP页面后,JSP页面才可以正常显示,因为只要你使用了Struts 2.x标签,那么JSP页面就一定要与某一个Action关联在一起。

但是是不是必须要进行关联呢?并不是,如果想要解决关联紧密的问题,那么首先来观察当前使用的过滤器:

<filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>

首先必须要清楚,如果要进行Struts 2.x的开发,那么官方推荐使用的过滤器StrutsPrepareAndExecuteFilter,但是严格来讲在org.apache.struts2.dispatcher.ng.filter包中有三个可以使用的过滤器。
(1)StrutsPrepareAndExecuteFilter:指的是在页面执行之前以及页面执行的时候都要操作此过滤器;
(2)StrutsPrepareFilter:在页面执行之前(此时页面还没有生成)执行过滤器;
(3)StrutsExecuteFilter:在页面执行完毕(此时页面代码已经生成了)执行过滤器。
但是经过解释可以发现这些过滤器都必须要一个前提:依然要通过Action跳转到JSP页面才可以。只要你的JSP页面中存在有Struts 2.x标签,那么就不可避免的一定要与Action关联,那么如果说现在不想关联呢?那么可以更换一个过滤器:org.apache.struts2.dispatcher.FilterDispatcher
范例:使用其他过滤器

<filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
  <filter-name>struts2</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

此时,在直接访问具备有Struts 2.x标签的页面就不会出现错误了,但是这种做法现在在新的版本之中已经被禁止了。只能够使用StrutsPrepareAndExecuteFilter过滤器,那么是不是就不能够直接执行JSP呢?依然可以直接执行JSP,但是这个时候的JSP页面要求不能够出现任何与Struts 2.x有关的标签操作;
范例:定义一个msg.jsp页面

<%@ page language="java" pageEncoding="UTF-8"%>

<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<html>
<head>
	<base href="<%=basePath%>">

	<title>Struts 2.x</title>
</head>
  
<body>
	<form action="EchoAction.action" method="post">
		Info:<input type="text" name="msg" id="msg">
		<input type="submit" value="submit">
	</form>
</body>
</html>

如果此时参数名称不一致,那么将无法实现自动赋值,但是也不会像Struts 1.x那样出错。

如果在以后的Struts 2.x开发还是使用其它框架开发,都强烈不建议使用各个框架自己的标签,习惯的做法依然是由Action传递request属性到JSP页面,而JSP页面,而JSP页面使用JSTL输出。

6 跳转配置

在Struts 2.x里面所有的跳转都是由Action进行的,而且只要想进行跳转,只需要在相应的方法里面返回指定的字符串即可,而这些字符串都会在struts.xml文件里面出现相应的<result>节点。
范例:观察已有代码

public String execute() throws Exception {  //执行操作
	this.msg = "ECHO: " + this.msg;
	return "echo.page";  //返回路径的映射的key
}
<result name="echo.page" type="dispatcher">echo.jsp</result>

<result>节点里面保存的是要跳转的路径,但是对于这个路径一定要记住它是与包匹配的:
(1)包的定义:<package name="root" namespace="/" extends="struts-default">
|————namespace表示的是整个可以访问程序的公共路径,有了这个路径之后在此包中配置的一切页面,如果没有明确声明都是在此路径下的;
(2)result定义:<result name="echo.page" type="dispatcher">echo.jsp</result>
|————组合:namespace/result,所以最终的这个跳转路径:/echo.jsp
很多时候写这种相对路径可能有些人并不习惯,所以可以使用绝对路径:

<result name="echo.page" type="dispatcher">/pages/demo/echo.jsp</result>

但是对于跳转结果的返回内容,实际上在ActionSupport类里面也有几个常量支持,这几个常量都有其特殊的使用环境,首先来观察常量(都在com.opensymphony.xwork2.Action接口里定义):
(1)成功操作:public static final String SUCCESS,“success”;
(2)错误操作:public static final String ERROR,“error”;
(3)重新登录:public static final String LOGIN,“login”;
(4)服务器验证未通过:public static final String INPUT,“input”;
(5)不做任何操作:public static final String NONE,“none”;
通过代码结构可以发现ActionSupport是Action接口的子类,所以现在EchoAction的继承关系:

如果在开发过程之中也可以直接使用这些常量来作为跳转的key。

@Override
	public String execute() throws Exception {  //执行操作
		this.msg = "ECHO: " + this.msg;
		return Action.SUCCESS;  //返回路径的映射的key
	}
<result name="success">echo.jsp</result>

这些常量在自己的开发之中不是必须编写的,只是留给标准使用的。

7 取得JSP内置对象

写了Action之后发现,这个Struts 2.x的Action与Struts 1.x不同,Struts 1.x里面的方法至少都带有HttpServletRequest、HttpServletResponse这样的接口实例对象。

在Struts 2.x为了方便的解决这些内置对象的操作,专门提供有一个org.apache.struts2.ServletActionContext的类,这个类里面可以方便的取出常用内置对象:
(1)取得pageContext:public static javax.servlet.jsp.PageContext getPageContext()
(2)取得request:public static javax.servlet.http.HttpServletRequest getRequest()
(3)取得response:public static javax.servlet.http.HttpServletResponse getResponse()
(4)取得ServletContext:public static javax.servlet.ServletContext getServletContext()
HttpSession对象可以通过HttpServletRequest接口里面定义的getSession()方法取得。
范例:观察内置对象的信息取得

public String execute() throws Exception {  //执行操作
	HttpServletRequest request = ServletActionContext.getRequest();
	HttpServletResponse response = ServletActionContext.getResponse();
	ServletContext servletContext = ServletActionContext.getServletContext();
	HttpSession session = request.getSession();
	System.out.println(servletContext.getRealPath("/"));
	System.out.println(request.getContextPath());
	System.out.println(response.getContentType());
	System.out.println(session.getId());
	this.msg = "ECHO: " + this.msg;
	return "echo.page";  //返回路径的映射的key
}

日后如果要由Action传递request属性或者是在session中保存登录信息的话,这些操作就可以使用了。

8 多人开发

在所有的项目开发之中,一定是多人团队集中开发,但是现在如果使用框架开发会出现一个问题,所有的Action以及相关的路径都要求在struts.xml文件里面配置,如果所有的人都去修改一个文件,那么会很混乱,有可能会一直冲突,那么在Struts 2.x里面为了解决这样的问题,可以由用户定义多个配置文件,并且在一个struts.xml文件里面合并。

首先在项目里面可以复制struts.xml文件为struts-lks.xml。在这里面编写所需要的内容,但是随后需要在综合的struts.xml文件里面导入这些小的配置信息。

<include file="struts-lks.xml"></include>

如果你有多个文件,那么就重复编写<include>元素即可。

9 乱码解决

如果你现在项目之中的编码都统一使用的是UTF-8编码,那么根本就没有必要去解决乱码,如果你采用的是其他编码形式,例如:整个项目都使用的是“GBK”,那么就必须解决乱码。

如果要想进行乱码的解决实际上是进行的是Struts 2.x的属性配置,既然是属性配置,那么就要在src目录下建立一个struts.properties的文件。

范例:建立struts.properties文件

struts.i18n.encoding=UTF-8
struts.locale=zh_CN

只要写上了此配置,那么Struts就可以使用任意的编码进行处理了。

但是此时有一个问题千万要记住:Struts 2.x利用过滤器完成所有处理,所以在Struts 2.x里面要想再使用过滤器解决乱码,或者是解决用户登录操作,那么就不这么方便了。

10 配置资源文件

从任何的项目开发来讲,都一定会存在有*.properties文件,这个·文件的结构使用了key=value的形式,同时在这种文件上还可以轻松实现国际化操作的需要,所以只要是项目,都建议至少定义一个Messages.properties文件保存所有的提示信息。
范例:在src下定义Messages.properties文件(资源文件的命名同类名称命名标准一致)

info=Hello, {0}, {1}, {2}, {3}, {4}, {5}!

那么现在需要在struts.properties文件里面定义资源文件。
范例:修改struts.properties文件

struts.custom.i18n.resources=Messages,Pages

随后在Action里面进行资源文件的读取。
范例:读取资源文件

//未填充满输出:Hello, hello, world, {2}, {3}, {4}, {5}!
System.out.println(super.getText("info", new String[]{"hello", "world"}));

//填充满输出:Hello, hello, world, 黄海燕, 大, 大, 笨蛋!
System.out.println(super.getText("info", new String[]{"hello", "world","黄海燕", "大", "大", "笨蛋"}));

尽可能保证有多少的占位符,就填写多少个内容。
在整个Struts 2.x开发之中,以上形式的资源读取是最为常见的,但是在整个Struts 2.x规划的时候定义了三种级别的资源文件类型:
(1)第一种:全局资源文件类型,推荐的使用形式(需要在struts.properties中注册名称);
(2)第二种:包级别资源文件,只能够在一个包下使用(package.properties);
(3)第三种:指定Action的资源文件,为一个Action服务(名称与Action相同)。
范例:定义包级别的资源文件——org/lks/action/package.properties

info=Hello, {0}, {1}, {2}, {3}, {4}!

在读取资源文件的时候如果多个级别的资源文件都出现了,那么会按照由小到大的范围读取(先读取单个Action,如果没有再读取package.properties,如果还没有则读取全局资源)。
范例:定义EchoAction专门使用的资源文件——EchoAction.properties(EchoAction.java)

info=Hello, {0}, {1}!

虽然有三种资源文件的类型,但是如果真使用,别太麻烦了,还是定义全局资源文件,如果是多个,使用,分隔,但是请一定记住,在struts.properties之中定义全局资源文件的时候,不许加后缀,它默认找到*.properties文件。

11 结合VO输入

从2005开始,VO的开发模式已经深入人心,所有的开发框架必定都要支持VO转换,并且在Struts 2.x里面也支持了VO转换,并且可以多级设置。
范例:定义两个VO类同时设置关系

package org.lks.vo;

public class Employee {
	private Long eid;
	private String ename;
	private String ejob;
	private Department edepartment;

	public Employee() {
	}

	public Long getEid() {
		return eid;
	}

	public void setEid(Long eid) {
		this.eid = eid;
	}

	public String getEname() {
		return ename;
	}

	public void setEname(String ename) {
		this.ename = ename;
	}

	public String getEjob() {
		return ejob;
	}

	public void setEjob(String ejob) {
		this.ejob = ejob;
	}

	public Department getEdepartment() {
		return edepartment;
	}

	public void setEdepartment(Department edepartment) {
		this.edepartment = edepartment;
	}
}

package org.lks.vo;

public class Department {
	private Long did;
	private String dname;
	private String dlocation;
	
	public Department() {
	}

	public Long getDid() {
		return did;
	}

	public void setDid(Long did) {
		this.did = did;
	}

	public String getDname() {
		return dname;
	}

	public void setDname(String dname) {
		this.dname = dname;
	}

	public String getDlocation() {
		return dlocation;
	}

	public void setDlocation(String dlocation) {
		this.dlocation = dlocation;
	}
}

范例:建立一个EmployeeAction的程序类,在这个程序类里面准备好要接收的数据

package org.lks.action;

import org.lks.vo.Employee;

import com.opensymphony.xwork2.ActionSupport;

@SuppressWarnings("serial")
public class EmployeeAction extends ActionSupport {
	private Employee employee = new Employee();

	public EmployeeAction(){	
	}
	
	public Employee getEmployee() {
		return employee;
	}
	
	@Override
	public String execute() throws Exception {  //执行操作
		return "employee.insert";  //返回路径的映射的key
	}
}

范例:在struts.xml文件里面配置EmployeeAction

<action name="EmployeeAction" class="org.lks.action.EmployeeAction">
			<!-- 定义跳转路径 -->
			<result name="employee.insert">employee_insert.jsp</result>
		</action>

后面的任务就是要进行表单的编写,但是在编写表单的时候请要考虑到名称问题。
范例:定义employee_insert.jsp页面

<%@ page language="java" pageEncoding="UTF-8"%>

<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<html>
<head>
	<base href="<%=basePath%>">

	<title>Struts 2.x</title>
</head>
  
<body>
	<form action="EmployeeAction.action" method="post">
		雇员编号: <input type="text" name="employee.eid" value="31613131"><br>
		雇员姓名: <input type="text" name="employee.ename" value="hhy"><br>
		雇员职位: <input type="text" name="employee.ejob" value="软件设计师"><br>
		部门编号: <input type="text" name="employee.edepartment.did" value="1001"><br>
		部门名称: <input type="text" name="employee.edepartment.dname" value="软件部"><br>
		部门地址: <input type="text" name="employee.edepartment.dlocation" value="深圳"><br>
		<input type="submit" value="submit">
	</form>
</body>
</html>

你现在发现所有的操作都可以向指定的类型进行转换,这一点的确是很方便。这种转换严格来讲并不安全,因为它必须要求前台用户输入正确格式的数据才可以操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章