之前开发都是使用的Servlet + JSP + JavaBean,开发流程很是凌乱,Servlet在完成对用户请求的处理后,下一个页面是谁,如何跳转过去,这些都是在Servlet中完成的,导致
Servlet既要处理请求又要负责页面的流程,使得Servlet功能不够单一。
除此之外之前的开发模式导致数据传递无序,使用JavaBean传来传去,在数据嵌套的时候使用JavaBean往往感到力不从心。
缺乏辅助功能,几乎所有的东西都要从头开始,没有统一的分发调度、验证框架、国际化、本地化、例外消息处理等。
Struts2其实就是基于MVC的轻量级web应用框架,所谓的框架就是能完成一部分内容的半成品软件,所谓的轻量级就是小号更少的资源而且拥有更快的速度。
来看下Struts2和MVC的关系

简单的注册登录系统,没有使用JavaBean,这里使用了两个
Servlet,一个是登录用的LoginServlet,如下
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
if("".equals(username.trim()))
response.getWriter().write("用户名不能为空");
if("admin".equals(username) && "123456".equals(password))
request.getRequestDispatcher("/login_reister/success.jsp").forward(request,response);
else
request.getRequestDispatcher("/login_reister/defeat.jsp").forward(request,response);
}
另一个是注册使用的RegisterServlet,如下
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
if(!username.equals("admin"))
request.getRequestDispatcher("/login_reister/success.jsp").forward(request,response);
else
request.getRequestDispatcher("/login_reister/defeat.jsp").forward(request,response);
}
上面两个登录注册页面分别对应了一个Servlet,以此来处理jsp页面的数据,相当的麻烦。
使用Struts2实现登录注册
首先使用IDEA新建struts2项目,使用外部的lib(官网下载的),目录如下所示

之后add as library即可。
别忘了一定要把web.xml改一下,把<filter-class>改成这样:
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
配置过的web.xml如下所示
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
接下来就是编写Action了,把继承了ActionSupport类的类叫做Action,Action类是处理请求的类。
用于登录注册的Action类
private String username;
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;
}
public String login(){
if("admin".equals(username) && "123456".equals(password)) {
System.out.println("登录成功");
return "success";
}
return "defeat";
}
public String register(){
if(!("admin".equals(username))) {
System.out.println("注册成功");
return "success";
}
return "defeat";
}
接下来就是struts.xml
<struts>
<package name="login_register" extends="struts-default" >
<action name="login" class="com.demo.tests.HelloAction" method="login">
<result>success.jsp</result>
</action>
<action name="register" class="com.demo.tests.HelloAction" method="register">
<result>success.jsp</result>
</action>
</package>
</struts>
再接着就是一个登录或者是注册表单了
<form action="login.action" method="post">
用户名:<input type="text" name="username" ><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
结果

可以看到和使用Servlet结果一致。
先放一张Struts2官方的架构图。

通过观察上图可知执行流程:
1、首先客户端提交一个HttpServletRequest请求到Tomcat服务器。
2、请求被一系列的过滤器处理,先是
ActionContextCleanUp,然后是Other Filters(如SiteMesh等),最后是FilterDispatcher过滤器。
FilterDispatcher是控制器的核心,就是MVC的Struts 2实现中控制层(Controller)的核心。
3、FilterDispatcher询问ActionMapper是否需要调用某个Action来处理这个请求(HttpServletRequest),如果
ActionMapper决定调用某个Action,则FilterDispatcher把请求的处理转交给ActionProxy。
4、ActionProxy通过ConfigurationManager(也就是
struts.xml),询问框架的配置文件,找到Action。
5、ActionProxy创建一个ActionInvocation实例,同时
ActionInvocation通过代理模式调用Action,但是在调用之前ActionInvocation会根据配置加载Action相关的所有
Interceptor(拦截器)。
6、Action执行完毕后,ActionInvocation负责根据
struts.xml中的配置找到对应的返回结果result。
下面这个图更加形象的解释了Struts2的执行流程

在struts2中使用包来管理Action,这里的包和java中的包十分类似,这里的包主要用于管理一组业务功能相关的Action
package所具有的属性:
name:必须有的,这里的包也可以继承,包的名字就是继承的关键,是引用该包的唯一标识,包名不能重复出现。
extends:可选属性,指定该包继承的父包,子包可以从父包继承到拦截器、action等配置,继承多个包时用逗号分开,一般情况下继承的都是默认包(struts-default)。
namespace:也是可选属性,定义包的命名空间,默认为"",其运用原理是package的name属性作为其唯一标识,同一个命名空间内不能出现相同name值的package,否则前面定义的package会被后面的覆盖,此时改放不同的命名空间下就可以了,处理时记得是 命名空间+Action名,例如:
<package name="login" extends="struts-default" namespace="/login">
<action name="login" class="HelloAction" method="execute">
<result>success.jsp</result>
<result name="defeat">defeat.jsp</result>
</action>
</package>
访问时就要使用:
http://localhost:8080/login/login.action
来进行访问。
abstract:可选属性,指定此包是否是一个抽象包,如果为
true,则不能包含action。
action映射是框架中基本的工作单元,action映射就是将一个请求URI映射到一个action类,当一个请求匹配某个action名字时,框架就使用这个映射来确定如何处理请求。
struts.xml文件中的每一个action元素就是一个action映射。
action的name和class属性分别给给出了action的名称和Action类的位置。
方法一:
method属性:可以通过method属性来指定action调用的方法,所指定的方法必须返回一个String。
实例:
<package name="login_register" extends="struts-default">
<action name="login" class="HelloAction" method="login">
<result>success.jsp</result>
</action>
<action name="register" class="HelloAction" method="register">
<result>success.jsp</result>
</action>
</package>
访问的时候可以这样:
<a href="login.action">登录</a>
<a href="register.action">注册</a>
方法二:
在实际应用中,随着应用程序的不断扩大,我们不得不管理数量庞大的 Action。例如,一个系统中,用户的操作可分为登录和注册两部分,一个请求对应一个 Action 的话,我们将要编写两个 Action 来处理用户请求。在具体开发过程中,为了减少 Action 的数量,通常在一个 Action 中编写不同的方法(必须遵守 execute 方法相同的格式)处理不同的请求,如编写 UserAction,其中 login()方法处理登录请求,
register()方法处理注册请求。此时可以采用动态方法调用
(Dynamic Method Invocation,DMI)来处理。动态方法调用是指表单元素的 action 并不是直接等于某个 Action 的名称。
使用格式:
<form action="Action名字!方法名字.action"> ! 后直接跟Action中方法名.action
注意: 需要配置打开动态方法调用
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
并且在result标签下还要加上<allowed-methods>调用的方法名,如果不止一个方法,方法名之间用逗号隔开。
对刚才的登录注册改善实例:
struts.xml
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<package name="user" extends="struts-default" >
<action name="user" class="com.demo.tests.HelloAction">
<result>success.jsp</result>
<allowed-methods>login,register</allowed-methods>
</action>
</package>
</struts>
以登录为例,登录表单
<form action="user!login.action">
用户名:<input type="text" name="username" ><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="提交">
</form>
方法三:
使用通配符
随着web应用程序规模增加,所需的action也会增多,从而导致了大量的action映射,使用通配符可以减少action配置的数量,使一些具有类似行为的acting或者action的方法可以使用通用的样式来配置。
通配符即 *,用于匹配0个或多个字符,在配置action时,可以在action元素的name属性中使用 *,类匹配任意字符,例如
<action name="user_*" class="com.demo.tests.{1}Action" method="{1}">
<result>{1}.jsp</result>
</action>
上述映射仅仅会匹配所有的以user_开头的URI,如user_login,user_register等。
在上述配置中还有一个特殊的记号{1},这是作为占位字符使用,它将被通配符所匹配的值所替换
在action映射和acting结果中,通配符的值可以用记号{N}来访问,N是从1-9的数字,指出替换的是哪一个通配符匹配的值,整个请求URI可以用记号{0}来访问。比如:
<action name="*_*" class="com.demo.tests.{1}Action" method="{2}">
<result>{0}.jsp</result>
</action>
注意:使用通配符的时候也要加上<allowed-methods>
例如
<action name="*_*" class="com.demo.tests.HelloAction" method="{2}">
<result>success.jsp</result>
<allowed-methods>login,register</allowed-methods>
</action>
如果我们请求一个不存在的 Action,那么结果将会是在页面上出现 HTTP 404 的错误。
为了解决这个问题,Struts 2 框架允许我们指定一个默认的 Action,即如果没有一个 Action
匹配请求,那么默认的 Action 将被执行。
配置默认的 Action 通过<default-action-ref…/>元素来完成
例如
<default-action-ref name="default"></default-action-ref>
<action name="default" class="com.demo.tests.DefaultAction">
<result>default.jsp</result>
</action>
这样就把本action设置成了默认的。
一个result代表了一个可能的输出,当Action类的方法执行完成时,它返回一个字符串的结果码,struts2根据这个结果码选择对应的result,向用户输出。 result元素有两个属性
name:指定result的名称
type:指定result的类型,不同类型的result代表了不同类型的结果输出
在result元素中,可以使用param子元素来指定这个结果对应的实际资源的位置。param元素有一个必须的属性name,用于指定参数名,param元素的内容给出参数值。如果要指定资源的位置,name属性的值需要设置location
比如
<result name="success" type="dispatcher">
<!-- param 元素作为result的子元素,用于为该类型的result设置参数 -->
<param name="location">/success.jsp</param>
</result>
chain:用于Action链式处理
dispatcher:转发,相当于forward
redirect:用于重定向
redirectAction:用于重定向到另外的action映射
1、dispatcher:请求转发,底层调用RequestDispatcher的forward()或include()方法,dispatcher是type属性的默认值,通常用于转向一个JSP,localtion指定JSP的位置,parse如果为false表示location的值不会被当作
OGNL解析,默认为true。
2、redirect:重定向,新页面无法显示Action中的数据,因为底层调用response.sendRedirect("")方法,无法共享请求范围内的数据,参数与dispatcher用法相同。
3、redirect-action:重定向到另一个Action,参数与
chain用法相同,允许将原Action中的属性指定新名称带入新Action中,可以在Result标签中添加 <param name=”b”>${a} </param>,这表示原Action中的变量a的值被转给b,下一个Action可以在值栈中使用b来操作,注意如果值是中文,需要做一些编码处理,因为Tomcat默认是不支持URL直接传递中文的。
4、chain:将action的带着原来的状态请求转发到新的
action,两个action共享一个ActionContext,actionName指定转向的新的Action的名字,method指定转向哪个方法,namespace指定新的Action的名称空间,不写表示与原Action在相同的名称空间;skipActions指定一个使用 , 连接的Action的name组成的集合,一般不建议使用这种类型的结果。
在某些场景中,可能有多个action需要访问同一个结果,例如在论坛系统中,用户在发帖、回帖、搜索帖子时都需要先登录,那么我们就可以配置一个全局的login结果,这样action就不需要一 一配置login结果了。
比如
<global-results>
<result name="error">/error.jsp</result>
<result name="login" type="redirectAction">login!input</result>
</global-results>




