2006年,WebWork与Struts这两个优秀的JavaEE Web框架(Web Framework)的团体,决定合作共同开发一个新的,整合了WebWork与Struts优点,并且更加优雅、扩展性更强的框架,命名为“Struts2”,原Struts的1.x版本产品称为”Struts1”。
Struts2框架由两部分组成:XWork2和Struts2。XWork2提供了很多核心功能:IoC容器,强大的表达式语言(OGNL),数据类型转换,验证和可插入的配置。XWork框架的核心概念包括:aciton、拦截器和result。Struts2扩展了这些核心概念的基础实现 , 用于支持Web应用程序的开发
说明:
不同版本的Struts 2的核心控制器有所不同 , 为了便于描述 , 下面有关核心控制器的名称均采用FilterDispatcher来进行描述 .
需要特别指出的是 , FilterDispatcher在版本2.1.3后被StrutsPrepareAndExecuteFilter替代 .
Struts2框架中的各个模块各自是做什么的?有什么样的功能?处于什么样的地位?
下面跟着系统架构图上的箭头一个一个地来查看
众所周知,Struts框架是基于MVC模式的,基于MVC模式框架核心就是控制器对所有请求进行统一处理。
传统的JSP页面通过GET或POST方法向服务器端的JSP页面提交数据。
采用Struts2框架后,不再提交给服务器端的JSP页面,框架会根据web.xml配置文件和struts.xml配置文件的配置内容,将数据提交给相应的ActionSupport类处理,并返回结果。根据返回的结果(Reuslt)和struts.xml文件中的配置内容(result标签对应的页面),将响应的页面返回给客户端
核心控制器
StrutsPrepareAndExecuteFilter控制器是Struts2框架的核心控制器,该控制器负责拦截所有的用户请求,用户请求到达时,该控制器或过滤用户的请求,如果用户请求以action结尾,改请求将被交给Struts2框架来处理。
当Struts2框架获得了用户请求后,根据请求的名字决定调用那部分业务逻辑组建,例如,对应login请求,Struts2调用login所对应的LoginAction业务来处理该请求。(对应关系请查看struts.xml配置文件的格式以及各标签代表的含义)
业务控制器
Action就是Struts2的业务逻辑控制器,复制处理客户端请求并将结果输出给客户端。对开发人员来说,使用Struts2框架,主要的编码工作就是编写Action类,Struts2并不要求编写的Action类一定要实现Action接口,可以编写一个普通的Java类作为Action类,只要给类含有一个返回字符串的无参的public方法即可。
运行流程解析如下:
任何优秀的MVC框架都会提供一些通用的操作 , 如请求数据的封装 , 类型转换 , 数据校验 , 解析上传的文件,防止表单的多次提交等 . 早期的MVC框架将这些操作统一封装在核心控制器中 , 但是这些通用的操作并不是所有的请求都需要实现,因此导致了框架的灵活性不足,可扩展性低 。
Struts2将它的核心功能放到拦截器中实现,而不是集中在核心控制器中实现 . 把大部分控制器需要完成的工作按功能分开定义,每个拦器器完成一个功能 , 而完成这些这些功能的拦截器可以自由选择、灵活组合 . 需要哪些拦截器,只需要在
struts.xml
指定即可,从而增强了框架的灵活性。
拦截器方法在action执行之前或之后自动执行,从而将通用的操作动态地插入到Action的前后,有利于系统的解耦 . 这种功能的实现类似于我们自己组装的计算机 , 变成了可插拔式 , 需要某个功能就"插入"
一个拦截器 , 不需要某个功能就"拔出"
一个拦截器 . 可以任意的组合Action提供的附加功能 , 而不需要修改Action代码.
如果有一批拦截器经常固定在一起使用,可以将这些执行小粒度功能的拦截器定义成大粒度的拦截器栈(根据不同的应用需求而定义拦截器组合)。从结构上看,拦截器相当于多个拦截器的组合 . 从功能上看,拦截器栈也是拦截器,同样可以和其他拦截器或拦截器一起组合更大粒度的拦截器栈。
通过组合不同的拦截器,我们能够以自己需要的方式来组合Struts2框架的各种功能 ; 通过扩展自己的拦截器 , 我们可以"无限"
扩展 Struts2 框架 .
拦截器围绕着Action 和 Result的执行而执行。拦截器的工作方式如下图所示 , 从图中可以看出 , Struts2拦截器的实现原理和Servlet Filter 的实现原理差不多,以链式执行,对真正要执行的方法进行拦截 .
首先执行Action配置的拦截器,在Action和Result执行之后,拦截器再一次执行,与之前调用相反的顺序,在此链式的执行过程中,任何一个拦截器都可以直接返回,从而终止余下的拦截器、Action及Result的执行。
当ActionInvocation的invoke()方法调用时,开始执行Action配置的第一个拦截器 , 拦截器做出相应的处理后会再次调用ActionInvocation的invoke()方法 , ActionInvocation对象负责跟踪执行过程到状态,并把控制权交个合适的拦截器。
ActionInvocation通过调用拦截器的 intercept()方法将控制转交给拦截器。因此 , 拦截器的执行过程可以看作是一个递归的过程,后续拦截器继续执行,直到最后一个拦截器,invoke()方法会执行Action.
拦截器有一个三阶段、有条件的执行周期 , 如下所示:
一句话总结: 拦截器在Action对象创建之后 , 框架调用Action方法之前执行
下面通过示例代码来体会拦截器的三个阶段 , 如示例1所示:
示例1
MyTimerInterceptor.java
代码如下:
package cn.strutsdemo.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
/**
* 定义拦截器
*/
public class MyTimerInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
//1.执行Action之前的工作:获取开始执行时间
long startTime = System.currentTimeMillis();
System.out.println("执行Action之前的工作,开始时间:"+startTime);
//2.执行后续拦截器或Action
/*if(true){
return "success";//可以随时控制后续拦截器和acion
}*/
String result = actionInvocation.invoke();
//1.执行Action之后的工作:计算并输出执行时间
long endTime = System.currentTimeMillis();
System.out.println("执行Action之前的工作,结束时间:" + endTime);
System.out.println("总共用时:"+(endTime-startTime));
//返回结果字符串
return result;
}
}
说明:
MyTimerInterceptor
拦 还行截器记录动作执行所花费的时间 , 代码很简单 . intercept()方法是拦截器执行的入口方法 , 需要注意的是它接受的是ActionInvocation
实例 .
当
intercept
方法被调用时 , 拦截器开始记录时间(也就是进行预处理的工作) , 接着MyTimerInterceptor
拦截器调用ActionInvocation
实例的invoke()
方法 , 将控制转交给剩余的拦截器和Action , 因为记录执行没有理由终止执行 , 所以MyTimerInterceptor
拦截器总是调用invoke()
方法 .
在调用invoke()
方法后 , MyTimerInterceptor
拦截器等待这个方法的返回值 . 虽然这个结果字符串告诉MyTimerInterceptor
拦截器哪个结果会被呈现 , 但并未之处Action是否执行(可能剩余的拦截器终止了执行操作) . 无论Action是否执行 , invoke()
方法返回时 , 就表明某个结果已经被呈现了(响应页面已经生成完毕).
获得结果字符串之后 ,
MyTimerInterceptor
拦截器记录了执行的用时 , 并在控制台进行了输出 . 此时拦截器可以使用结果字符串做一些操作 , 但是在这里不能停止或者改变响应 . 对于MyTimerInterceptor
拦截器而言 , 它不关心结果 , 因此它不查看返回的结果字符串.
MyTimerInterceptor
拦截器执行到最后 , 返回了从invoke()
方法获得的结果字符串 , 从而使递归又回到了拦截器链 , 使前面的拦截器继续执行它们的后续处理工作
配置拦截需要经过两个步骤:
- 通过
元素来定义拦截器
- 通过
元素来使用拦截器
定义MyTimerAction.java
, 代码如下:
package cn.strutsdemo.action;
import com.opensymphony.xwork2.ActionSupport;
public class MyTimerAction extends ActionSupport {
@Override
public String execute() throws Exception {
System.out.println("执行Action业务");
return SUCCESS;
}
}
使用拦截器的struts.xml
配置文件 , 内容如示例2所示:
<struts>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="myTimer"
class="cn.strutsdemo.interceptor.MyTimerInterceptor"/>
interceptors>
<action name="action"
class="cn.strutsdemo.action.MyTimerAction">
<result>index.jspresult>
<interceptor-ref name="myTimer"/>
<interceptor-ref name="defaultStack"/>
action>
package>
struts>
说明:
在struts.xml
文件中 , 首先在
元素中使用
元素来定义拦截器 ,
元素的name
属性和class
属性是必须填写的 , 前者指定拦截器的名字 , 后者指定拦截器的全限定类名 . 然后在
元素中使用
子元素指定引用的拦截器 .
如果除了希望调用自己编写的拦截器外 , 还希望调用
Struts2
框架定义的默认拦截器 , 就需要将默认拦截器一并添加到元素中 , 有关 Struts 2 默认的拦截器的内容将在后面介绍.
示例2实现了一个简单的拦截器配置 , 运行程序后在控制台将会输出Action的执行用时 , 如下所示:
执行Action之前的工作,开始时间:1571055411023
执行Action业务
执行Action之前的工作,结束时间:1571055411773
总共用时:750
在Struts 2体系结构中可以包含多个拦截器 , 配置过程会相对复杂 , 而拦截器的详细配置过程如图示例3所示:
示例3:
<package name="packName" extends="struts-default" namespace="/manage">
<interceptors>
<interceptor name="interceptorName" class="interceptorClass" />
<interceptor-stack name="interceptorStackName">
<interceptor-ref name="interceptorName|interceptorStackName" />
interceptor-stack>
interceptors>
<default-interceptor-ref name="interceptorName|interceptorStackName" />
<action name="actionName" class="actionClass">
<!—- 为Action指定拦截器引用 -->
<interceptor-ref name="interceptorName|interceptorStackName"/>
action>
package>
修改struts.xml
, 使用拦截器栈 ,内容如下:
<struts>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="myTimer"
class="cn.strutsdemo.interceptor.MyTimerInterceptor"/>
<interceptor-stack name="myTimerStack">
<interceptor-ref name="myTimer"/>
<interceptor-ref name="defaultStack"/>
interceptor-stack>
interceptors>
<action name="action"
class="cn.strutsdemo.action.MyTimerAction">
<result>index.jspresult>
<interceptor-ref name="myTimerStack"/>
action>
package>
struts>
如果想要把多个拦截器组成一个拦截器栈,就需要在 interceptors
元素中使用interceptor-stack
元素定义拦截器栈 , 其中name
属性指定拦截器栈的名称 , 依然使用intercept-ref
元素指定引用的拦截器.
解释:引用拦截器时,Struts2并不区分拦截器和拦截器栈,因此在定义拦截器时,可以引用其他的拦截器栈。
如果配置文件大多数Action都引用相同的拦截器,可以使用默认的拦截器引用。
元素用来定义默认的拦截器引用 , 其name
属性指定引用的拦截器或拦截器栈的名称。
如下所示:
可以继续简化struts.xml
, 内容如下:
<struts>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="myTimer"
class="cn.strutsdemo.interceptor.MyTimerInterceptor"/>
<interceptor-stack name="myTimerStack">
<interceptor-ref name="myTimer"/>
<interceptor-ref name="defaultStack"/>
interceptor-stack>
interceptors>
<default-interceptor-ref name="myTimerStack"/>
<action name="action"
class="cn.strutsdemo.action.MyTimerAction">
<result>index.jspresult>
action>
package>
struts>
在Struts2框架中,内置了很多拦截器供开发人员使用。
提供了框架必不可少的功能,将请求中的数据设置到Action的属性上。负责将请求参数设置成action属性。
staticParams拦截器是将在配置文件中通过
元素的子元素
设置的参数设置到对于到Acton属性中。
servletConfig拦截器提供了一种将源于 ServletAPI的各种对象注入 Action 当中的简洁方法 . Action必须实现相对应的接口,此拦截器才能将对应的Servlet对象注入Action中 .
下列表格中的 接口可以由Action实现,用来取得 Servlet API的不同对象:
接口 | 作用 |
---|---|
ServletContextAware | 设置 ServletContext |
ServletRequestAware | 设置 HttpServletRequest |
ServletResponseAware | 设置 ServletResponse |
ParameterAware | 设置 Map 类型的请求参数 |
RequestAware | 设置 Map 类型的请求 |
SessionAware | 设置 Map 类型的会话 |
ApplicationAware | 设置 Map 类型的应用程序作用域对象 |
解释:为了降低 Action 与 Servlet API 之间的耦合 ,在实际的开发中要尽量减少或者避免在Action中直接访问 Servlet API
fileUpload 拦截器将文件和元数据从多重请求 ( multipart/form-data )转换为常规的请求数据 , 以便它们设置在对应的 Action 属性上,实现文件上传。也就是对文件上传提供支持。
validation 拦截器用于执行数据校验调用验证框架进行数据验证
workflow 拦截器提供当数据校验错误时终止执行流程的功能
exception 拦截器用于捕获异常,并且能根据异常类型将捕获的异常映射到用户自定义的错误页面 . 此拦截器执行的时候应位于所定义的拦截器的第一个。
Struts2 还有很多有用的拦截器,以上这些只是其中的一部分,如果有需要可以查阅 struts-default.xml , 了解更多的Struts 2内置拦截器。
Struts2 除了提供有用的拦截器外,还定义了一些拦截器栈 . 在开发Web应用程序时,可以直接引用这些拦截器栈,不用自己组合拦截器栈。
struts-default.xml
中定义了一个非常重要的拦截器栈——defaultStack拦截器栈 . 此拦截器栈组合了多个拦截器,这些拦截器的顺序经过了精心的设计,能够满足大多数Web应用程序发开发需求。
只要在定义包的时候中继承 struts-default
包,那么defaultStack
拦截器栈将是默认的拦截器引用。
defaultStack
拦截器栈定义如示例4所示:
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="datetime"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browseparam>
interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browseparam>
interceptor-ref>
<interceptor-ref name="debugging"/>
interceptor-stack>
<interceptor-stack name="completeStack">
<interceptor-ref name="defaultStack"/>
interceptor-stack>
<interceptor-stack name="executeAndWaitStack">
<interceptor-ref name="execAndWait">
<param name="excludeMethods">input,back,cancelparam>
interceptor-ref>
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="execAndWait">
<param name="excludeMethods">input,back,cancelparam>
interceptor-ref>
interceptor-stack>
interceptors>
Struts 2 为我们提供了如此丰富的拦截器 , 但是这并不意味着我们失去了创建自定义拦截器的能力 , 恰恰相反 , 自定义Struts 2拦截器是相当容易的一件事
在Struts2中支持自定义的拦截器,Sturts2中所有的拦截器直接或间接地实现接口com.opensymphony.xwork2.intercptor.Interceptor
。
接口提供了三个方法 , 如下所示:
void init():该拦截器被初始化后,在该拦截器执行拦截之前,系统回调该方法对于每个拦截器而言,此方法只执行一次。
void destroy():该方法与 init() 方法对应。在拦截器实例被销毁之前,系统将回调该方法。
String intercept( ActionInvocation invocation)throws Exception : 该方法是用户需要实现的拦截动作 ,该方法会返回一个字符串作为逻辑视图。
除此之外 , 继承
com.opensymphony.xwork2.intercptor.Interceptor.AbstractIntercept
类是更简单的一种实现拦截器的方式。 该类提供了init()和destroy()方法的空实现 , 我们只需要实现 intercept() 方法 , 就可以创建自己的拦截器了。
为租房网开发一个自定义拦截器来判断用户是否登录 .
当用户需要请求执行某个受保护的操作时 , 先检查用户是否登录 . 如果没有登录 , 则向用户显示登录页面 ; 如果用户已经登录 , 则继续操作 . 首先编写权限验证拦截器 , 代码如示例5所示:
示例5:
AuthorizationInterceptor.java
代码如下:
/**
* 实现登录权限的验证
*/
public class AuthorizationInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
Map<String, Object> session = actionInvocation.getInvocationContext().getSession();
System.out.println("权限验证拦截器");
Object login = session.get("login");
if (login == null) {
return Action.LOGIN;
}else{
return actionInvocation.invoke();
}
}
}
定义Default.java
实现关于用户登录失败时需要跳转的页面:
/**
* 提示失败信息的action
*/
public class Default extends ActionSupport {
@Override
public String execute() throws Exception {
System.out.println("执行action");
return "fail";
}
}
定义HouseAction.java
, 实现租户管理的需求:
/**
* 租户信息
*/
public class HouseAction extends ActionSupport {
@Override
public String execute() throws Exception {
//这里只为演示权限检查拦截器的作用,故忽略业务逻辑
return SUCCESS;
}
}
配置文件struts.xml
内容如示例6所示:
示例6:
<struts>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="myAuthorization"
class="cn.strutsdemo.action.AuthorizationInterceptor" />
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="myAuthorization"/>
interceptor-stack>
interceptors>
<default-interceptor-ref name="myStack"/>
<default-action-ref name="defaultAction"/>
<global-results>
<result name="login" type="redirect">/page/login.jspresult>
global-results>
<action name="defaultAction" class="cn.strutsdemo.action.Default">
<result name="fail">/page/fail.jspresult>
action>
<action name="houseAction" class="cn.strutsdemo.action.HouseAction">
<result>/page/manage.jspresult>
action>
package>
struts>
webapp
目录下 , 创建page
目录 , 并在此目录下分别创建login.jsp,fail.jsp,manage.jsp
,代码分别如下:
login.jsp
代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
登录页面
登录页面
fail.jsp
代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
登录失败页面
登录失败
返回
manage.jsp
代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
用户管理
租房页面
部署项目 , 浏览器上输入的地址无论是http://localhost:8080/defaultAction
还是http://localhost:8080/houseAction
,因为session中未设置key("login"
)对应的value , 因此浏览器都会跳转到登录页面
, 但如果直接请求jsp页面 , 程序并没有跳转到登录页面 , 由此可以拦截器只针对Action的请求才会发生作用 .
以上的示例是拦截器对所有的Action都发生了作用 , 因为在
struts.xml
中全局引用默认的拦截器栈 , 代码证据.
若想要拦截器只针对某个action起作用 , 比如登录成功之后跳转到用户管理页面 . 可以修改上面的案例 , 在defaultAction
中获取session , 并为key("login"
)设置对应的值 , 代码如下:
package cn.strutsdemo.action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
/**
* 提示失败信息的action
*/
public class Default extends ActionSupport {
@Override
public String execute() throws Exception {
ActionContext.getContext().getSession().put("login","username");
return "fail";
}
}
修改struts.xml
文件 , 在用户管理请求引用拦截器栈 , 并将默认的拦截器栈注释掉 , 代码如下:
<struts>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="myAuthorization"
class="cn.strutsdemo.action.AuthorizationInterceptor"/>
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="myAuthorization"/>
interceptor-stack>
interceptors>
<default-action-ref name="defaultAction"/>
<global-results>
<result name="login" type="redirect">/page/login.jspresult>
global-results>
<action name="defaultAction" class="cn.strutsdemo.action.Default">
<result name="fail">/page/fail.jspresult>
action>
<action name="houseAction" class="cn.strutsdemo.action.HouseAction">
<result>/page/manage.jspresult>
<interceptor-ref name="myStack"/>
action>
package>
struts>
重新部署项目 , 浏览器中先输入地址:http://localhost:8080/defaultAction
, 然后再输入地址:http://localhost:8080/houseAction
, 因为session中的login
有了对应的value , 所以会放行 . 浏览器会跳转到对应的用户管理页面.
文件上传是一个经常用到的功能 , 在Struts2中已经封装好了上传的组件,只需要在程序中简单设置即可实现文件上传。
在Struts 2 框架中提供了对commons-fileupload
组件的支持 , 并且默认使用该组件实现文件上传 , 因此 , 为了实现文件上传功能 , 我们需要在项目中包含两个jar文件:commons-fileupload-x.x.x.jar
, commons-io-x.x.x.jar
.
说明:jar文件的版本取决于当前工程使用的Struts 2版本
制作一个简单页面 , 用于实现文件上传 , 制作页面的代码如示例7所示:
示例7:
fileinput.jsp
代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
演示单个文件上传
备注:表单必须设置enctype
属性
实现文件上传的代码如示例8所示:
/**
* 实现文件上传
*/
public class UploadAction extends ActionSupport {
private static final Logger LOG = LogManager.getLogger(UploadAction.class);
//封装文件标题属性
private String title;
//封装上传到服务器的文件对象
private File upload;
//封装上传文件的类型
private String uploadContentType;
//封装上传文件的名称
private String uploadFileName;
@Override
public String execute() throws Exception {
//文件的信息:
LOG.info("file:"+this.getUpload());
LOG.info("fileName:"+this.getUploadFileName());
LOG.info("contentType:"+this.getUploadContentType());
//构建要保存文件夹的物理路径(绝对路径)
//ServletActionContext.getServletContext()可以定位到webapp目录下
String realPath =
ServletActionContext.getServletContext().getRealPath("upload");
//构建文件上传的目录
File file = new File(realPath);
//测试此抽象路径名表示的文件或目录是否存在。
//若不存在,创建此抽象路径名指定的目录(包括所有必需但不存在的父目录)
if(!file.exists()){
file.mkdirs();
}
//保存文件
try {
//可以使用时间戳+随机数构建新的文件名
//保证文件名不重复 文件可以重复提交
//uploadFileName =
// System.currentTimeMillis()+"_"+ RandomUtils.nextInt()+
// ".jpg";
File destFile = new File(file,uploadFileName);
FileUtils.copyFile(upload,destFile);
//显示文件的绝对路径
LOG.info(destFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
return SUCCESS;
}
//省略getter setter
}
需要特别强调的是 ,在Action中使用了三个属性来封装文件信息 , 分别如下:
org.apache.struts2.interceptor.FileUploadInterceptor
org.apache.struts2.interceptor.FileUploadInterceptor
有了这三个属性 , 在执行文件上传时就可以直接通过getter方法来获取上传文件的文件名,类型以及文件内容.
Action编写完毕后 , 下一步就需要进行配置 , 配置Action的方式很简单 , 代码如示例9所示:
示例9:
<struts>
<constant name="struts.multipart.maxSize" value="500000000"/>
<constant name="struts.ui.theme" value="simple"/>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<action name="upload"
class="cn.strutsdemo.action.UploadAction">
<interceptor-ref name="defaultStack">
<param name="fileUpload.maximumSize">5000000param>
<param name="fileUpload.allowedTypes">text/plain,
application/vnd.ms-powerpoint,image/pngparam>
<param name="fileUpload.allowedExtensions">png,bmp,jpg,doc,xlsparam>
interceptor-ref>
<result name="success">/success.jspresult>
<result name="input">/error.jspresult>
action>
package>
struts>
附加:contentType类型的设置
文件类型 | 类型设置 |
---|---|
Word | application/msword |
Excel | application/vnd.ms-excel |
PPT | application/vnd.ms-powerpoint |
图片 | image/gif , image/bmp,image/jpeg |
文本文件 | text/plain |
html网页 | text/html |
任意二进制数据 | application/octet-stream |
编写上传成功success.jsp
页面 , 示例代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String path = request.getContextPath();
//${pageContext.request.contextPath }
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
上传成功
您所上传的文件名是:
文件类型:
图片:
编写上传失败error.jsp
页面 , 代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
上传失败
跳转错误页面。。
<%-- --%>
部署项目 , 在结果页面中输入上传文件文件的标题以及文件类型 , 效果如图所示:
点击上传文件 , 效果如图所示:
实现多文件上传的操作非常简单 , 在表单中添加多个相同的name属性的File控件 , 这样当表单提交时 , 将会提交一个数组 , 因此只需要在上传Action中将原本处理单个单个文件的操作改成对数组的操作即可 .
修改实现文件上传的Action , 以满足多文件的上传 , 如示例11所示:
示例11:
UploadAction.java
代码如下:
/**
* 实现多文件上传
*/
public class UploadAction extends ActionSupport {
private static final Logger LOG = LogManager.getLogger(UploadAction.class);
//封装文件标题属性
private String title;
//封装上传到服务器的文件对象
private File[] upload;
//封装上传文件的类型
private String[] uploadContentType;
//封装上传文件的名称
private String[] uploadFileName;
@Override
public String execute() throws Exception {
//文件的信息:
for (File f : this.getUpload()) {
LOG.info("file:"+f);
}
for (String s : this.getUploadFileName()) {
LOG.info("fileName:"+s);
}
for (String s : this.getUploadContentType()) {
LOG.info("contentType:"+s);
}
//构建要保存文件夹的物理路径(绝对路径)
//getRealPath:方法是获取当前项目的绝对磁盘路径
//
String realPath =
ServletActionContext.getServletContext().getRealPath("upload");
//构建文件上传的目录
File file = new File(realPath);
//测试此抽象路径名表示的文件或目录是否存在。
// 若不存在,创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。
if(!file.exists()){
file.mkdirs();
}
//保存文件
try {
for (int i = 0; i < this.getUpload().length; i++) {
//可以使用时间戳+随机数构建新的文件名 文件可以重复提交
//uploadFileName[i] = System.currentTimeMillis()+"_"+ RandomUtils.nextInt()+
//".jpg";
File destFile = new File(file,uploadFileName[i]);
FileUtils.copyFile(this.getUpload()[i],destFile);
LOG.info(destFile.getAbsolutePath());
}
} catch (IOException e) {
e.printStackTrace();
}
return SUCCESS;
}
//省略getter setter
}
struts.xml
内容不用修改 , 修改fileinput.jsp
文件 , 增加一个File控件 , 并指定name跟第一个File控件的name值一致 , 代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
演示多个文件上传
修改上传成功之后的页面(success.jsp
) , 代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
上传成功
您所上传的文件是:<%--
--%>
文件类型:<%--
--%>
图片:
重新部署项目 , 输入文件上传的标题以及选择文件类型 , 效果如下:
提示 : 实现多文件上传 , 还可以采用多个File控件 , 不同name属性的方式 , 不过这样每增加一个File控件 , 都必须相应的增加属性设置 , 会造成Action中属性过多的情况 , 因此不建议使用这种方式 .
Struts2框架提供了 stream 结果类型,该类型的作用就是专门实现文件下载功能
stream结果类型用于实现文件下载功能 , 在实现功能时需要指定一个输入流 , 即inputStream
参数 , 通过这个流就可以读取需要下载的文件内容 .
当然 , 实现文件下载也并非如此简单 , 我们还需要对相关的参数进行配置 , 如MIME类型
,HTTP
请求头信息 , 缓冲区大小等 .
stream
结果类型的配置参数如表:
名称 | 作用 |
---|---|
contentType | 设置发送到浏览器的MIME类型 |
contentLength | 设置文件的大小 |
contentDisposition | 设置响应的HTTP头信息中的Content-Disposition参数的值 |
inputName | 指定Action中提供的inputStream类型的属性名称 |
bufferSize | 设置读取和下载文件时的缓冲区大小 |
Struts 2框架支持文件下载功能 , 下面通过分步的方式实现文件的下载功能.
在Struts 2中实现文件下载时需要用到InputStream
, 所以在文件下载Action中提供一个获得InputStream的方法 , 通过这个输入流就可以获取希望下载的文件内容 .代码如示例12所示:
示例12:
/**
* 实现文件下载
*/
public class FileDownAction extends ActionSupport {
//读取下载文件的目录
private String inputPath;
//下载文件的文件名
private String fileName;
//读取下载文件的输入流
private InputStream inputStream;
//下载文件的类型
private String contentType;
public InputStream getInputStream() throws FileNotFoundException {
//获取下载的绝对路径
String path =
ServletActionContext.getServletContext().getRealPath(inputPath);
//注意是目录+“\”+文件名
return new BufferedInputStream(new FileInputStream(path+File.separator+fileName));
}
@Override
public String execute() throws Exception {
return SUCCESS;
}
public String getInputPath() {
return inputPath;
}
public void setInputPath(String inputPath) {
this.inputPath = inputPath;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
}
在示例12中 , 通过ServletContext上下文得到下载文件的实际路径 , 并构建一个InputStream
输入流实现文件的读取 .
在配置文件中 , 同样要对下载Action进行配置 , 并且要对stream结果类型的参数进行设置 , 代码如示例13所示 :
示例13:
<struts>
<constant name="struts.multipart.maxSize" value="500000000"/>
<constant name="struts.ui.theme" value="simple"/>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<action name="download"
class="cn.strutsdemo.action.FileDownAction">
<param name="inputPath">uploadparam>
<result name="success" type="stream">
<param name="contentType">
application/octet-stream
param>
<param name="contentDisposition">
attachment;filename="${fileName}"
param>
<param name="bufferSize">4096param>
result>
action>
package>
struts>
在配置文件中 , contentType
参数决定了下载文件的类型 . 不同的文件类型对应的参数值也不是相同的 , 如下表所示:
文件类型 | 类型设置 |
---|---|
Word | application/msword |
Excel | application/vnd.ms-excel |
PPT | application/vnd.ms-powerpoint |
图片 | image/gif , image/bmp,image/jpeg |
文本文件 | text/plain |
html网页 | text/html |
任意二进制数据 | application/octet-stream |
提示:通常情况下 , contentType
参数直接设置为application/octet-stream
即可
contentDispoistion
参数由两部分组成 , 前面的部分表示处理文件的形式 , 如attachment
表示在下载时弹出对话框 , 提示用户保存或者直接打开该文件 ; 后一部分filename
表示下载文件的文件名称 . 两部分以";"
进行分隔.
文件下载的处理方式,包括内联(inline)和附件(attachment)两种方式,而附件方式会弹出文件保存对话框,内联的方式浏览器会尝试直接显示文件(仅限IE)。取值为:
attachment;filename=“readme.txt”,表示文件下载的时候保存的名字应为readme.txt。
如果直接写filename=“readme.txt”,那么默认情况是代表inline,浏览器会尝试自动打开它,等价于这样的写法:inline;filename="readme.txt"
Response.addHeader("Content-Disposition","attachment;filename=FileName.txt");
备注:这样浏览器会提示保存还是打开,即使选择打开,也会使用相关联的程序比如记事本打开,而不是IE直接打开了
关于param参数的设置可参考源码:org.apache.struts2.result.StrutsResultSupport
最后我们开发一个简单的下载页面 , 在页面中设置一个超链接 , 并通过超链接请求下载Action , 代码如示例14所示:
示例14:
filedown.jsp
代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
演示文件下载
点击此处下载文件