第一节:model1与model2的区别和联系
JSPModel1和Model2是两种JSP建立应用程序的方式。
(1).Model1的体系结构图
1).首先web浏览器对JSP页面发出request请求,取得页面参数。
2.)通过JavaBean对业务逻辑进行操作。
3.)从而对应用数据进行操作,并返回数据。
4.)通过Response响应,渲染JSP页面.
优点:架构简单,比较适合小型项目开发。基本上两层代码就能搞定,JSP+持久层。
缺点:JSP页面有控制页面显示的代码又有与后台业务逻辑交互的Java代码,职责不单一,页面负担很重,影响页面运行速度;而且不方便后期的维护。
(2)Model2的体系结构图:MVC
Model2跟Model1不同的地方是:
1.Model1是JSP负责业务逻辑+页面显示,Model2采用servlet和JSP协作,JSP负责页面显示,servlet负责业务逻辑,将页面和业务解耦和,让整个前台的开发和维护更加灵活,代码复用性高,便于维护。
具体职责:
JavaBean(Model),模型,主要职责 1.业务逻辑 2.保存数据的状态
JSP(View),视图,主要职责:1.页面显示
Servlet(Controller),控制器,主要职责:1.获取表单数据 2.调用业务逻辑 3.渲染页面
优点:职责清晰,适合大型项目
缺点:分层过多,不适合小型项目的开发
总结
Model2是Model1的进化版,或者也可以说拓展版。两者应用的场合不同,一大项目一小项目。就跟公司一样,如果公司想要扩充规模,就必须职责单一,这样才不会导致人员责任不明确,效率低下,资源浪费。
第二节:框架
1.框架:framework。框架就是一个模板,半成品,使用框架必须遵守框架的规则。
好处:提高开发效率,并不会提高执行效率。
典型的MVC框架:
Structs2:由structs1+webwork整合而来。是apatch下的一个项目,开源免费。基于包来管理。
2.MVC框架完成的事情:
(1).Servlet做哪些事情:处理用户提交的数据,调用业务方法,处理业务结果,控制试图显示,将用户请求映射到一个java类。
(2). MVC框架完成的事情:将用户请求映射到一个java类,获取用户提交的数据,渲染数据(将数据封装到前台显示(request)),控制试图跳转。
struts2执行原理(执行流程)
(2012-12-06 21:35:48)
转载▼
一个请求在Struts2框架中的处理大概分为以下几个步骤:
在上述过程中所有的对象(Action,Results,Interceptors,等)都是通过ObjectFactory来创建的。
Struts2的目标很简单--使Web开发变得更加容易。为了达成这一目标,Struts2中提供了很多新特性,比如智能的默认设置、annotation的使用以及"惯例重于配置"原则的应用,而这一切都大大减少了XML配置。Struts2中的Action都是POJO,这一方面增强了Action本身的可测试性,另一方面也减小了框架内部的耦合度,而HTML表单中的输入项都被转换成了恰当的类型以供action使用。开发人员还可以通过拦截器(可以自定义拦截器或者使用Struts2提供的拦截器)来对请求进行预处理和后处理,这样一来,处理请求就变得更加模块化,从而进一步减小耦合度。模块化是一个通用的主题--可以通过插件机制来对框架进行扩展;开发人员可以使用自定义的实现来替换掉框架的关键类,从而获得框架本身所不具备的功能;可以用标签来渲染多种主题(包括自定义的主题);Action执行完毕以后,可以有多种结果类型--包括渲染JSP页面,Velocity和Freemarker模板,但并不仅限于这些。
Struts2的优缺点:
优点:
(1)实现了MVC模式,层次结构清晰,使程序员只需关注业务逻辑的实现。
(2)丰富的标签库,大大提高了开发的效率。Struts的标记库(Taglib)。
(3) Struts2提供丰富的拦截器实现。
(4) 页面导航,通过配置文件,就可以掌握整个系统各个部分之间的关系,对于后期的维护有着莫大的好处。
(7) 面向切面编程的思想在Strut2中也有了很好的体现。最重要的体现就是拦截器的使用,拦截器就是一个一个的小功能单位,用户可以将这些拦截器合并成一个大的拦截器,这个合成的拦截器就像单独的拦截器一样,只要将它配置到一个、Action中就可以。
(8)支持国际化i18N,数据库链接池管理。
缺点:
1. 转到展示层时,需要配置forward,如果有十个展示层的jsp,需要配置十次struts,而且还不包括有时候目录、文件变更,需要重新修改forward,注意,每次修改配置之后,要求重新部署整个项目,而tomcate这样的服务器,还必须重新启动服务器
2.Struts 的Action必需是thread-safe方式,它仅仅允许一个实例去处理所有的请求。所以action用到的所有的资源都必需统一同步,这个就引起了线程安全的问题。
3. 测试不方便.Struts的每个Action都同Web层耦合在一起,这样它的测试依赖于Web容器,单元测试也很难实现。不过有一个Junit的扩展工具Struts TestCase可以实现它的单元测试。
4. 类型的转换.Struts的FormBean把所有的数据都作为String类型,它可以使用工具Commons-Beanutils进行类型转化。但它的转化都是在Class级别,而且转化的类型是不可配置的。类型转化时的错误信息返回给用户也是非常困难的。
5. 对Servlet的依赖性过强.Struts处理Action时必需要依赖ServletRequest和ServletResponse,所有它摆脱不了Servlet容器。
6.前端表达式语言方面.Struts集成了JSTL,所以它主要使用JSTL的表达式语言来获取数据。可是JSTL的表达式语言在Collection和索引属性方面处理显得很弱。
7.对Action执行的控制困难. Struts创建一个Action,如果想控制它的执行顺序将会非常困难。甚至你要重新去写Servlet来实现你的这个功能需求。
8.对Action执行前和后的处理. Struts处理Action的时候是基于class的hierarchies,很难在action处理前和后进行操作。
9.对事件支持不够. 在struts中,实际是一个表单Form对应一个Action类(或DispatchAction),换一句话说:在Struts中实际是一个表单只能 对应一个事件,struts这种事件方式称为applicationevent,application event和component event相比是一种粗粒度的事件
Struts源码解析跟踪:http://qq421606162.iteye.com/blog/2009428
第三节:struts线程安全问题
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
Struts2线程安全吗?
每次请求都会重新创建新的action对象,所以线程安全。由于action对象是struts2,反射生成的,所以要求Action类要有一个公共的无参构造方法。
第四节:struts2xml配置
4.1.1常量配置:
1).包配置-------Struts2框架中核心组件就是Action、拦截器等,Struts2框架使用包来管理Action和拦截器等。每个包就是多个Action、多个拦截器、多个拦截器引用的集合。
在struts.xml文件中package元素用于定义包配置,每个package元素定义了一个包配置。它的常用属性有:
(1)name:必填属性,用来指定包的名字。
(2) extends:可选属性,用来指定该包继承其他包。继承其它包,可以继承其它包中的Action定义、拦截器定义等。
(3) namespace:可选属性,用来指定该包的命名空间。
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <!-- struts2的action必须放在一个指定的包空间下定义 --> <package name="default" extends="struts-default"> <!-- 定义处理请求URL为login.action的Action --> <action name="login" class="org.qiujy.web.struts.action.LoginAction"> <!-- 定义处理结果字符串和资源之间的映射关系 --> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> </action> </package> </struts> |
4.1.2. 命名空间配置:
考虑到同一个Web应用中需要同名的Action,Struts2以命名空间的方式来管理Action,同一个命名空间不能有同名的Action。
Struts2通过为包指定namespace属性来为包下面的所有Action指定共同的命名空间。
把上示例的配置改为如下形式:
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <!-- struts2的action必须放在一个指定的包空间下定义 --> <package name="qiujy" extends="struts-default"> <!-- 定义处理请求URL为login.action的Action --> <action name="login" class="org.qiujy.web.struts2.action.LoginAction"> <!-- 定义处理结果字符串和资源之间的映射关系 --> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> </action> </package>
<package name="my" extends="struts-default" namespace="/manage"> <!-- 定义处理请求URL为login.action的Action --> <action name="backLogin" class="org.qiujy.web.struts2.action.LoginAction"> <!-- 定义处理结果字符串和资源之间的映射关系 --> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> </action> </package></struts> |
如上配置了两个包:default和my,配置my包时指定了该包的命名空间为/manage。
对于包default:没有指定namespace属性。如果某个包没有指定namespace属性,即该包使用默认的命名空间,默认的命名空间总是""。
对于包my:指定了命名空间/manage,则该包下所有的Action处理的URL应该是“命名空间/Action名”。如上名为backLogin的Action,它处理的URL为:
http://localhost:8080/userlogin_struts2/manage/backLogin.action
Struts2的命名空间的作用等同于struts1里模块的作用。
4.1.3. 包含配置:
在Struts2中可以将一个配置文件分解成多个配置文件,那么我们必须在struts.xml中包含其他配置文件。
<struts> <include file="struts-default.xml"/> <include file="struts-user.xml"/> <include file="struts-book.xml"/> <include file="struts-shoppingCart.xml"/>
...... </struts> |
4.1.4. 拦截器配置:
见后面章节介绍。
4.1.5. 常量配置:
Struts2框架有两个核心配置文件,其中struts.xml文件主要负责管理应用中的Action映射,及Action处理结果和物理资源之间的映射关系。除此之外,Struts2框架还包含了一个struts.properties文件,该文件主义了Struts2框架的大量常量属性。但通常推荐也是在struts.xml文件中来配置这些常量属性。
如:后面会讲到Struts2的国际化,它的资源文件位置就用常量属性来指定:
<struts> ...... <constant name="struts.custom.i18n.resources" value="messages"/> </struts> |
表示指定了资源文件的放置在classes目录下,基本名是messages,则在classes目录下您就应该放置类似messages_zh_CN.properties,message_en.properties名的文件。
4.5. Struts2的Action
4.5.1. 实现Action类:
Struts2中Action是核心内容,它包含了对用户请求的处理逻辑,我们也称Action为业务控制器。
Struts2中的Action采用了低侵入式的设计,Struts2不要求Action类继承任何的Struts2的基类或实现Struts2接口。(但是,我们为了方便实现Action,大多数情况下都会继承com.opensymphony.xwork2.ActionSupport类,并重写此类里的public String execute() throws Exception方法。因为此类中实现了很多的实用接口,提供了很多默认方法,这些默认方法包括获取国际化信息的方法、数据校验的方法、默认的处理用户请求的方法等,这样可以大大的简化Action的开发。)
Struts2中通常直接使用Action来封装HTTP请求参数,因此,Action类里还应该包含与请求参数对应的属性,并且为属性提供对应的getter和setter方法。(当然,Action类中还可以封装处理结果,把处理结果信息当作一属性,提供对应的getter和setter方法)
修改第一部分的用户登录示例:把Action改成如下:
package org.qiujy.web.struts2.action; import com.opensymphony.xwork2.ActionSupport; /** *@authorqiujy *@version1.0 */ publicclass LoginAction extends ActionSupport{ private String userName; private String password;
private String msg; //结果信息属性
/** *@returnthemsg */ public String getMsg() { returnmsg; } /** *@parammsgthemsgtoset */ publicvoid setMsg(String msg) { this.msg = msg; } /** *@returntheuserName */ public String getUserName() { returnuserName; } /** *@paramuserNametheuserNametoset */ publicvoid setUserName(String userName) { this.userName = userName; } /** *@returnthepassword */ public String getPassword() { returnpassword; } /** *@parampasswordthepasswordtoset */ publicvoid setPassword(String password) { this.password = password; } /** *处理用户请求的excute()方法 *@return结果导航字符串 *@throwsException */ public String execute() throws Exception{ if("test".equals(this.userName) && "test".equals(this.password)){ msg = "登录成功,欢迎" + this.userName; returnthis.SUCCESS; }else{ msg = "登录失败,用户名或密码错"; returnthis.ERROR; } } } |
往success.jsp和error.jsp页面中添加 ${msg} EL表达式来显示结果信息。则最终效果跟以前一样。
4.5.2. Action访问Servlet API:(解耦两种,耦合两种)
Struts2中的Action并没有和任何Servlet API耦合,这样框架更具灵活性,更易测试。
但是,对于web应用的控制器而言,不访问Servlet API几乎是不可能的,例如跟踪HTTP Session状态等。Struts2框架提供了一种更轻松的方式来访问Servlet API。Struts2中提供了一个ActionContext类(当前Action的上下文对象),通过这个类可以访问Servlet API。下面是该类中提供的几个常用方法:
(ActionContext是map结构的容器。ActionContext是Action的上下文,存放Action过程中数据信息。ActionContext存放Action数据,ActionInvocation,request的数据,session的数据,local的数据,conversion errors等。每次请求时会为当前线程创建一个新的ActionContxt。而ActionContext采用了ThreadLocal(存放多线程内部的局部变量容量,静态的,线程安全)的方式来存放ActionContext,所以ActionContext是线程安全的。可以通过ActionContext.getContext获取,由于ActionContext是线程安全的,并且是通过静态方法获取的,所以在本线程的非Action类中也可以直接访问,注意:ActionContext是基于请求创建的,所以在非请求的线程中是不能直接使用ActionContext对象的,如:filter的init()方法。)
ActionContext包含六大对象:
ActionContext |
Application |
Session |
Request |
Parameters |
Attribute(page->request-àsession->application) |
ValueStack() |
Ognl ValueStack值栈是ActionContext中的一个对象,本身是栈结构,存放的数据时Action对象。
结论:使用ognl表达式去访问action的属性时可以直接访问。访问ActionContext中的属性时需要加“#”。(需要在jsp页面中导入标签库:<%@ taglib prefix="s” uri=”/struts-tag”%>,访问格式:用户名:<s:propertyvalue=“#session.user”/>)。
l publicstatic ActionContext getContext() :获得当前Action的ActionContext实例。
l publicObject get(Object key) :此方法类似于调用HttpServletRequest的getAttribute(String name)方法。
l publicvoid put(Object key, Object value) :此方法类似于调用HttpServletRequest的setAttribute(String name, Object o)。
l publicMap getParameters() :获取所有的请求参数。类似于调用HttpServletRequest对象的getParameterMap() 方法。
l publicMap getSession() :返回一个Map对象,该Map对象模拟了HttpSession实例。
l publicvoid setSession(Map session) :直接传入一个Map实例,将该Map实例里的key-value对转换成session的属性名-属性值对。
l publicMap getApplication() :返回一个Map对象,该对象模拟了该应用的ServletContext实例。
l publicvoid setApplication(Map application) :直接传入一个Map实例,将该Map实例里的key-value对转换成application的属性名-属性值对。
修改以上用户登录验证示例的Action类中的execute方法:
public String execute() throws Exception{ if("test".equals(this.userName) && "test".equals(this.password)){ msg = "登录成功,欢迎" + this.userName; //获取ActionContext实例,通过它来访问Servlet API ActionContext context = ActionContext.getContext(); //看session中是否已经存放了用户名,如果存放了:说明已经登录了; //否则说明是第一次登录成功 if(null != context.getSession().get("uName")){ msg = this.userName + ":你已经登录过了!!!"; }else{ context.getSession().put("uName", this.userName); }
returnthis.SUCCESS; }else{ msg = "登录失败,用户名或密码错"; returnthis.ERROR; } } |
Struts2中通过ActionContext来访问Servlet API,让Action彻底从Servlet API 中分离出来,最大的好处就是可以脱离Web容器测试Action。
另外,Struts2中还提供了一个ServletActionContext类,Action只要继承自该类,就可以直接访问Servlet API。具体方法参看struts2的API文档。
4.5.3. 一个Action内包含多个请求处理方法的处理
Struts1提供了DispatchAction,从而允许一个Action内包含多个请求处理方法。Struts2也提供了类似的功能。处理方式主要有以下三种方式:
4.5.3.1. 动态方法调用:(提示,这种调用方式会带来安全隐患!)
DMI:Dynamic MethodInvocation 动态方法调用。
动态方法调用是在action的名字中使用感叹号(!)来标识要调用的方法名,其语法格式为 actionName!methodName.action
动态方法调用是指:表单元素的action不直接等于某个Action的名字,而是以如下形式来指定对应的动作名:
<form method="post" action="userOpt!login.action"> |
则用户的请求将提交到名为”userOpt”的Action实例,Action实例将调用名为”login”方法来处理请求。同时login方法的签名也是跟execute()一样,即为public String login() throws Exception。
注意:要使用动态方法调用,必须设置Struts2允许动态方法调用,通过设置struts.enable.DynamicMethodInvocation常量来完成,该常量属性的默认值是true。
4.5.3.1.1. 示例:
修改用户登录验证示例,多增加一个注册用户功能。
1. 修改Action类:
package org.qiujy.web.struts2.action;
import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport;
/** *@authorqiujy *@version1.0 */ publicclass LoginAction extends ActionSupport{ private String userName; private String password; private String msg; //结果信息属性 /** *@returnthemsg */ public String getMsg() { returnmsg; } /** *@parammsgthemsgtoset */ publicvoid setMsg(String msg) { this.msg = msg; } /** *@returntheuserName */ public String getUserName() { returnuserName; } /** *@paramuserNametheuserNametoset */ publicvoid setUserName(String userName) { this.userName = userName; } /** *@returnthepassword */ public String getPassword() { returnpassword; } /** *@parampasswordthepasswordtoset */ publicvoid setPassword(String password) { this.password = password; }
/** *处理用户请求的login()方法 *@return结果导航字符串 *@throwsException */ public String login() throws Exception{ if("test".equals(this.userName) && "test".equals(this.password)){ msg = "登录成功,欢迎" + this.userName; //获取ActionContext实例,通过它来访问Servlet API ActionContext context = ActionContext.getContext(); //看session中是否已经存放了用户名,如果存放了:说明已经登录了; //否则说明是第一次登录成功 if(null != context.getSession().get("uName")){ msg = this.userName + ":你已经登录过了!!!"; }else{ context.getSession().put("uName", this.userName); }
returnthis.SUCCESS; }else{ msg = "登录失败,用户名或密码错"; returnthis.ERROR; } }
public String regist() throws Exception{ //将用户名,密码添加到数据库中 //... msg = "注册成功。"; returnthis.SUCCESS; } } |
2. struts.xml文件:没有什么变化,跟以前一样配置
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="my" extends="struts-default" namespace="/manage"> <!-- 定义处理请求URL为login.action的Action --> <action name="userOpt" class="org.qiujy.web.struts2.action.LoginAction"> <!-- 定义处理结果字符串和资源之间的映射关系 --> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> </action> </package> </struts> |
3. 页面:
index.jsp
<%@ page language="java" pageEncoding="UTF-8"%> <html> <head> <title>用户登录页面</title> </head>
<body> <h2>用户入口</h2> <hr> <form action="manage/userOpt!login.action" method="post"> <table border="1"> <tr> <td>用户名:</td> <td><input type="text" name="userName"/></td> </tr> <tr> <td>密码:</td> <td><input type="password" name="password"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value=" 确定 "/> </td> </tr> </table> </form> </body> </html> |
regist.jsp
<%@ page language="java" pageEncoding="UTF-8"%> <html> <head> <title>用户注册页面</title> </head>
<body> <h2>用户注册</h2> <hr> <form action="manage/userOpt!regist.action" method="post"> <table border="1"> <tr> <td>用户名:</td> <td><input type="text" name="userName"/></td> </tr> <tr> <td>密码:</td> <td><input type="password" name="password"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value=" 注册 "/> </td> </tr> </table> </form> </body> </html> |
4. 运行结果:
4.5.3.2. 为Action配置method属性:
将Action类中的每一个处理方法都定义成一个逻辑Action方法。
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="my" extends="struts-default" namespace="/manage"> <action name="userLogin" class="org.qiujy.web.struts2.action.LoginAction" method="login"> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> </action>
<action name="userRegist" class="org.qiujy.web.struts2.action.LoginAction"method="regist"> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> </action> </package> </struts> |
如上,把LoginAction中的login和regist方法都配置成逻辑Action。要调用login方法,则相应的把index.jsp中表单元素的action设置为"manage/userLogin.action";要调用regist方法,把regist.jsp中表单元素的action设置为"manage/userRegist.action"。
4.5.3.3. 使用通配符映射(wildcard mappings)方式:(*)
在struts.xml文件中配置<action…>元素时,它的name、class、method属性都可支持通配符,这种通配符的方式是另一种形式的动态方法调用。
当我们使用通配符定义Action的name属性时,相当于用一个元素action定义了多个逻辑Action:
<action name="user_*" class="org.qiujy.web.struts2.action.UserAction" method="{1}"> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> </action> |
如上,<action name=”user_*”>定义一系列请求URL是user_*.action模式的逻辑Action。同时method属性值为一个表达式{1},表示它的值是name属性值中第一个*的值。例如:用户请求URL为user_login.action时,将调用到UserAction类的login方法;用户请求URL为user_regist.action时,将调用到UserAction类的regist方法。
4.6. 配置处理结果
Struts2的Action处理完用户请求后,将返回一个普通字符串,整个普通字符串就是一个视图名。Struts2通过配置逻辑视图名和物理视图资源之间的映射关系,一旦系统收到Action返回的某个逻辑视图名,系统就会把对应的物理视图资源呈现给浏览者。
4.6.1. 配置处理结果:
Struts2的Action处理用户请求结束后,返回一个普通字符串-逻辑视图名,必须在struts.xml文件中完成逻辑视图和物理视图资源的映射,才可让系统转到实际的视图资源。
Struts2通过在struts.xml文件中使用<result…/>元素来配置结果。Struts2提供了两种结果。
l 局部结果:将<result …/>作为<action …>元素的子元素配置。
l 全局结果:将<result …/>作为<global-results…>元素的子元素配置。
在package元素中配置<global-results>子元素:
<global-results> <result name="error">/Error.jsp</result> <result name="invalid.token">/Error.jsp</result> <result name="login" type="redirect-action">Logon!input</result> </global-results> |
l 4.6.2. 处理结果类型:
Struts2提供了对不同种类返回结果的支持,常见的有JSP,FreeMarker,Velocity等。
Struts2支持的不同类型的返回结果为:
名字 | 说明 |
chain | 用来处理Action链 |
dispatcher | 用来转向页面,通常处理JSP,这是默认的结果类型 |
freeMarker | 处理FreeMarker模板 |
httpHeader | 用来控制特殊的Http行为 |
redirect | 重定向到一个URL |
redirect-action | 重定向到一个Action |
stream | 向浏览器发送InputSream对象,通常用来处理文件下载 |
velocity | 处理Velocity模板 |
xslt | 处理XML/XLST模板 |
plaintext | 显示原始文件内容,例如文件源代码 |
tiles | 结合Tile使用 |
另外第三方的Result类型还包括JasperReportsPlugin,专门用来处理JasperReport类型的报表输出;JfreechartPlugin;JSF Plugin。
4.6.3. 动态返回结果
有些时候,只有当Action执行完毕的时候我们才知道要返回哪个结果,这个时候我们可以在Action内部定义一个属性,这个属性用来存储Action执行完毕之后的result值,例如:
private String nextAction; public String getNextAction() { return nextAction; } |
在strutx.xml配置文件中,我们可以使用${nextAction}来引用到Action中的属性,通过${nextAction}表示的内容来动态的返回结果,例如:
<action name="fragment" class="FragmentAction"> <result name="next" type="redirect-action">${nextAction}</result> </action>
|
上述Action的execute方法返回next的时候,还需要根据nextAction的属性来判断具体定位到哪个Action。
4.7:属性驱动和模型驱动
不管属性驱动还是模型驱动,Struts2框架都是通过拦截器负责提取请求参数,并将请求数据封装到相应的Action实例的属性或专门的模型的属性。
4.7.1. 属性驱动:
属性驱动就是属性(property)作为贯穿MVC流程的信息携带者。简单的说,就是使用Action实例来封装请求参数和处理结果信息。前面我们做的示例都属于属性驱动模式。
4.7.2. 模型驱动:
模型驱动就是使用单独的javaBean作为贯穿整个MVC流程的信息携带者。也就是说,使用单独的VO(值对象)来封装请求参数和处理结果信息。
示例:继续修改用户登录验证:
1. 新增一用户域模型对象:User.java
package org.qiujy.domain; publicclass User { private String userName; private String password; /** *@returntheuserName */ public String getUserName() { returnuserName; } /** *@paramuserNametheuserNametoset */ publicvoid setUserName(String userName) { this.userName = userName; } /** *@returnthepassword */ public String getPassword() { returnpassword; } /** *@parampasswordthepasswordtoset */ publicvoid setPassword(String password) { this.password = password; } } |
2. 业务控制器:UserAction.java
package org.qiujy.web.struts2.action; import org.qiujy.domain.User; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; publicclass UserAction extends ActionSupport{ //定义用于封装请求参数的模型对象 private User user = new User(); private String msg; //结果信息属性 /** *@returntheuser */ public User getUser() { returnuser; } /** *@paramusertheusertoset */ publicvoid setUser(User user) { this.user = user; } /** *@returnthemsg */ public String getMsg() { returnmsg; } /** *@parammsgthemsgtoset */ publicvoid setMsg(String msg) { this.msg = msg; } /** *处理用户请求的login()方法 *@return结果导航字符串 *@throwsException */ public String login() throws Exception{ String userName = user.getUserName(); String password = user.getPassword();
if("test".equals(userName) && "test".equals(password)){ msg = "登录成功,欢迎" + userName; //获取ActionContext实例,通过它来访问Servlet API ActionContext context = ActionContext.getContext(); //看session中是否已经存放了用户名,如果存放了:说明已经登录了;否则说明是第一次登录成功 if(null != context.getSession().get("uName")){ msg = userName + ":你已经登录过了!!!"; }else{ context.getSession().put("uName", userName); } returnthis.SUCCESS; }else{ msg = "登录失败,用户名或密码错"; returnthis.ERROR; } } public String regist() throws Exception{ //将用户名,密码添加到数据库中 //... msg = "注册成功。"; returnthis.SUCCESS; } } |
3. 配置文件:struts.xml
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="my" extends="struts-default" namespace="/manage"> <action name="userOpt" class="org.qiujy.web.struts2.action.UserAction"> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> </action> </package> </struts> |
4. 页面:
index.jsp
<%@ page language="java" pageEncoding="UTF-8"%> <html> <head> <title>用户登录页面</title> </head> <body> <h2>用户入口</h2> <hr> <form action="manage/userOpt!login.action" method="post"> <table border="1"> <tr> <td>用户名:</td> <td><input type="text" name="user.userName"/></td> </tr> <tr> <td>密码:</td> <td><input type="password" name="user.password"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value=" 确定 "/> </td> </tr> </table> </form> </body> </html> |
其它页面略。
5. 运行效果:同以前一样。
6. 源代码:
Struts2的异常处理机制:
任何成熟的MVC框架都应该提供成就的异常处理机制。Strut2也不例外。Struts2提供了一种声明式的异常处理方式。Struts2也是通过配置的拦截器来实现异常处理机制的。
Struts2的异常处理机制通过在struts.xml文件中配置<exception-mapping…>元素完成的,配置该元素时,需要指定两个属性:
exception:此属性指定该异常映射所设置的异常类型。
result:此属性指定Action出现该异常时,系统转入result属性所指向的结果。
异常映射也分为两种:
l 局部异常映射:<exception-mapping…>元素作为<action…>元素的子元素配置。
l 全局异常映射:<exception-mapping…>元素作为<global-exception-mappings>元素的子元素配置。
输出异常信息:
使用Struts2的标签来输出异常信息:
l <s:propertyvalue="exception.message"/> :输出异常对象本身。
l <s:propertyvalue="exceptionStack"/> :输出异常堆栈信息。
示例:
还是修改用户登录示例:
1) 把UserAciton.java中的regist方法改成:
public String regist() throws Exception{ //将用户名,密码添加到数据库中 //... //msg = "注册成功。"; if(true){ throw new java.sql.SQLException("没有数据库驱动程序"); }
return this.SUCCESS; } |
2) 修改struts.xml文件:
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="my" extends="struts-default" namespace="/manage"> <!-- 定义全局处理结果 --> <global-results> <!-- 逻辑名为sql的结果,映射到/exception.jsp页面 --> <result name="sql">/exception.jsp</result> </global-results>
<global-exception-mappings> <!-- 当Action抛出SQLException异常时,转入名为sql的结果 --> <exception-mapping exception="java.sql.SQLException" result="sql"/> </global-exception-mappings>
<action name="userOpt" class="org.qiujy.web.struts2.action.UserAction"> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> </action> </package> </struts> |
3) 新增一页面:exception.jsp
<%@ page language="java" pageEncoding="utf-8"%> <%@ taglib uri="/struts-tags" prefix="s" %> <html> <head> <title>异常信息</title> </head>
<body> <h2> 出现异常啦 </h2> <hr/> <h3 style="color:red"> <!-- 获得异常对象 --> <s:property value="exception.message"/> </h3> <br/> <!-- 异常堆栈信息 --> <s:property value="exceptionStack"/> </html> |
4) 运行regist.jsp进行调试:
第五节:拦截器的配置和应用
1. 理解拦截器
5.1.0.
什么是拦截器:
拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
在Webwork的中文文档的解释为——拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。
谈到拦截器,还有一个词大家应该知道——拦截器链(Interceptor Chain,在Struts 2中称为拦截器栈InterceptorStack)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
5.1.1.
拦截器的实现原理:
大部分时候,拦截器方法都是通过代理的方式来调用的。Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时,Struts 2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。如下图:
5.1.2.
拦截器的配置
Struts 2已经为您提供丰富多样的,功能齐全的拦截器实现。大家可以至struts2的jar包内的struts-default.xml查看关于默认的拦截器与拦截器链的配置。
Struts2(XWork)提供的拦截器的功能说明:
拦截器 | 名字 | 说明 |
Alias Interceptor | alias | 在不同请求之间将请求参数在不同名字件转换,请求内容不变 |
Chaining Interceptor | chain | 让前一个Action的属性可以被后一个Action访问,现在和chain类型的result(<result type=”chain”>)结合使用。 |
Checkbox Interceptor | checkbox | 添加了checkbox自动处理代码,将没有选中的checkbox的内容设定为false,而html默认情况下不提交没有选中的checkbox。 |
Cookies Interceptor | cookies | 使用配置的name,value来是指cookies |
Conversion Error Interceptor | conversionError | 将错误从ActionContext中添加到Action的属性字段中。 |
Create Session Interceptor | createSession | 自动的创建HttpSession,用来为需要使用到HttpSession的拦截器服务。 |
Debugging Interceptor | debugging | 提供不同的调试用的页面来展现内部的数据状况。 |
Execute and Wait Interceptor | execAndWait | 在后台执行Action,同时将用户带到一个中间的等待页面。 |
Exception Interceptor | exception | 将异常定位到一个画面 |
File Upload Interceptor | fileUpload | 提供文件上传功能 |
I18n Interceptor | i18n | 记录用户选择的locale |
Logger Interceptor | logger | 输出Action的名字 |
Message Store Interceptor | store | 存储或者访问实现ValidationAware接口的Action类出现的消息,错误,字段错误等。 |
Model Driven Interceptor | model-driven | 如果一个类实现了ModelDriven,将getModel得到的结果放在Value Stack中。 |
Scoped Model Driven | scoped-model-driven | 如果一个Action实现了ScopedModelDriven,则这个拦截器会从相应的Scope中取出model调用Action的setModel方法将其放入Action内部。 |
Parameters Interceptor | params | 将请求中的参数设置到Action中去。 |
Prepare Interceptor | prepare | 如果Acton实现了Preparable,则该拦截器调用Action类的prepare方法。 |
Scope Interceptor | scope | 将Action状态存入session和application的简单方法。 |
Servlet Config Interceptor | servletConfig | 提供访问HttpServletRequest和HttpServletResponse的方法,以Map的方式访问。 |
Static Parameters Interceptor | staticParams | 从struts.xml文件中将<action>中的<param>中的内容设置到对应的Action中。 |
Roles Interceptor | roles | 确定用户是否具有JAAS指定的Role,否则不予执行。 |
Timer Interceptor | timer | 输出Action执行的时间 |
Token Interceptor | token | 通过Token来避免双击 |
Token Session Interceptor | tokenSession | 和Token Interceptor一样,不过双击的时候把请求的数据存储在Session中 |
Validation Interceptor | validation | 使用action-validation.xml文件中定义的内容校验提交的数据。 |
Workflow Interceptor | workflow | 调用Action的validate方法,一旦有错误返回,重新定位到INPUT画面 |
Parameter Filter Interceptor | N/A | 从参数列表中删除不必要的参数 |
Profiling Interceptor | profiling | 通过参数激活profile |
在struts.xml文件中定义拦截器,拦截器栈:
<package name="my" extends="struts-default" namespace="/manage"> <interceptors> <!-- 定义拦截器 --> <interceptor name="拦截器名" class="拦截器实现类"/> <!-- 定义拦截器栈 --> <interceptor-stack name="拦截器栈名"> <interceptor-ref name="拦截器一"/> <interceptor-ref name="拦截器二"/> </interceptor-stack> </interceptors> ...... </package> |
3.
使用拦截器
一旦定义了拦截器和拦截器栈后,就可以使用这个拦截器或拦截器栈来拦截Action了。拦截器的拦截行为将会在Action的exceute方法执行之前被执行。
<action name="userOpt" class="org.qiujy.web.struts2.action.UserAction"> <result name="success">/success.jsp</result> <result name="error">/error.jsp</result> <!-- 使用拦截器,一般配置在result之后, --> <!-- 引用系统默认的拦截器 --> <interceptor-ref name="defaultStack"/> <interceptor-ref name="拦截器名或拦截器栈名"/> </action> |
此处需要注意的是,如果为Action指定了一个拦截器,则系统默认的拦截器栈将会失去作用。为了继续使用默认拦截器,所以上面配置文件中手动引入了默认拦截器。
4.
自定义拦截器
作为“框架(framework)”,可扩展性是不可或缺的。虽然,Struts2为我们提供如此丰富的拦截器实现,但是这并不意味我们失去创建自定义拦截器的能力,恰恰相反,在Struts2自定义拦截器是相当容易的一件事。
5.3.
实现拦截器类:
所有的Struts 2的拦截器都直接或间接实现接口com.opensymphony.xwork2.interceptor.Interceptor。该接口提供了三个方法:
1) void init(); 在该拦截器被初始化之后,在该拦截器执行拦截之前,系统回调该方法。对于每个拦截器而言,此方法只执行一次。
2) void destroy();该方法跟init()方法对应。在拦截器实例被销毁之前,系统将回调该方法。
3) Stringintercept(ActionInvocation invocation) throws Exception; 该方法是用户需要实现的拦截动作。该方法会返回一个字符串作为逻辑视图。
除此之外,继承类com.opensymphony.xwork2.interceptor.AbstractInterceptor是更简单的一种实现拦截器类的方式,因为此类提供了init()和destroy()方法的空实现,这样我们只需要实现intercept方法。
5.4.
使用自定义拦截器:
两个步骤:
l 通过<interceptor…>元素来定义拦截器。
l 通过<interceptor-ref…>元素来使用拦截器。
5..5
自定义拦截器示例
5.6.
问题描述:
使用自定义拦截器来完成用户权限的控制:当浏览者需要请求执行某个操作时,应用需要先检查浏览者是否登录,以及是否有足够的权限来执行该操作。
5.7.
实现权限控制拦截器类:
AuthorizationInterceptor.Java
package org.qiujy.common;
import java.util.Map;
import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
/** * 权限检查拦截器 * * @author qiujy * @version 1.0 */ public class AuthorizationInterceptor extends AbstractInterceptor {
/* * 拦截Action处理的拦截方法 * */ public String intercept(ActionInvocation invocation) throws Exception {
Map session = invocation.getInvocationContext().getSession(); String userName = (String) session.get("userName");
if (null != userName && userName.equals("test")) { System.out.println("拦截器:合法用户登录---"); return invocation.invoke(); } else { System.out.println("拦截器:用户未登录---"); return Action.LOGIN; } } } |
5.6.
配置权限控制拦截器:
struts.xml:
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="my" extends="struts-default">
<interceptors> <!-- 定义权限控制拦截器 --> <interceptor name="authority" class="org.qiujy.common.AuthorizationInterceptor"/> </interceptors>
<!-- 定义全局处理结果 --> <global-results> <!-- 逻辑名为login的结果,映射到/login.jsp页面 --> <result name="login">/login.jsp</result> </global-results>
<action name="listall" class="org.qiujy.web.struts2.action.UserAction" method="listAllUser"> <result name="success">/listall.jsp</result> <!-- 使用拦截器 --> <interceptor-ref name="defaultStack"/> <interceptor-ref name="authority"/> </action>
<action name="userOpt" class="org.qiujy.web.struts2.action.UserAction"> <result name="success">/success.jsp</result> </action> </package> </struts> |
其它页面见源代码。
5.7.
运行调试:
在浏览器地址栏直接输入http://localhost:8080/AuthorityInterceptorDemo/listall.action 来访问,此动作配置了权限拦截器,所有被转到登录页面。
登录后:
再访问http://localhost:8080/AuthorityInterceptorDemo/listall.action 这个链接:
如果为了简化struts.xml文件的配置,避免在每个Action重复配置该拦截器,可以将拦截器配置成了一个默认拦截器栈。如下:
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="my" extends="struts-default">
<interceptors> <!-- 定义权限控制拦截器 --> <interceptor name="authority" class="org.qiujy.common.AuthorizationInterceptor" /> <!-- 定义一个包含权限控制的拦截器栈 --> <interceptor-stack name="mydefault"> <interceptor-ref name="defaultStack" /> <interceptor-ref name="authority" /> </interceptor-stack> </interceptors>
<!-- 定义默认拦截器 --> <default-interceptor-ref name="mydefault" />
<!-- 定义全局处理结果 --> <global-results> <!-- 逻辑名为login的结果,映射到/login.jsp页面 --> <result name="login">/login.jsp</result> </global-results>
<action name="listall" class="org.qiujy.web.struts2.action.UserAction" method="listAllUser"> <result name="success">/listall.jsp</result> </action> </package>
<package name="font" extends="struts-default"> <action name="userOpt" class="org.qiujy.web.struts2.action.UserAction"> <result name="success">/success.jsp</result> </action> </package> </struts> |
一旦在某个包下定义了默认拦截器栈,在该包下的所有action都会使用此拦截器栈。对于那些不想使用些拦截器栈的action,则应该将它放置在其它的包下。
注:拦截器与过滤器的区别:
过滤器(filter),是在Java web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者struts的action前统一设置字符集,或者去除掉一些非法字符
拦截器(interceptor),是在面向切面编程的就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。
拦截器与过滤器的区别 :
1、拦截器是基于Java的反射机制的,而过滤器是基于函数回调。
2、拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
3、拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
4、拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
5、在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
6、执行顺序 :过滤前 - 拦截前 - Action处理 - 拦截后 - 过滤后。
过滤是一个横向的过程,首先把客户端提交的内容进行过滤(例如未登录用户不能访问内部页面的处理);过滤通过后,拦截器将检查用户提交数据的验证,做一些前期的数据处理,接着把处理后的数据发给对应的Action;Action处理完成返回后,拦截器还可以做其他过程(还没想到要做啥),再向上返回到过滤器的后续操作。
一个Filter 可负责拦截多个请求或响应:一个请求或响应也可被多个请求拦截。
创建一个Filter 只需两个步骤:
(1)创建Filter 处理类:
(2)在web.xml 文件中配置Filter 。
创建Filter 必须实现javax.servlet.Filter 接口,在该接口中定义了三个方法。
• voidinit(FilterConfig config): 用于完成Filter 的初始化。
• void destroy(): 用于Filter 销毁前,完成某些资源的回收。
• voiddoFilter(ServletRequest request, ServletResponse response,FilterChain chain): 实现过滤功能,该方法就是对每个请求及响应增加的额外处理。
过滤器Filter也具有生命周期:init()->doFilter()->destroy(),由部署文件中的filter元素驱动。在servlet2.4中,过滤器同样可以用于请求分派器,但须在web.xml中声明,<dispatcher>INCLUDE或FORWARD或REQUEST或ERROR</dispatcher>该元素位于filter-mapping中。
注:struts2中所体现出的设计模式有:
1.Command Pattern
基本定义:把Command(Request)封装成对象,把发出命令(Invoker)的责任和执行命令(Receiver)的责任分割开,委派给不同的对象。
责任划分有什么好处?
责任约单一,内聚性越高,可重用的可能性越大,试想下,如果服务员不仅要点菜,还要去做菜,会是什么情景。
为什么把Invoker和Receiver解耦好处多?
类之间的耦合越低,可扩展的可能性越高。解耦后,更换一个服务员并不会影响厨师的工作
那么把Request封装成对象具体是什么意思呢?
在遥控器RemoteControl例子中,比如我有一个‘开灯’的Request,那么就应该对应有一个LightOnCommand对象,关灯就应该有LightOffCommand
在Web应用中,我有一个add user的Request,那么就应有一个AddUserCommand去处理请求, 或者用struts的name convention就是AddUserAction.
那么在Struts中又是怎么运用Command Pattern的呢?
Client : FilterDispatcherServlet
Invoker:ActionInvocation
Command:Action (not a must in Struts2)
ConcreteCommand:AddUserAction
Receiver:AddUserServiceImpl (not a must, depends on how many logic you want to put intoconcreteCommand)
Struts,你什么时候调用的setCommand()?
用户定义了ActionMapping在struts.xml里,在web Container启动时,ConfigurationManager 就装载了struts.xml,当Request过来时,Struts Framework会根据当前的URL去找ConfigurationManager所对应的concreteCommand/Action,然后这个Action会被set到ActionInvocation
Command Interface是必须的吗?
在struts1中,有一个Interface,所有的Action都必须是它的实现。但是在Struts2中,Action可以是任意的POJO,因为在Runtime的时候,具体的Action是什么,该调用它的什么方法,都可以通过配置文件(MetaData)+ Java Reflection来实现。这种新方式的好处是POJO Action没有了对框架的依赖,测试将会更加容易。缺点是因为没有interface的约束,调用Action的什么方法完全取决于默认值(比如execute)或是配置文件中的配置。若设置不妥,只有在Runtime的时候才能发现错误。
Receiver 是必须得吗?
不是,取决于你Action的厚度,如果你想让Action很轻的话,那么通常你会在Action中使用UserService.addUser()去做事情,此时的UserService就是Receiver。把Action设计的厚点,直接把addUser的logic放在Action中也是可以的。
Struts2中运用了command的思想,但并没有严格的按照其经典模型实现,而是做了些变通,这些变通乍看起来可能是有点违背设计原则,比如说取消了Action Interface,这不是反模式吗,反面向接口的编程吗,但仔细想想,这里我们真的需要这个接口吗?通过配置文件+Reflection,我们同样可以做到在Runtime的时候给ActionInvocation注入不同的Action的目的。而接口却增加了用户实现对框架的依赖,降低了程序的可测性,所以这样的变通其实是有积极意义的,虽然我们损失了一点点接口作为契约所带来的好处。
2. Interceptor Pattern
于其说这是模式,不如说这是AOP和Pipeline思想的结合,只不过Struts2中的实现非常的精巧,我不得不说这应该作为一个模式来推广。
AOP:所谓的AOP就是preProcess and postProcess, 系统应用中,许多地方是需要面向切面的,比如log,authentication,etc...。java Dynamic Proxy通常也是实现AOP的方式,但它是通过Java Reflection的机制,动态的产生一个被修改过的method,从而达到预处理和后处理的目的,个人觉得这种方式粗暴不够优雅,并且可扩展性不好。不过其优点是它是内嵌在JDK中的AOP,使用比较方面,对于那些不需要扩展性的需求,动态代理也是可以考虑的。
Pipeline:分层就是把复杂的问题分层多个层次,每一层只处理问题的一小部分。通信的7层模型就是典型的范例
Interceptor Pattern: 不仅为系统进行分层,而且还提供了AOP的处理,此外,还以一种plug-in的方式为用户提供了无限扩展的可能。
应该怎么实现?
Interceptor的调用过程类似于一个栈式调用,所以想到递归是很自然的,这既避免了像Dynamic Proxy那样的classhack,又提供了更好的扩展性。
还是以Struts中的类作为例子,其类图如下:
调用过程:
Pseudo 代码:
ActionInvocation
[java] view plain copy
1. public Result invoke(){
2. if( interceptors.hasNext() ){
3. Interceptor interceptor = interceptors.next();
4. result = interceptor.intercept(this);
5. }
6. else {
7. action.execute();//如果没有更多的Interceptor,停止递归,调用action
8. }
9. }
InterceptorImpl
[java] view plain copy
1. public SomeInterceptor implements Interceptor{
2. public Result intercept(ActionInvocation actionInvocation){
3. //pre-processing
4.
5. // 递归调用
6. result = actionInvocation.invoke();
7.
8. //post-processing
9.
10. return result;
11. }
12. }
先后接触到命令(Command)模式、ThreadLocal模式(严格上来说不算入设计模式中)、装饰(Decorator)模式、策略(Strategy)模式、构造(Builder)模式、责任链(Chain Of Responsibility)模式(在过滤器和拦截器中体现)、适配器模式、代理(Proxy)模式(action中体现)、模板方法模式、组合模式、单例模式(线程安全threadlocal)等等。
第六节:struts2的文件上传和下载
struts2没有提供自己的请求解析器,也就是说,struts2不会自己去处理multipart/form-data的请求,它需要调用其他请求解析器,将HTTP请求中的表单域解析出来,但struts2在原有的上传解析器上作了进一步封装,更进一步简化了文件上传,Struts2的struts.properties配置文件中,配置struts2的上传文件解析器struts.multipart.parser=jakarta(srtuts2默认),也可以设置为常用的cos,pell等。
struts2实现上传下载所必须的2个jar包:commons-fileupload-xxx.jar和commons-io-xxx.jar。
一、文件上传
1. 单文件上传
(1)单文件上传表单视图:
1. <body>
2. <form action="/test/upload.action" enctype="multipart/form-data" method="post">
3. <input name="uploadfile" type="file">
4. <input type="submit" value="上传">
5. </form>
6. </body>
注意:表单必须设置enctype="multipart/form-data"和method="post"属性,否则上传会出错。
2. 单文件上传Action类
1. /**
2. * 文件上传类
3. * @author Ye
4. *
5. */
6. public class UploadAction extends ActionSupport{
7.
8. <!--获取上传文件,名称必须和表单file控件名相同-->
9. private File uploadfile;
10.
11. <!--获取上传文件名,命名格式:表单file控件名+FileName(固定)-->
12. private String uploadfileFileName;
13.
14. //获取上传文件类型,命名格式:表单file控件名+ContentType(固定)
15. private String uploadfileContentType;
16.
17. ...//省略属性的getter、setter方法
18.
19. //方法一:使用FileUtils的copyFile来实现文件上传
20. public String upload() throws IOException
21. {
22. //设置上传文件目录
23. String realpath = ServletActionContext.getServletContext().getRealPath("/image");
24.
25. //判断上传文件是否为空
26. if(uploadfile!=null)
27. {
28. //设置目标文件(根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例)
29. File savefile = new File(realpath,uploadfileFileName);
30.
31. // 判断上传目录是否存在
32. if(!savefile.getParentFile().exists())
33. savefile.getParentFile().mkdirs();
34.
35. //把文件uploadfile 拷贝到 savefile 里,FileUtils类需要commons-io-x.x.x.jar包支持
36. FileUtils.copyFile(uploadfile,savefile);
37.
38. //设置request对象值
39. ActionContext.getContext().put("message", "上传成功!");
40. }
41. return "success";
42. }
43.
44. //方法二:使用文件流来实现文件上传
45. public String upload() throws IOException
46. {
47. FileOutputStream fos = new FileOutputStream("D:\\"+uploadfileFileName);
48.
49. FileInputStream fis = new FileInputStream(uploadfile);
50.
51. byte[] buffer = new byte[1024];
52.
53. int len = 0;
54.
55. while((len=fis.read(buffer))>0)
56. {
57. fos.write(buffer,0,len);
58. }
59. return "success";
60. }
61.
62. }
注意:记得Action类要继承ActionSupport。
3. 配置struts.xml文件
1. <package name="hello" namespace="/test" extends="struts-default">
2. <action name="upload" class="action.UploadAction" method="upload">
3.
4. <!-- 在struts.xml文件中使用constant元素指定在全局的struts.properties文件中自定义出错信息,value值为*.properties类型的文件名 -->
5. <constant name="struts.custom.i18n.resources" value="struts"></constant>
6.
7. <!-- 配置fileUpload的拦截器 -->
8. <interceptor-ref name="fileUpload">
9.
10. <!-- 配置允许上传的文件类型 -->
11. <param name="allowedTypes">image/bmp,image/gif,image/jpg</param>
12.
13. <!--配置允许上传文件的扩展名,如果有多个用","隔开 -->
14. <param name="allowedExtensions">txt,excel,ppt</param>
15.
16. <!-- 配置允许上传的文件大小,最大为20k -->
17. <param name="maximumSize">20480</param>
18.
19. </interceptor-ref>
20.
21. <!-- 配置struts2的默认拦截器栈 -->
22. <interceptor-ref name="defaultStack"></interceptor-ref>
23.
24. <result>/success.jsp</result>
25.
26. <!-- 上传失败时返回的视图页面 -->
27. <result name="input">/index.jsp</result>
28.
29. </action>
30. </package>
注意:(1) 拦截器实现要在UploadAction类中继承ActionSupport,否则拦截器无效。
(2) 必须显示配置引用Struts默认的拦截器栈:defaultStack
(3) struts的默认上传文件大小为2M,可以用常量来控制扩大上传限制:
<constantname="struts.multipart.maxSize"value="104857600"></constant> //设置上传上限为10M
真正的视频网站会使用插件而不是这种web上传的方式上传文件,因为Web上传的稳定性差。
(4) 在失败的页面视图中使用<s:fielderror/>标签可以输出上传失败的原因,错误信息默认是struts提供的英文字符,如果需要将其转换为中文字符,可在国际化资源文件中配置以下代码:
struts.properties:
1. struts.messages.error.content.type.not.allowed = 上传的文件类型不正确
2. struts.messages.error.file.too.large= 上传的文件太大
3. struts.messages.error.uploading=上传时出现未知错误
2. 多文件上传:
多文件上传原理同单文件上传,主要是在action中定义File[]数组来接收多文件数据。
(1)多文件上传表单视图:
1. <body>
2. <s:form action="multiUpload" method="post" namespace="/test3" enctype="multipart/form-data">
3. <s:file name="uploadfile" label="文件1"></s:file>
4. <s:file name="uploadfile" label="文件2"></s:file>
5. <s:file name="uploadfile" label="文件3"></s:file>
6. <s:file name="uploadfile" label="文件4"></s:file>
7. <s:submit value="上传"></s:submit>
8. </s:form>
9. </body>
(2)多文件上传Action类:
1. public class UploadAction extends ActionSupport {
2. private File[] uploadfile;
3. private String[] uploadfileFileName;
4. private String[] uploadfileContentType;
5.
6. ...//省略属性的getter、setter方法
7.
8. //方法一:
9. public String upload() throws IOException {
10. String realpath = ServletActionContext.getServletContext().getRealPath("/image");
11. if (uploadfile != null) {
12. File savepath = new File(realpath);
13. if (!savepath.exists())
14. savepath.mkdirs();
15. for (int i = 0; i < uploadfile.length; i++) {
16. File savefile = new File(realpath, uploadfileFileName[i]);
17. FileUtils.copyFile(uploadfile[i], savefile);
18. }
19. ActionContext.getContext().put("message", "上传成功!");
20. }
21. return "success";
22. }
23.
24. //方法二:
25. public String upload() throws IOException
26. {
27. for(int i = 0 ; i < uploadfile.length; i ++)
28. {
29. FileOutputStream fos = new FileOutputStream("D:\\"+uploadfileFileName[i]);
30.
31. FileInputStream fis = new FileInputStream(uploadfile[i]);
32.
33. byte [] buffer = new byte[1024];
34.
35. int len = 0 ;
36.
37. while((len = fis.read(buffer))>0)
38. {
39. fos.write(buffer,0,len);
40. }
41.
42. }
43. return "success";
44. }
45.
46. }
二、文件下载
(1)文件下载Action类
1. public class DownloadAction extends ActionSupport {
2.
3. //downloaPath属性用于封装被下载文件的路径
4. private String downloadPath;
5.
6. //初始化要保存的文件名
7. private String filename;
8.
9. //文件保存路径
10. private String savePath;
11.
12. public String getSavePath() {
13. return savePath;
14. }
15.
16. public void setSavePath(String savePath) {
17. this.savePath = savePath;
18. }
19.
20. public String getFilename() {
21. return filename;
22. }
23.
24. public void setFilename(String filename) {
25. this.filename = filename;
26. }
27.
28. public void setDownloadPath(String downloadPath) {
29. this.downloadPath = downloadPath;
30. }
31.
32. // 隐含属性 targetFile ,用于封装下载文件
33. public InputStream getTargetFile() throws FileNotFoundException
34. {
35. return new FileInputStream(downloadPath);
36. }
37.
38. public String execute()
39. {
40. return "success";
41. }
42.
43. }
(2)配置struts.xml文件:
1. <package name="test3_download" extends="struts-default">
2. <action name="download" class="com.action.DownloadAction" method="execute">
3. <!-- 对Action类中的文件路径参数设置初始值 -->
4. <param name="downloadPath">${savePath}</param>
5.
6. <!-- 设置一个stream类型的result -->
7. <result name="success" type="stream">
8.
9. <!-- 设置下载文件的输入流属性名 -->
10. <param name="inputName">targetFile</param>
11.
12. <!-- 设置下载文件的文件类型,配置后可以直接在浏览器上浏览,省略则会弹出是保存文件还是打开文件信息的对话框 -->
13. <!-- <param name="contentType">image/jpg</param> -->
14.
15. <!-- 设置下载文件的文件名 -->
16. <param name="contentDisposition">attachment;filename=${filename}</param>
17.
18. <!-- 设置下载文件的缓冲 -->
19. <param name="bufferSize">2048</param>
20. </result>
21. </action>
22. </package>