Struts2的快速入门
前言:不管学习什么技术,首先要了解这个技术是什么和它能解决什么问题,然后了解怎么利用它解决问题(敲代码),最后了解它的工作原理。这是我一贯学习新知识新技术的过程。这种学习过程让我能很快的掌握一种新的知识,让我在这个高速发展的it技术时代得以生存下去。
第一部分(了解技术)
什么是struts2?
Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts 2是Struts的下一代产品,是在 struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts 2可以理解为WebWork的更新产品。虽然从Struts 1到Struts 2有着太大的变化,但是相对于WebWork,Struts 2的变化很小。
根据对上面那段文字的理解加上自己总结的其他struts2的概念,总的来说,struts2是一个基于拦截器的比原生servlet技术开发更为简单的一个前端框架,用于接收页面提交的数据以及请求,用于处理请求的对象我们习惯将其称为action,由于action对象是__多实例__(可以设置为单实例)的对象,即每次访问一个action的时候都是一个新的action对象,所以action对象是一个线程安全的对象。而对于传统servlet+jsp页面的开发来说,由于servlet是单实例(默认配置)的,会导致线程不安全。而且使用struts2框架可以很容易将前端(html、jsp等页面)传递到后台(服务器)的数据,并且可以很容易的讲数据封装成对象。同样的,只要前端页面设置某些属性值的时候遵从某些规则,利用struts2框架可以很容易的将后台的数据传递到前端页面,这在后面具体代码展现的时候会看到效果。总的来说,我们使用struts2可以很容易的处理前端和后台的数据交换,这也是前端框架的主要功能。
第二部分(怎么使用框架)
使用环境声明:win10、Spring Tool Suite3.8.4、Tomcat8.0、struts2-2.3.4
Tips:所有的软件均可在相应的官方网站下载(尽量不要在杂七杂八的网站下载)
一、快速入门
毋庸置疑的是,如果要使用一个框架,那么必须导入其相关的jar包。当我们下载好了struts2的压缩包并且解压之后,进入lib目录,发现lib目录下有一大批jar文件,如下图:
那么我们怎么选择一些基本jar包导入呢?
这里有一个简单的方法:进入apps目录并解压struts2-blank.war文件,进入解压后的文件,如下图:
将该文件夹下的所有jar包引入到工程目录的lib目录下。
接下来做的是写xml配置文件,在struts-2.3.4.1\apps\struts2-blank\WEB-INF\classes目录下有一个struts.xml的配置文件,我们先将其复制到工程的类路径下。然后删除所有元素,只留下文档声明和struts标签,代码如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> </struts>
核心配置文件需要放置在类路径下,名称为struts.xml。
必须在web.xml中配置一个核心过滤器,代码如下:
<!-- 配置struts的核心过滤器开始 --> <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>/*</url-pattern> </filter-mapping> <!-- 配置struts的核心过滤器结束 -->
学过servlet的都知道这是配置一个过滤器,这个核心过滤器由struts2框架实现。拦截器的实现原理和过滤器大相径庭,都是使用了设计模式的责任链模式,/*表示拦截所有的资源请求(jsp、html、action等等)。
新建一个Action类HelloAction,代码如下:
public class HelloAction extends ActionSupport { public String hello() throws Exception { System.out.println("有人访问我了!但是现在我并不知道是谁"); return SUCCESS; } }
配置这个action的hello.xml文件(该xml的文档声明与核心配置文件的文档声明一样),代码如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <!-- package标签:提供了将多个Action组织为一个模块的方式 package的名字必须是唯一的,package可以扩展,当一个package扩展自 另一个package时该package会在本身配置的基础上加入扩展的package 的配置,父package必须在子package前配置 name:package名称 extends:继承的父package名称,一般全部写为struts-default,不能不写。 namespace:定义package命名空间 该命名空间影响到url的地址,例如此命名空间为/test那么访问是的地址为http://localhost:8080/项目名称/test/XX.action --> <package name="demo" namespace="/" extends="struts-default"> <!-- action标签:用来配置一个action name:action的名字 class:这个action所属于的类 method:当访问这个名称为hello的action的时候就调用HelloAction类的hello方法 --> <action name="hello" class="cn.demo.action.HelloAction" method="hello"> <!-- result标签配置返回结果,当调用HelloAction类的某一个方法返回结果名称为name指定的值的时候,就跳转到result标签体里面字符串所代表的路径 --> <result name="success">hello.jsp</result> </action> </package> </struts>
使用include标签将该action的配置引入到核心配置文件中,include标签在分模块开发中尤为重要。当然也可以将hello.xml中的内容直接写到核心配置文件中,但是并不推荐。代码如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <!-- 引入action的配置文件 --> <include file="cn/demo/action/hello.xml"></include> </struts>
在web根目录下创建一个hello.jsp文件
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>hello struts2</title> </head> <body> <h1>hello struts2!</h1> </body> </html>
在浏览器输入http://localhost/struts2demo/hello就可以访问该action了,此时页面显示如下图:
eclipse中也有输出:
二、搭建一个有前后台数据交换的项目
项目模拟:前端有一个index.jsp界面,里面有一个form表单,填写用户名称和用户名密码,可以将数据提交到服务器,服务器处理数据(利用Struts2框架自动封装成User对象)并且返回这个User对象到user.jsp这个视图中。
详细步骤
创建一个User对象,代码如下:
package cn.demo.domain;public class User { 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; } }
创建index.jsp页面,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>struts2 demo</title> </head> <body> <form action="${pageContext.request.contextPath}/echo" method="post"> 用户名:<input type="text" name="user.username"><br> 密码:<input type="password" name="user.password"><br> <input type="submit"> </form> </body> </html>
需要注意的是,form表单里面的两个input输入项的name值的写法,a.b,其中a代表的是echo这个action类里面的一个属性的名字,而b则是属性(这个属性为pojo对象)的一个属性。
配置action,代码如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <package name="demo" namespace="/" extends="struts-default"> <action name="*" class="cn.demo.action.HelloAction" method="{1}"> <result name="success">hello.jsp</result> <result name="echo">echo.jsp</result> </action> </package> </struts>
这里使用了一个*填充action标签的name属性,然后后面的method属性使用{1}表示,其实这个1的意思就是匹配第一个*所代表的字符串,即访问echo这个action的时候,就会调用echo方法。
action类修改为:
package cn.demo.action; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.config.entities.ActionConfig; import cn.demo.domain.User; public class HelloAction extends ActionSupport { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } public String echo() throws Exception { ActionContext ac = ActionContext.getContext(); //获取值栈 ac.put("user", user); //将数据存放到值栈中,便于前端页面获取 // ServletActionContext可以用来获取元素的servlet开发中使用的各种域对象 String addr = ServletActionContext.getRequest().getRemoteAddr(); System.out.println("有人访问我了!我知道他是:" + addr); System.out.println(user.getUsername() + "-" + user.getPassword()); return "echo"; } public String hello() throws Exception { System.out.println("有人访问我了!但是现在我并不知道是谁"); return SUCCESS; } }
这里使用了Struts2的一个概念——值栈,它是用于服务器向前端页面传递数据的,现在只是简单的了解一下就行了,后面会有详细的讲解。
echo.jsp的代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>echo</title> </head> <body> <h1>echo</h1> 用户名:${user.username }<br> 密码:${user.password } </body> </html
这里使用ognl表达式来接收服务器传递过来的数据,其实写法很像对象属性导航的写法,都是用‘.’不断的‘点’下去,一直到基本属性为止,当然这里只是获取熟悉的一种方法,还可以使用struts2的标签库来引用,这里就不在细说,我会在其他文章中写出其方法。
效果演示
访问该项目,页面定向到如下图:
输入zhangsan qazplm,然后点击提交:
浏览器响应:
服务端响应:
从上面的演示看出,不管是将前端数据提交到后台还是讲后台数据传递到前端都很简单,这就是struts2的强大之处。
第三部分(原理解析)
struts2作为一个强大的前端框架,能够很容易的交互前端和后台的数据,这里说的简单指的是将前端的数据封装到一个对象中和将后台的数据填充到前端页面中很简单。
前面也有说过,struts2的核心是拦截器,struts2的拦截器有许多,其中有部分拦截器就可以封装数据以及处理数据的编码问题,这样才可以很容易将数据在前端和后台之间传递而不产生乱码。
我们在工程中找到struts2-core-2.3.4这个包,里面有一个struts-default.xml文件,相信读者看到这个名字的时候应该不陌生,因为在上面的action的配置中用到了这个,没错,所以的action应该直接或者间接的继承这个xml文件,这样才可以使用该配置文件里面的拦截器功能。
struts-default.xml配置解读
定义了一个名称为struts-default的抽象包,定义了10种返回类型
<package name="struts-default" abstract="true"> <result-types> <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/> <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/> <result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/> <result-type name="httpheader" class="org.apache.struts2.dispatcher.HttpHeaderResult"/> <result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/> <result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/> <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/> <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/> <result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/> <result-type name="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" /> </result-types> </package>
里面一共配置了32个拦截器,如下:
<interceptor name="alias" class="com.opensymphony.xwork2.interceptor.AliasInterceptor"/> <interceptor name="autowiring" class="com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor"/> <interceptor name="chain" class="com.opensymphony.xwork2.interceptor.ChainingInterceptor"/> <interceptor name="conversionError" class="org.apache.struts2.interceptor.StrutsConversionErrorInterceptor"/> <interceptor name="cookie" class="org.apache.struts2.interceptor.CookieInterceptor"/> <interceptor name="clearSession" class="org.apache.struts2.interceptor.ClearSessionInterceptor" /> <interceptor name="createSession" class="org.apache.struts2.interceptor.CreateSessionInterceptor" /> <interceptor name="debugging" class="org.apache.struts2.interceptor.debugging.DebuggingInterceptor" /> <interceptor name="execAndWait" class="org.apache.struts2.interceptor.ExecuteAndWaitInterceptor"/> <interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/> <interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/> <interceptor name="i18n" class="com.opensymphony.xwork2.interceptor.I18nInterceptor"/> <interceptor name="logger" class="com.opensymphony.xwork2.interceptor.LoggingInterceptor"/> <interceptor name="modelDriven" class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/> <interceptor name="scopedModelDriven" class="com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor"/> <interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/> <interceptor name="actionMappingParams" class="org.apache.struts2.interceptor.ActionMappingParametersInteceptor"/> <interceptor name="prepare" class="com.opensymphony.xwork2.interceptor.PrepareInterceptor"/> <interceptor name="staticParams" class="com.opensymphony.xwork2.interceptor.StaticParametersInterceptor"/> <interceptor name="scope" class="org.apache.struts2.interceptor.ScopeInterceptor"/> <interceptor name="servletConfig" class="org.apache.struts2.interceptor.ServletConfigInterceptor"/> <interceptor name="timer" class="com.opensymphony.xwork2.interceptor.TimerInterceptor"/> <interceptor name="token" class="org.apache.struts2.interceptor.TokenInterceptor"/> <interceptor name="tokenSession" class="org.apache.struts2.interceptor.TokenSessionStoreInterceptor"/> <interceptor name="validation" class="org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor"/> <interceptor name="workflow" class="com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor"/> <interceptor name="store" class="org.apache.struts2.interceptor.MessageStoreInterceptor" /> <interceptor name="checkbox" class="org.apache.struts2.interceptor.CheckboxInterceptor" /> <interceptor name="profiling" class="org.apache.struts2.interceptor.ProfilingActivationInterceptor" /> <interceptor name="roles" class="org.apache.struts2.interceptor.RolesInterceptor" /> <interceptor name="annotationWorkflow" class="com.opensymphony.xwork2.interceptor.annotations.AnnotationWorkflowInterceptor" /> <interceptor name="multiselect" class="org.apache.struts2.interceptor.MultiselectInterceptor" /> </interceptor>
配置了多个拦截器栈,所谓的拦截器栈,就是将多个拦截器组合起来作为一个拦截器栈,如果该拦截器栈配置到action中,那么当一个请求访问该action的时候就依次执行拦截器里面的一些代码。下面列举一些拦截器栈:
基本拦截器
<!-- Basic stack --> <interceptor-stack name="basicStack"> <interceptor-ref name="exception"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="prepare"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> </interceptor-stack>
处理数据封装的拦截器
<interceptor-stack name="paramsPrepareParamsStack"> <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="i18n"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param> </interceptor-ref> <interceptor-ref name="servletConfig"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="modelDriven"/> <interceptor-ref name="fileUpload"/> <interceptor-ref name="staticParams"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> <interceptor-ref name="validation"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="workflow"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> </interceptor-stack>
在配置文件的最后有一段配置
<default-interceptor-ref name="defaultStack"/> <default-class-ref class="com.opensymphony.xwork2.ActionSupport" />
这段配置表示默认使用的拦截器使用默认的拦截器栈,默认的action类为ActionSupport,这意味着如果某个action没有定义Class,那么ActionSupport将作为该action的class出现。
补充内容
struts2执行流程
执行流程图
根据流程图和自己的理解解析执行流程
1、客户端发出HttpServletRequest请求(地址栏输入url或者表单提交到action等等)。
2、请求经过各种过滤器(filter),其中ActionContextCleanUp和FilterDispatcher这俩个过滤器中间放置用户自定义的过滤器。在执行完了所以的过滤器的doFilter方法之后,核心控制器FilterDispatcher(也就是在web.xml中配置的核心过滤器StrutsPrepareAndExecuteFilter)会清空ActionContext,所以, 其中ActionContextCleanUp作用是在doFilter里设置了一个计数器,使后续的filter不会清空ActionContext,最后执行完流程后由ActionContextCleanUp清空。如果没有ActionContextCleanUp,如果涉及到struts2中的如valuestack中的数据时会有可能得不到想要的数据。
3、经过FilterDispaer之后到达ActionMapper,ActionMapper的作用是根据配置文件中action的映射关系来查找action,如果找到了该action则返回一个代理对象ActionProxy,该对象存放了要准备调用的action的所有信息。如果没有找到则直接返回并且在页面上显示错误信息。
4、得到action的代理对象ActionProxy之后,ActionProxy根据ConfigurationMananger配置管理的类来读取Struts的配置文件,找到指定的action。
5、找到指定的action之后会依次执行拦截器,然后执行action里面的方法,action执行完毕之后会返回一个字符串,该字符串与配置文件Action中result的name属性相对应,将处理得到的数据封装到此视图来。
6、在依次出了拦截器栈之后,将数据响应(HttpServletResponse)到客户端。其中在经过ActionContextCleanUp时候会清空ActionContext。