好了,今天开始第二章,O(∩_∩)O哈哈~,今天这一章主要讲Struts 2的运行环境和原理,及工作流程,后面长长的文章就当是看API把,哈哈。
第一部分 Struts 2框架开发入门
2.1.1 MVC基本思想
MVC把应用程序分成3大基本模块:模型(Model,即M)、视图(View,即V)和控制器(Controller,即C),它们(三者联合即MVC)分别担当不同的任务。下图显示了这几个模块各自的职能及相互关系。
传统的Java EE开发采用JSP+Servlet+JavaBean的方式来实现MVC(如【实例1.1】),但它有一个缺陷:程序员在编写程序时必须继承HttpServlet类、覆盖doGet()和doPost()方法,严格遵守Servlet代码规范编写程序,形如:
有些代码跟程序无关,但还是必须要程序员实现接口(比如Servlet的API,这就很烦了,其实这些代码每个Servlet都要写,还没实际逻辑),为了屏蔽这种不必要的复杂性,减少工作量,可以用Struts 2框架,哈哈.
用Struts 2实现的MVC系统与传统的用Servlet编写的MVC系统相比,两者在结构上的区别如下图所示。
可见,其区别是:用Struts 2框架代替Servlet的部分作为控制器,业务处理则由用户自定义的Action去实现,与Struts2的控制核心相分离,降低了系统中各部分组件的耦合度,和编程难度.
2.1.3 简单Struts 2开发
其实之前我们有个简单的Servlet开发的项目,后续也会用到,我会把这个项目传到gitee上.
1.加载Struts 2包
登录http://struts.apache.org/,下载Struts 2,本书使用的是Struts 2.5.13,其官方下载页面如图:将下载的文件struts-2.3.16.3-all.zip解压缩,得到文件夹包含的目录结构如图2.3所示,这是一个典型的Web结构。
大部分的时候,使用Struts 2的Java EE应用并不需要用到Struts 2的全部特性,开发Struts 2程序只需用到lib下的9个jar包,包括:
(1)传统Struts 2的5个基本类库。
struts2-core-2.5.13.jar
ognl-3.1.15.jar
log4j-api-2.8.2.jar
freemarker-2.3.23.jar
(2)附加的4个库。
commons-io-2.5.jar
commons-lang3-3.6.jar
javassist-3.20.0-GA.jar
commons-fileupload-1.3.3.jar
将它们一起复制到项目的WebRootWEB-INFlib路径下,右击项目名,从弹出菜单中选择“Refresh”按钮刷新即可,加载成功的项目工程目录树如图2.4所示。
其中,主要类描述如下。
struts2-core-2.5.13.jar:Struts 2.5的主框架类库。
ognl-3.1.15.jar:OGNL表达式语言。
log4j-api-2.8.2.jar:管理程序运行日志的API接口。
freemarker-2.3.23.jar:所有的UI标记模板。
2.2.1 Struts 2工作原理
长话短说:
(1)客户端提交一个(HttpServletRequest)请求。
(2)请求被提交到一系列(主要是3层)的过滤器(Filter),如(ActionContextCleanUp、其他过滤器、FilterDispatcher)。
(3)FilterDispatcher接收到请求后,询问ActionMapper是否需要调用某个Action来处理这个(HttpServletRequest)请求,如果ActionMapper决定需要调用某个Action,FilterDispatcher则把请求的处理交给ActionProxy。
(4)ActionProxy通过Configuration Manager(struts.xml)询问框架的配置文件,找到需要调用的Action类(该Action类一般是程序员自定义的处理请求的类)。
(5)ActionProxy创建一个ActionInvocation实例,同时ActionInvocation通过代理模式调用Action。但在调用之前,ActionInvocation会根据配置加载Action相关的所有Interceptor(拦截器)。
(6)一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果Result。
这是官方文档总结下来的,初学者看了肯定很头疼啊,那么下面就结合我们上面的实例来总结一个容易理解的Struts 2开发的项目的运行流程!
总结其运行流程如下:
(1)浏览器请求:“http://localhost:8080/bookManage/login.jsp”,发送到web应用服务器(如Tomcat等)。
(2)容器接收到了login.action的请求,根据web.xml中的配置,服务器将包含.action后缀的请求转到"org.apache.struts2.dispatcher.FilterDispatcher“类进行处理。
(3)框架在struts.xml配置文件中查找名为"login”的action对应的类。框架初始化该Action(就是对数据进行了封装,并数据放入栈中),并且执行该Aciton类的execute方法(默认的,可以设置),该方法可以执行一些数据处理等操作,然后返回结果(广义上的结果)。
(4)框架检查配置以查看返回成功时对应的页面,框架告诉容器来获得请求返回的结果页面main.jsp。
在Struts 2框架中,Action类的调用是通过代理类ActionProxy来完成的,代理类再创建一个ActionInvocation对象,来调用程序猿自定义的Action类,但在调用之前会先加载有关的Interceptor(拦截器),而Struts 2框架就是通过一系列拦截器来工作的!
2.3 简单Struts 2开发Dome
理论讲的再好,不如上手个实际项目,通过一个Deme让我们了解下Struts2的流程控制,可能有些地方不懂,看看代码就行,后面教程会详细讲解的!
2.3.2. 配置web.xml
Struts 2框架需要在项目web.xml文件中配置,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<filter>
<filter-name>struts-prepare</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StruTSPrepareFilter</filter-class>
</filter>
<filter>
<filter-name>struts-execute</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-prepare</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>struts-execute</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<display-name>bookManage</display-name>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
代码加粗部分即是Struts2.5特有的。
2.3.3. 实现控制器Action
基于Struts 2框架的Java EE应用程序使用自定义的Action(控制器)来处理深层业务逻辑,完成用户想要完成的功能。本例定义名为“login”的控制器,判断登录用户名和密码的正确性。在项目src下建立包org.action,在包里创建LoginAction类,LoginAction.java代码所示:
package org.action;
import java.util.*;
import org.model.*;
import org.dao.*;
import com.opensymphony.xwork2.*;
public class LoginAction extends ActionSupport{
private Login login;
//处理用户请求的 execute 方法
public String execute() throws Exception{
//该类为项目与数据的接口(DAO接口),用于处理数据与数据库表的一些操作
LoginDao loginDao = new LoginDao();
Login l = loginDao.checkLogin(login.getName(), login.getPassword());
if(l!=null){ //如果登录成功
//获得会话,用来保存当前登录用户的信息
Map session = ActionContext.getContext().getSession();
session.put("login", l); //把获取的对象保存在 Session 中
return SUCCESS;//验证成功返回字符串SUCCESS(此时 Session 中已有用户对象)
}else{
return ERROR; //验证失败返回字符串ERROR
}
}
//属性 login 的 get/set 方法,记住这里
public Login getLogin() {
return login;
}
public void setLogin(Login login) {
this.login = login;
}
}
2.3.4. 配置struts.xml
在编写好Action(控制器)的代码之后,还需要进行配置才能让Struts 2识别Action。在src下创建文件struts.xml(注意文件位置和大小写),输入如下的配置代码:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="default" extends="struts-default">
<!-- 用户登录 -->
<action name="login" class="org.action.LoginAction">
<result name="success">/main.jsp</result>
<result name="error">/error.jsp</result>
</action>
</package>
<constant name="struts.i18n.encoding" value="gb2312"></constant>
</struts>
struts.xml一定不要弄错地方哦:
2.3.5. 编写JSP
本例login.jsp(登录页)、main.jsp(欢迎主页)这两个JSP文件均使用Struts 2的标签进行了重新改写。登录页login.jsp,代码为:
<%@ page language="java" pageEncoding="gb2312"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>图书管理系统</title>
</head>
<body bgcolor="#71CABF">
<s:form action="login" method="post" theme="simple">
<table>
<caption>用户登录</caption>
<tr>
<td>登录名<s:textfield name="login.name" size="20"/></td>
</tr>
<tr>
<td>密 码<s:password name="login.password" size="21"/></td>
</tr>
<tr>
<td>
<s:submit value="登录"/>
<s:reset value="重置"/>
</td>
</tr>
</table>
</s:form>
</body>
</html>
欢迎主页main.jsp,代码如下:
<%@ page language="java" pageEncoding="gb2312"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>欢迎使用</title>
</head>
<body>
<s:set name="login" value="#session['login']"/>
<s:property value="#login.name"/>,您好!欢迎使用图书管理系统。
</body>
</html>
好了,这里我们运行,效果图还是和上一篇文章的一样,这个Demo只是让我们认识一下Struts2的工作流程,下面我们会详细讲解Struts2的配置,也是对这个Demo的问题释疑!因为下面要写的东西很多(虽然很枯燥,但是要好好看哦,下一章很重要的),这样也让大家对Struts2更加了解!
第二部分 Struts 2基础
2.3 Struts2的控制器Action类
2.3.1 使用ActionSupport
Struts2的核心功能就是Action。对于开发人员来说,使用Struts2框架,主要编码工作就是编写Action类。一般情况下我们的Action类都会继承这个ActionSupport类,ActionSupport类实现了5个接口:
1.Action接口 实现了5个常量及一个execute()方法,如下:
如果Action类继承了ActionSupport类,就可以直接应用这几个常量,比如在【我们前面的实例】中的Action代码:
2.Validateable接口:该接口提供了一个validate()方法用于校验表单数据,在实际应用中只要在Action类中重写该方法即可,该方法是在执行execute()方法之前执行的。
3.ValidationAware接口:该接口定义了一些方法用来对Action执行过程中产生的信息进行处理。例如,该接口中提供了addFieldError(String fieldname,String errorMessage)方法用来在验证出错时保存错误信息。
4.TextProvider接口:该接口中提供了一系列getText()方法可以用key来获得相应的value值,用于获得对应的国际化信息资源。
5.LocaleProvider接口:该接口提供了一个getLocale()方法,用于国际化时获得语言/地区信息。
2.3.2 使用Action传值
Action可以通过其属性获取页面上表单文本框中用户输入的值,在运行login.jsp时候,Struts2框架会根据页面的文本框名(代码:<s:textfield name="name" size="20"/>)在Action类中寻找其set方法(setName)来进行赋值,这叫字段传值代码如下:
但是如果字段比较多的话,就需要在Action中写很多属性,增加了代码的耦合性,非常麻烦也,这个时候可以用模型传值的方法,要把字段封装成一个类,并生成其get和set方法,就是通常说的JavaBean了。因【实例】的项目中已编写好了JavaBean类Login.java,须在Action中改变写法:
而且在前端页面中也要改变用法,改为“模型对象名.属性名”:<s:textfield name="login.name" size="20"/>
注意:在实际项目开发中,当项目的“.java文件-需重新编译”或者一些配置文件如 “.xml文件-需框架重新读”经过修改后,一定要重启Tomcat服务器才会生效,而前端页面“login.jsp”则刷新页面即可。
2.3.3 Action访问ServletAPI
Struts2中没有与任何的ServletAPI相关联,这样大大降低了程序的耦合性,但是有时候我们的程序必须要用这些:如“request、response、application、session”等,为此,Struts2提供了两种方法访问ServletAPI:
1.通过ActionContext
ActionContext类提供了一个静态的getContext()方法来获得ActionContext对象,然后根据其对象来获得一些Servlet API的对象。例如:
由于“request”和“response”比较特殊,也是在开发中经常会用到的,所以Struts 2提供了专门的类来获取,即“ServletActionContext”:
2.通过实现*Aware接口
例如,要获得Application对象,Action类就可以编写如下:
2.3.3 Action返回结果
在一个Action类中,有时会返回多个结果,如判断一件事情,如果为真就返回SUCCESS,否则返回ERROR(或"error")。在【实例】中,对DAO接口执行checkLogin()方法获取到Login对象l的值进行判断,然后返回不同结果:
这里判断l对象不为空(数据库中有这个用户信息)就返回成功,然后根据配置文件的返回跳转到欢迎页面,如果l为空则返回出错页面,所以还要在struts.xml文件中配置两种不同的返回结果跳转到的页面,如下:
2.3.3 在Action中定义多方法
如果程序中功能越来越多,那就要定义越来越多的Action类,所以一般不采取这样的方式,而是把相关的功能定义在同一个Action类中,用多个方法来实现不同的功能。例如,在LoginAction类中定义两个方法:
2.4 解密Struts 2框架的程序文件
2.4.1 web.xml文件
所有过滤器必须实现java.Serlvet.Filter接口,这个接口中含有3个过滤器类必须实现的方法:
init(FilterConfig):这是Servlet过滤器的初始化方法。
doFilter(ServletRequest,ServletResponse,FilterChain):这个方法完成实际的过滤操作。FilterChain参数用于访问过滤器链上的下一个过滤器。
destroy():Servlet容器在销毁过滤器实例前调用该方法。
过滤器类编写完成后,必须在web.xml中进行配置,格式如下:
过滤器的关联方式有3种:与一个URL关联、与一个URL目录下的所有资源关联、与一个Servlet关联。
2.4.2 struts.xml文件
Struts 2.5.13版本下的struts.xml的大体格式如下:
几个常用的子标签:
include:用于导入其他xml配置文件。
constant:配置一些常量信息。
package:配置包信息。
bean:由容器创建并注入的组件。
2.4.3 struts.properties文件
该文件用于用户单独需要更改的默认设置之类的,放在src/下,Struts框架会默认加载该文件,一些常用配置:
2.5 Struts 2配置详解
一个简单的struts.xml文件解析图:
2.5.1 <action>配置详解
name:该属性是必需的,对应请求的Action的名称
class:该属性不是必需的,指明处理类的具体路径,如“org.action.LoginAction”。
method:该属性不是必需的,若Action类中有不同的方法,该属性指定请求对应应用哪个方法。
converter:该属性不是必需的,指定Action使用的类型转换器(类型转换内容会在类型转换部分讲解)。
在一般情况下,都会为<action>设置name和class属性,如果没有设置method属性,系统会默认调用Action类中的execute方法。若在Action中存在多个方法,请求其某个方法的时候就要通过这里的method属性来指定所请求的方法名。例如在2.3.1节中的第5点的Action类中有execute和regist两个方法,如果要在请求中应用regist方法,就要在相应的action中配置method属性:
2.在<action>中应用通配符
这个*的意思就相当于定义变量,{1}就是使用变量,form中的action传递的参数就给了*。所以传入regist就是*为regist,而{1}就是使用了*,所以{1}也为regist。不仅方法可以使用通配符这样匹配,返回的值也可以。例如,如果应用regist方法返回“error”时就跳转到“regist.jsp”界面。<action>配置修改为:
使用通配符可以很大程度地减少struts.xml的配置内容,但是可以发现,在编写时也会对Action类中的方法命名有限制,必须和请求名称对应,返回视图的名称也同样要对应。比如上面例子的意思就是:传入regist.action的请求,则会交给org.action.LoginAction类中的regist方法来处理,如果返回error,则进入regist.jsp页面。
3.访问Action类中方法的其他方式
仍以前面的LoginAction为例,<action>配置可以用正常情况,只需配置name和class(会有默认对应的方法execute):
这样配置完全不知道要访问LoginAction类中的哪个方法,但是可以在请求中指明,请求的form表单要改为:
其中,“login!regist.action”中“!”前面的“login”对应<action>中name属性值,“!”后面的“regist”对应要使用的LoginAction类中的方法名。
该方法是在请求中指定应用Action类中的哪个方法,还有一种办法是在提交按钮中设置的,<action>不用做任何改变,不过提交按钮需要用Struts 2的标签来实现,并且指定method:
4.使用默认类
<action>中如果未指明class属性,则系统将会自动引用<default-class-ref>标签中所指定的类,即默认类。在Struts 2中,系统默认类为ActionSupport,当然也可以自己定义默认类,例如:
若<action>中指定了自己的class属性,则上面定义的默认类在该<action>中将不起作用。
2.5.2 <result>配置详解
<result>是为Action类的返回值指定跳转方向的,在Struts 2框架中,一个完整的<result>配置为:
<result>包含两个属性name和type。name属性与Action类中返回的值进行匹配,type属性指定了将要跳转的结果类型,在实际应用中不一定都要跳转到一个页面,有可能会从一个action跳转到另一个action,这时就要指定type属性。<param>是为返回结果设置参数的。
Struts 2中支持多种结果类型如下:
1.dispatcher类型--转发:属于同一请求,可以传递参数,浏览器地址栏不变化。
定义该类型时,物理视图为JSP页面,并且该JSP页面必须和请求信息处于同一个Web应用中。还有一点值得注意的是,请求转发时地址栏不会改变,也就是说,属于同一请求,所以请求参数及请求属性等数据不会丢失,该跳转类似于JSP中的“forward”。从前面的例子中也可以看出,跳转到“main.jsp”页面后,仍可以取出“name”的值。在应用该类型时,一般都会省略不写。配置该类型后,<result>可以指定以下两个参数:
location:指定请求处理完成后跳转的地址,如“/main.jsp”。
parse:指定是否允许在location参数值中使用表达式,如“/main.jsp?name=${name}”,在实际运行时,这个结果信息会替换为用户输入的“name”值,该参数默认值是true。
2.redirect类型--重定向:不同请求,不可传参数,浏览器地址会发生变化。
该结果类型可以重定向到JSP页面,也可以重定向到另一个Action。该类型是与dispatcher类型相对的,当Action处理用户请求结束后,将重新生成一个请求,转入另一个界面。例如,在【实例】中,当用默认值“dispatcher”时,请求完成,转向“main.jsp”界面,如图所示。
可以发现,请求没变,还是“login.action”,但页面已经跳转到“main.jsp”,并且可以取出“name”的值--注意这里仍是解释的dispatcher类型。如果把<result>中的内容改为:
<result name="success" type="redirect">main.jsp</result>
则请求完成,重定向到main.jsp欢迎主页,如图2.8所示,可以看出URL已变为“main.jsp”。
配置redirect类型后,<result>也可指定location和parse两个参数。
3.redirectAction类型:也是重定向,多用于重定向到一个新的Action。
actionName:该参数指定重定向的action名。
namespace:该参数指定需要重定向的action所在的命名空间(命名空间会在后面讲解)。
此段代码释义:有regist.action请求,由org.action.LoginAction的regist的方法处理,若返回值为success,则重定向到/test2/login.action,然后。。。。
4.chain类型
前面的redirect及redirectAction虽然都可以重定向到另外的action,但是它们都不能实现数据的传递,在重定向过程中,请求属性等都会丢失,这样有的时候就不利于编程了。chain可以跳转到另外的action而且数据不丢失,通过设置chain类型,可以组成一条action链,action跳转可以共享数据的原理是处于同一个action链的action都共享同一个值栈,每个action执行完毕后都会把数据压入值栈,如果需要就直接到值栈中取。
5.全局结果
假如都返回到同一页面,而且在不同的action请求中都会用到,那么配置局部结果就显得冗余了。所以,Struts 2提供了全局结果的配置,例如,如果返回“error”,都跳转到错误页面:
2.5.3 <package>配置详解
1.可配置的属性
(1)name属性:该属性必须指定,代表包的名称,由于struts.xml中可以定义不同的<package>,而且它们之间还可以互相引用,所以必须指定名称。
(2)extends属性:该属性是可选的,表示当前定义的包继承其他的包,继承了其他包,就可以继承其他包中的action、拦截器等。由于包信息的获取是按照配置文件中的先后顺序进行的,所以父包必须在子包之前被定义。
(3)namespace属性:该属性是可选的,用来指定一个命名空间,如在前面讲redirectAction类型时已经用到了,定义命名空间非常简单,只要指定“namespace="/*"”即可,其中“*”是我们自定的,如果直接指定“"/"”,表示设置命名空间为根命名空间。如果不指定任何namespace,则使用默认的命名空间,默认的命名空间为“" "”,也就是空。当指定了命名空间后,相应的请求也要改变,例如:
而且请求就不能是“login”,而必须改为“user/login”。当Struts 2接收到请求后,会将请求信息解析为namespace名和action名两部分,然后根据namespace名在struts.xml中查找指定命名空间的包,并且在该包中寻找与action名相同的配置,如果没有找到,就到默认的命名空间中寻找与action名称相同的配置,如果再没找到,就给出错误信息。
(4)abstract属性:该属性是可选的,如果定义该包是一个抽象包,则该包不能包含<action>配置信息,但可以被继承。
2.可配置的标签
<action>:action标签,其作用前面已经详细讲解。
<default-action-ref>:配置默认action,如果配置了默认action,则若请求的action名在包中找不到与之匹配的名称就会应用默认action。
<default-interceptor-ref>:配置默认拦截器。
<default-class-ref>:配置默认类。
<global-exception-mappings>:配置发生异常时对应的视图信息,为全局信息,与之对应还有局部异常配置,局部异常配置要配置在<action>标签中,局部异常配置用<exception-mapping>进行配置:
<exception-mapping>中可以指定3个属性,分别为name:可选属性,用来标识该异常配置信息;result:该属性必须指定,指定发生异常时显示的视图信息,必须配置为逻辑视图;exception:该属性必须指定,用来指定异常类型。
下一章讲的就是Struts2的标签库了,稍微有意思了那么一丢丢,哈哈,别急,慢慢学,学的东西多了,才有能力做好玩的程序嘛,n(*≧▽≦*)n,加油↖(^ω^)↗,共勉。
写文不易,点个赞⑧。
本系列所有教程均上传至码云,链接如下:
https://gitee.com/jahero/bookManage