Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。
struts2的流程
1.客户端(HttpServletRequest)发送请求
2.请求经过过滤器(ActionContextCleanUp–>StrutsPrepareAndExecuteFilter(核心过滤器))
3.核心过滤器将请求转发给ActionMapper,ActionMapper负责识别当前请求是否需要struts2做出处理,如果决定调用某个Action,则告诉核心过滤器需要处理这个请求,返回一个对象来描述请求对应的ActionInvocation的信息
4.当决定调用某个Action后,请求又会通过核心过滤器,核心过滤器把请求的处理交给ActionProxy。
ActionProxy位于Action和xwork之间,会代理Action的运行过程
5.ActionProxy对象被创建出来时,手里只有从核心过滤器中拿到的url,并不知道要调用哪个Action,然后它会通过ConfigurationManager询问配置文件(struts.xml),找到要调用的Action类。
ConfigurationManager是xwork配置的管理中心,负责读取并管理struts.xml,在服务器启动的时候,ConfigurationManager会一次性的把struts.xml中的所有信息都到内存里并缓存起来,以方便后面直接匹配
6.ActionProxy拿到需要运行的Action、相关拦截器以及所有可能使用的
Result信息,就可以创建一个ActionInvocation的实例。ActionInvocation对象描述了Action运行的整个过程
7.经过拦截器(interceptor),通过ActionInvocation的实例调用真正的Action
8.Action执行完毕,ActionInvocation创建Result并在struts.xml中寻找对应的页面,ActionInvocation对象在按照拦截器的引用顺序的倒序依次执行拦截器的后置部分
9.ActionInvocation对象执行完毕后,实际就已经得到响应对象了,即HttpServletResponse对象,最后按照过滤器定义箱单的顺序依次经过过滤器,向用户展示相应的结果
web.xml配置
web.xml主要是来配置过滤器的,下面来看一下过滤器的配置
<filter>
<filter-name>strutsFilter</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>strutsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意<Filter-class>中只能这么写,这是核心过滤器,按ctrl,然后鼠标移到这个地址上一点就可以跳转到另一个页面说明地址写的是对的,<filter-name>可以自己命名,<url-pattern>也可以自己指定要拦截的路径
Action
在struts 2中,action是其核心功能,使用struts 2框架,主要的开发都是围绕action进行的,我们编写的Action类可以是一个普通的Java类也可以继承ActionSupport类,该类位于com.opensymphony.xwork2下,其实现了Action接口的execute方法
用户的每次请求都会转到一个相应的Action类里面,Action里面封装了业务数据和业务逻辑,下面看一个简单的Action类
public class UserAction extends ActionSupport{
//业务数据
private String uaername;//用户名
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
//写业务逻辑
//这个地方也可以自己写别的方法,只要有返回值与result对应就行了
@Override
public String execute() throws Exception{
return SUCCESS;
}
}
上面这个Action类的属性和属性对应的getter/setter方法,就是用来接收用户请求的数据,并把这些数据封装在Action类中,在后续处理中可以访问这些数据。
struts.xml
再来看看Action的配置,Action的配置要在struts.xml中完成,这样才能正确运行,struts.xml一般建在src包下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
这是struts.xml中的头部分
我们的Action要配置在<package>标签中
<struts>
<!--
package标签要写在<struts>标签中,package元素有如下属性:
name:就是包的名称,必须配置,而且不能重名
extends:继承的父包名称,如果没有就是struts-default,这个包是struts2中的包,里面有默认的拦截器
namespace:包的命名空间。如果没有,则默认使用空字符串,namespace的命名会影响url地址,如果命名空间是/my,则访问的地址就是http://localhost:8080/项目名/my/xx.action
-->
<package name="" extends="struts-dafault" namespace="/">
<!--
action元素有如下属性:
name:action的名字,用于匹配url,如果name是test,则访问的url后面就是test.action
class:Action实现类的完整类名(包括包名)
method:执行Action类时调用的方法名,注意这个方法必须要有返回值才能得到对应的Result
可以写多个action来对应调用不同的方法
-->
<action name="userAction" class="" method="execute">
<!-- result的name属性对应的就是action标签中方法所返回的值,对应上了,则user,jsp这个页面会被打开-->
<result name="success">/user.jsp</result>
</action>
</package>
</struts>
默认的action
如果请求一个不存在的action,那么默认的action会被执行。默认的action使用<default-action-ref>来声明
<package>
<!--
<default-action-ref>这个标配要写在<action>标签的上面,并且它的名字要与对应的action的名字一样,才能显示相应的页面,每个包中只有一个默认的action
-->
<default-action-ref name="errors" />
<action name="errors">
<result>/error.jsp</result>
</action>
</package>
在Struts2中可以将一个配置文件分解成多个配置文件,那么我们必须在struts.xml中包含其他配置文件。
<struts>
<include file="struts-default.xml"/>
<include file="struts-user.xml"/>
......
</struts>
获取数据
再回来看一下Action类,在Struts中,页面的数据和Action有三种对应方式:
1.基本类型的属性对应
web页面上要提交的空间的name属性,和Action类的属性对应
html:
<!-- form中的xx要与action标签的name对应 -->
<form action="xx.action" method="post">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit" value="提交">
</form>
Action类:
public class UserAction extends ActionSupport{
//业务数据
private String uaername;//用户名
private String password;//密码
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//写业务逻辑
@Override
public String execute() throws Exception{
System.out.println(username);
return SUCCESS;
}
}
当页面提交时,Struts2会自动从request中把对象取出来,然后按照名称进行对应(这个对应是由OGNL来表达),自动设置到Action的属性中去(这个功能由拦截器实现的),Action中的private属性通过getter/setter方法让外部访问
2.直接使用域对象
如果页面有很多属性,就会有很多数据要传进来,那么基本类型的属性对应Action类中就会有很多代码要写,我们可以把这些属性封装到一个bean中然后在Action类中使用这个bean就可以了
先写一个bean,bean中的参数和表单中相对应:
public class UserInfo {
private String uaername;//用户名
private String password;//密码
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
改一下Action这个类,就是定义一个属性是这个对象类型,然后添加相应的getter/setter方法即可,在这里不需要通过new关键字为user属性创建对象进行实例化。当然也可以直接把这个属性的可访问属性设置成public,这样就不需要写getter/setter方法了。
public class UserAction extends ActionSupport{
private UserInfo user;//使用的是UserInfo的model,是和表单对应的
public UserInfo getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
//写业务逻辑
@Override
public String execute() throws Exception{
System.out.println(user.getUsername);
return SUCCESS;
}
}
同时html代码也需要改一下:
<form>
<!--
要在对应的属性要用 对象名.属性,指明这个值到底去了哪一个域对象
-->
用户名:<input type="text" name="user.username">
密码:<input type="password" name="user.password">
<input type="submit" value="提交">
</form>
3.模型驱动
模型驱动基本实现方式是让Action类实现一个ModelDriven的接口,这个接口需要我们实现一个getModel方法,这个方法返回的就是Action所使用的数据模型对象
Action类只是添加了ModelDriven的实现,另外去掉了属性对应的getter/setter方法
public class UserAction extends ActionSupport implements ModelDriven<UserInfo>{
private UserInfo users = new UserInfo();//不需要写getter/setter方法
//写业务逻辑
@Override
public String execute() throws Exception{
System.out.println(users.username);
return SUCCESS;
}`
@Override
public User getModel(){
return users;
}
}
登录页面也需要做相应调整,主要就是去掉属性的前缀,原因很简单,使用ModelDriven的方式,一个Action类只能对应一个Model,因此不需要添加前缀,Struts2就能够知道
<form>
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit" value="提交">
</form>
Result
Result就是Action执行完后返回的一个字符串,它指示Action完成后的下一个具体页面的位置,在struts.xml中配置
<action name="" class="" method="">
<result name="">/xx.jsp</result>
</action>
Action返回的字符串就是上面result的name属性,在Struts2中,预定义了一些Result的字符串常量:
SUCCESS:表示Action执行成功,返回相应的视图。SUCCESS是name属性的默认值
NONE:表示Action正确的执行完成,但不返回任何视图
ERROR:表示Action执行失败,返回到错误视图
LOGIN:表示因用户没有登陆而没有正确执行,将返回该登陆试
INPUT:表示Action执行需要更多的输入信息,回到input对应的页面
也可以不使用这些预定义的字符串常量,只要在Action中返回的字符串与<result>中的name属性一致就可以了。
Result还有一个属性type,响应结果的类型. 默认值为 dispatcher。在Struts2中预定义了很多type:
chain 用来处理Action链
dispatcher 用来转向页面,通常处理JSP
redirect 重定向到一个URL
redirectAction 重定向到一个Action
plaintext 显示源文件内容,如文件源码
freemarker 处理FreeMarker模板
httpheader 控制特殊http行为的结果类型
stream 向浏览器发送InputStream对象,通常用来处理文件下载,还可用于返回AJAX数据
velocity 处理Velocity模板
xslt 处理XML/XLST模板
来看一下几个比较常用的:
1.dispatcher 地址栏不更新
即转发,Struts2后台通过HttpServletRequest的RequestDispatcher进行页面跳转,保持是同一个对象请求
dispatchcer类型的实现有两个属性:location和parse
<result name="success" type="dispatcher">
<param name="location">/success.jsp</param>
<param name="parse">true</param>
</result>
上面的代码就等价于
<result name="success" type="dispatcher">/success.jsp</result>
location参数用于指定action执行完后要转向的目标资源。parse如果为true,则解析location参数中的OGNL表达式,如果为false,则不解析默认是true
使用dispatcher 则结果类型不能把控制权转发给一个外部资源
2.redirect
即重定向,Struts2后台通过HttpServletResponse的SendRedirect方法将请求重定向到另一个资源
缺点是保存在原来Action中的数据无法访问,如果要访问之前存储的数据一种方式是将数据保存到session中,另一种就是通过请求参数来传递数据
<result type="redirect">/success.jsp?name=${username}</result>
这个参数会向新请求中传入name的参数,username是对应Action中的属性
如果要在success.jsp这个页面中得到name参数的值:意识直接使用HttpServletRequest对象的方式获取参数,另一种是使用EL表达式
欢迎,<%=request.getParameter("name") %>
欢迎,${psram["name"]}
3.redirectAction 重定向到一个action
这个Result使用ActionMapperFactory提供的ActionMapper来重定位浏览器的url来调用指定的action和namespace
<action name="xx" class="xxx" method="execute">
<!--重定向的页面不用加后缀名
actionName: 指定 “目的地” action 的名字. 它是默认属性
namespace: 用来指定 “目的地” action 的命名空间. 如果没有配置该参数, Struts 会把当前 Action 所在的命名空间作为 “目的地” 的命名空间
-->
<result name="success" type="redirectAction">
<param name="namespace">/redirectAction</param>
<param name="actionName">defo</param>
</result>
</action>
<action name="defo" class="xxx" method="xx">
<result>/success.jsp</result>
</action>
4.chain 从一个Action转发到另一个Action
用来将Action执行完后链接到另一个Action中继续执行,新的Action用上一个Action的上下文,数据也会传递
chain有四个属性:
actionName(default):将链接的action的名称
namespace:用来决定链接的action的命名空间,如果为空,则默认为当前的命名空间
method:用来指定在目标action中调用的方法,如果为空,则默认执行execute方法
skipAction:(可选)将可链接的action用逗号分隔
<package name="xxx" extends="struts-default" namespace="/" >
<action name="firstAction" class="xxx">
<result name="toSecond" type="chain">
<!-- 这个actionName要与将要调用的action名字一样 -->
<param name="actionName">secondAction</param>
</result>
</action>
<action name="secondAction" class="xxx">
<result name="welcome">/welcome.jsp</result>
</action>
</package>
上面演示了从firstAction跳到了secondAction。注意chain不能在配置的时候传递参数,即不能类似于如下配置
<result type="chain">
<param name="actionName">secondAction?num=5</param>
</result>
全局result
之前我们的<result>元素是<action>的子元素,这被称为局部result,只可以由包含它的action访问,但有一些result可以由多个action共用,就需要配置全局result了。
全局result是作为<global-results>的子元素,而<global-results>是<package>的子元素
<package>
<global-results>
<result name="tologin">/login.jsp<result>
</global-results>
</package>
result会先找自己action中的result元素是否有匹配的,如果没有就去找全局result,如果全局result也没有,则向上依次寻找父包的全局result
自定义Result
自定义Result就是自己开发的result,只要实现com.openSymphony.xwork2.Result接口就行了,这个接口只有一个execute(ActionInvocation actionInvocation)方法,在这个方法中去写Result的真正处理,即如何展示视图
public class MyResult implements Result {
public vois execute(ActionInvocation actionInvocation) throws Exception {
System.out.println("要处理的result字符串是="+invocation.getResultCode());
}
}
所需要的获得更多的值可以通过ActionInvocation去获取ActionContext,这里面封装着所有需要的值
在struts.xml中去配置自定义的Result
<package name="xx" extends="struts-default" namespace="/">
<!-- 先要声明一下自定义的Result
default表示这个ResultType是否缺省
-->
<result-types>
<result-type name="MyResult" class="自定义Result所在的位置" default="false" />
</result-types>
<action name="xx" class="xxx">
<result name="xxx" type="这里写自定义的<result-type>中的name属性的值">/success.jsp</result>
</action>
</pacakge>