Top

JAVA Struts2 DAY05

  1. 拦截器HelloWorld
  2. 扩展拦截器HelloWorld
  3. NetCTOSS登录检查
  4. 上传文件

1 拦截器HelloWorld

1.1 问题

写一个拦截器的HelloWorld程序。

1.2 方案

拦截器的使用步骤

  1. 创建拦截器组件,实现接口Interceptor
  2. 在struts.xml中注册拦截器
  3. 在action的配置中引用拦截器

这里我们复用StrutsDay04项目的修改客户功能,并对打开修改页面的action引用自定义的拦截器。

1.3 步骤

步骤一:创建拦截器

创建包interceptor,在该包下创建拦截器组件FirstInterceptor,并实现接口Interceptor,在拦截方法中调用action业务方法,并且在调用action前后分别输出一些内容,以模拟对action请求的拦截。代码如下:

package interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

/**
 * 第一个拦截器
 */
public class FirstInterceptor implements Interceptor {

	@Override
	public void destroy() {

	}

	@Override
	public void init() {

	}

	@Override
	public String intercept(ActionInvocationai) throws Exception {
		System.out.println("FirstInterceptor拦截前...");
		// 执行action业务方法
		ai.invoke();
		System.out.println("FirstInterceptor拦截后...");
		/*
		 * 返回值匹配对应的result,但是一旦代码中调用了
		 * ai.invoke时,则此返回值无效,Struts2会根据
		 * action的返回值匹配result。如果当前代码中没有
		 * 调用ai.invoke,则此返回值生效。
		 * */
		return "error";
	}

}

步骤二:注册拦截器

在struts.xml中,客户package下注册拦截器组件FirstInterceptor,代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>

	<!--客户配置信息 -->
	<package name="customer" 
		namespace="/customer" extends="struts-default">
#cold_bold		<interceptors>
#cold_bold			<!--注册拦截器 -->
#cold_bold			<interceptor name="first"
#cold_bold				class="interceptor.FirstInterceptor"/>
#cold_bold		</interceptors>
		
		<!--打开修改页面 -->
		<action name="toUpdateCustomer" 
			class="action.ToUpdateCustomerAction">
			<result name="success">
				/WEB-INF/customer/update_customer.jsp
			</result>
		</action>
	</package>

</struts>

步骤三:引用拦截器

在修改客户action的配置下,引用已注册的拦截器,代码如下:

<?xmlversion="1.0"encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>

	<!--客户配置信息 -->
	<package name="customer" 
		namespace="/customer" extends="struts-default">
		<interceptors>
			<!--注册拦截器 -->
			<interceptor name="first"
				class="interceptor.FirstInterceptor"/>
		</interceptors>
		
		<!--打开修改页面 -->
		<action name="toUpdateCustomer" 
			class="action.ToUpdateCustomerAction">
#cold_bold			<!--引用拦截器 -->
#cold_bold			<interceptor-ref name="first"/>
			<result name="success">
				/WEB-INF/customer/update_customer.jsp
			</result>
		</action>
	</package>

</struts>

步骤四:测试

为了便于观察拦截器与Action的执行顺序,在Action的构造方法及业务方法中,输出一些内容,代码如下:

package action;

importjava.util.List;

importdao.CustomerDAO;
importentity.City;
importentity.Customer;
importentity.Sex;

public class ToUpdateCustomerAction {

	// output
	private Customer customer; // 客户
	private List<Sex> sexes; // 性别
	private List<City> cities; // 城市

#cold_bold	publicToUpdateCustomerAction() {
#cold_bold		System.out.println("实例化ToUpdateCustomerAction...");
#cold_bold	}
	
	public String execute() {
#cold_bold		System.out.println("调用ToUpdateCustomerAction业务方法...");
		
		CustomerDAOdao = new CustomerDAO();
		// 模拟查询客户
		customer = dao.findById();
		// 模拟查询全部的性别
		sexes = dao.findAllSex();
		// 模拟查询全部的城市
		cities = dao.findAllCities();
		return "success";
	}

	public Customer getCustomer() {
		return customer;
	}

	public void setCustomer(Customer customer) {
		this.customer = customer;
	}

	public List<Sex>getSexes() {
		return sexes;
	}

	public void setSexes(List<Sex> sexes) {
		this.sexes = sexes;
	}

	public List<City>getCities() {
		return cities;
	}

	public void setCities(List<City> cities) {
		this.cities = cities;
	}

}

重新部署并重启tomcat,然后重新访问修改客户页面,控制台输出结果如下图:

图-1

从输出结果中可以看出,拦截器是在实例化Action之后,调用业务方法之前开始调用的。

1.4 完整代码

本案例的完整代码如下所示:

FirstInterceptor完整代码

package interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

/**
 * 第一个拦截器
 */
public class FirstInterceptor implements Interceptor {

	@Override
	public void destroy() {

	}

	@Override
	public void init() {

	}

	@Override
	public String intercept(ActionInvocationai) throws Exception {
		System.out.println("FirstInterceptor拦截前...");
		// 执行action业务方法
		ai.invoke();
		System.out.println("FirstInterceptor拦截后...");
		/*
		 * 返回值匹配对应的result,但是一旦代码中调用了
		 * ai.invoke时,则此返回值无效,Struts2会根据
		 * action的返回值匹配result。如果当前代码中没有
		 * 调用ai.invoke,则此返回值生效。
		 * */
		return "error";
	}

}

ToUpdateCustomerAction完整代码:

package action;

importjava.util.List;

importdao.CustomerDAO;
importentity.City;
importentity.Customer;
importentity.Sex;

public class ToUpdateCustomerAction {

	// output
	private Customer customer; // 客户
	private List<Sex> sexes; // 性别
	private List<City> cities; // 城市

	publicToUpdateCustomerAction() {
		System.out.println("实例化ToUpdateCustomerAction...");
	}
	
	public String execute() {
		System.out.println("调用ToUpdateCustomerAction业务方法...");
		
		CustomerDAOdao = new CustomerDAO();
		// 模拟查询客户
		customer = dao.findById();
		// 模拟查询全部的性别
		sexes = dao.findAllSex();
		// 模拟查询全部的城市
		cities = dao.findAllCities();
		return "success";
	}

	public Customer getCustomer() {
		return customer;
	}

	public void setCustomer(Customer customer) {
		this.customer = customer;
	}

	public List<Sex>getSexes() {
		return sexes;
	}

	public void setSexes(List<Sex> sexes) {
		this.sexes = sexes;
	}

	public List<City>getCities() {
		return cities;
	}

	public void setCities(List<City> cities) {
		this.cities = cities;
	}

}

struts.xml完整代码:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>

	<!-- 客户配置信息 -->
	<package name="customer" 
		namespace="/customer" extends="struts-default">
		<interceptors>
			<!-- 注册拦截器 -->
			<interceptor name="first"
				class="interceptor.FirstInterceptor"/>
		</interceptors>
		
		<!-- 打开修改页面 -->
		<action name="toUpdateCustomer" 
			class="action.ToUpdateCustomerAction">
			<!-- 引用拦截器 -->
			<interceptor-ref name="first"/>
			<result name="success">
				/WEB-INF/customer/update_customer.jsp
			</result>
		</action>
	</package>

</struts>

2 扩展拦截器HelloWorld

2.1 问题

扩展拦截器HelloWorld示例,练习使用拦截器栈,并观察多个拦截器的执行顺序。

2.2 方案

创建一个新的拦截器,与第一个拦截器打包成栈,然后让修改客户的action引用这个拦截器栈,并观察控制台中这两个拦截器与Action的执行顺序。

2.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:创建一个新的拦截器

创建一个新的拦截器组件SecondInterceptor,代码如下:

package interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

/**
 * 第二个拦截器
 */
public class SecondInterceptor implements Interceptor {

	@Override
	public void destroy() {

	}

	@Override
	public void init() {

	}

	@Override
	public String intercept(ActionInvocationai) throws Exception {
		System.out.println("SecondInterceptor拦截前...");
		ai.invoke();
		System.out.println("SecondInterceptor拦截后...");
		return "error";
	}

}

步骤二:注册新拦截器,并打包成栈

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>

	<!--客户配置信息 -->
	<package name="customer" 
		namespace="/customer" extends="struts-default">
		<interceptors>
			<!--注册拦截器 -->
			<interceptor name="first"
				class="interceptor.FirstInterceptor"/>
#cold_bold			<interceptor name="second" 
#cold_bold				class="interceptor.SecondInterceptor"/>	
#cold_bold			<!--注册拦截器栈 -->
#cold_bold			<interceptor-stack name="myStack">
#cold_bold				<interceptor-ref name="first"/>
#cold_bold				<interceptor-ref name="second"/>
#cold_bold			</interceptor-stack>
		</interceptors>
		
		<!--打开修改页面 -->
		<action name="toUpdateCustomer" 
			class="action.ToUpdateCustomerAction">
			<!--引用拦截器 -->
			<interceptor-ref name="first"/>
			<result name="success">
				/WEB-INF/customer/update_customer.jsp
			</result>
		</action>
	</package>

</struts>

步骤三:引用拦截器栈

可以在action的配置下引用拦截器,也可以给一个包下所有的action统一设置默认使用的拦截器,代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>

	<!--客户配置信息 -->
	<package name="customer" 
		namespace="/customer" extends="struts-default">
		<interceptors>
			<!--注册拦截器 -->
			<interceptor name="first"
				class="interceptor.FirstInterceptor"/>
			<interceptor name="second" 
				class="interceptor.SecondInterceptor"/>	
			<!--注册拦截器栈 -->
			<interceptor-stack name="myStack">
				<interceptor-ref name="first"/>
				<interceptor-ref name="second"/>
			</interceptor-stack>
		</interceptors>
#cold_bold		<!--设置当前包下所有Action默认引用的拦截器 -->
#cold_bold		<default-interceptor-ref name="myStack"/>
		
		<!--打开修改页面 -->
		<action name="toUpdateCustomer" 
			class="action.ToUpdateCustomerAction">
#cold_bold			<!--引用拦截器 -->
#cold_bold			<!--<interceptor-ref name="first"/> -->
			<result name="success">
				/WEB-INF/customer/update_customer.jsp
			</result>
		</action>
	</package>

</struts>

步骤四:测试

重新部署项目并重启tomcat,重新访问修改客户页面时,控制台输出结果如下图:

图-2

从图中可以看出,多个拦截器拦截Action的顺序满足先进后出的原则,其顺序可以按照下图来理解和记忆:

图-3

2.4 完整代码

本案例的完整代码如下。

SecondInterceptor完整代码:

package interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

/**
 * 第二个拦截器
 */
public class SecondInterceptor implements Interceptor {

	@Override
	public void destroy() {

	}

	@Override
	public void init() {

	}

	@Override
	public String intercept(ActionInvocationai) throws Exception {
		System.out.println("SecondInterceptor拦截前...");
		ai.invoke();
		System.out.println("SecondInterceptor拦截后...");
		return "error";
	}

}

struts.xml完整代码:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>

	<!-- 客户配置信息 -->
	<package name="customer" 
		namespace="/customer" extends="struts-default">
		<interceptors>
			<!-- 注册拦截器 -->
			<interceptor name="first"
				class="interceptor.FirstInterceptor"/>
			<interceptor name="second" 
				class="interceptor.SecondInterceptor"/>	
			<!-- 注册拦截器栈 -->
			<interceptor-stack name="myStack">
				<interceptor-ref name="first"/>
				<interceptor-ref name="second"/>
			</interceptor-stack>
		</interceptors>
		<!-- 设置当前包下所有Action默认引用的拦截器 -->
		<default-interceptor-ref name="myStack"/>
		
		<!-- 打开修改页面 -->
		<action name="toUpdateCustomer" 
			class="action.ToUpdateCustomerAction">
			<!-- 引用拦截器 -->
			<!--<interceptor-ref name="first"/> -->
			<result name="success">
				/WEB-INF/customer/update_customer.jsp
			</result>
		</action>
	</package>

</struts>

3 NetCTOSS登录检查

3.1 问题

当前NetCTOSS项目中,我们可以直接在地址栏中输入地址访问资费模块的功能,这使得登录功能形同虚设。要求在没登录时不允许直接访问资费模块的任何action,登录后才可以访问。

3.2 方案

需要在访问资费action时进行校验,判断是否已进行了登录。这种判断每一个action都要处理,甚至以后有更多业务模块时也要做这样的事情,因此是action通用的业务逻辑,可以使用拦截器来处理。

我们可以创建拦截器组件,在拦截方法中调用action业务方法之前,判断是否进行过登录,从而确定是否可以继续访问该action,然后给每一个业务模块注册该action即可实现此需求。

3.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:创建登录检查拦截器

创建com.netctoss.interceptor包,在该包下创建登录检查拦截器LoginInterceptor,代码如下:

packagecom.netctoss.interceptor;

importjava.util.Map;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

/**
 * 登录检查拦截器,用于检查用户是否登录。
 */
public class LoginInterceptor implements Interceptor {

	@Override
	public void destroy() {

	}

	@Override
	public void init() {

	}

	@Override
	public String intercept(ActionInvocationai) throws Exception {
		// 获取session
		Map<String, Object> session = ai.getInvocationContext().getSession();
		// 从session中读取登录信息
		Object admin = session.get("admin");
		// 如果登录信息为空,则踢回登录页,而不调用业务Action
		if (admin == null) {
			return "login";
		} else {
			// 如果登录信息不为空,则调用业务Action
			returnai.invoke();
		}
	}

}

步骤二:使用登录检查拦截器

在struts.xml中注册该拦截器,然后将其与默认拦截器栈打包成新的拦截器栈loginStack,再通过default-interceptor-ref标记设置action默认使用的拦截器为loginStack。在检查到未登录时,需要找到名为login的result,跳转回登录页面,这个result就需要是所有action公用的,可以在global-results标记下定义这个result,来实现被action复用的目的。代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
#cold_bold	<!--公共的包,封装了通用的拦截器、通用的result -->
#cold_bold	<package name="netctoss" extends="json-default">
#cold_bold		<interceptors>
#cold_bold			<!--登录检查拦截器 -->
#cold_bold			<interceptor name="loginInterceptor"
#cold_bold				class="com.netctoss.interceptor.LoginInterceptor"/>
#cold_bold			<!--登录检查拦截器栈 -->
#cold_bold			<interceptor-stack name="loginStack">
#cold_bold				<interceptor-ref name="loginInterceptor"/>
#cold_bold				<!--不要丢掉默认的拦截器栈,里面有很多Struts2依赖的拦截器 -->
#cold_bold				<interceptor-ref name="defaultStack"/>
#cold_bold			</interceptor-stack>
#cold_bold		</interceptors>
#cold_bold		<!--设置action默认引用的拦截器 -->
#cold_bold		<default-interceptor-ref name="loginStack"/>
#cold_bold		<!--全局的result,包下所有的action都可以共用 -->
#cold_bold		<global-results>
#cold_bold			<!--跳转到登录页面的result -->
#cold_bold			<result name="login" type="redirectAction">
#cold_bold				<param name="namespace">/login</param>
#cold_bold				<param name="actionName">toLogin</param>
#cold_bold			</result>
#cold_bold		</global-results>
#cold_bold	</package>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
#cold_bold	<package name="cost" namespace="/cost" extends="netctoss">
		<!--查询资费数据 -->
		<action name="findCost" class="com.netctoss.action.FindCostAction">
			<!-- 
				正常情况下跳转到资费列表页面。
				一般一个模块的页面要打包在一个文件夹下,并且文件夹以模块名命名。
			 -->
			<result name="success">
				/WEB-INF/cost/find_cost.jsp
			</result>
			<!-- 
				错误情况下,跳转到错误页面。
				错误页面可以被所有模块复用,因此放在main下,
				该文件夹用于存放公用的页面。
			 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
		<!--删除资费 -->
		<action name="deleteCost" 
			class="com.netctoss.action.DeleteCostAction">
			<!--删除完之后,重定向到查询action -->
			<result name="success" type="redirectAction">
				findCost
			</result>
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
		<!--打开资费新增页 -->
		<action name="toAddCost">
			<result name="success">
				/WEB-INF/cost/add_cost.jsp
			</result>
		</action>
		<!--资费名唯一性校验 -->
		<action name="checkCostName" 
			class="com.netctoss.action.CheckCostNameAction">
			<!--使用json类型的result把结果输出给回调函数 -->
			<result name="success" type="json">
				<param name="root">info</param>
			</result>
		</action>
		<!--打开修改页面 -->
		<action name="toUpdateCost" 
			class="com.netctoss.action.ToUpdateCostAction">
			<result name="success">
				/WEB-INF/cost/update_cost.jsp
			</result>
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
	</package>
	
	<!--登录模块 -->
	<package name="login" namespace="/login" extends="struts-default">
		<!-- 
			打开登录页面:
			1、action的class属性可以省略,省略时Struts2
			会自动实例化默认的Action类ActionSupport,
			该类中有默认业务方法execute,返回success。
			2、action的method属性可以省略,省略时Struts2
			会自动调用execute方法。
		-->
		<action name="toLogin">
			<result name="success">
				/WEB-INF/main/login.jsp
			</result>
		</action>
		<!--登录校验 -->
		<action name="login" class="com.netctoss.action.LoginAction">
			<!--校验成功,跳转到系统首页 -->
			<result name="success">
				/WEB-INF/main/index.jsp
			</result>
			<!--登录失败,跳转回登录页面 -->
			<result name="fail">
				/WEB-INF/main/login.jsp
			</result>
			<!--报错,跳转到错误页面 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
		<!--生成验证码 -->
		<action name="createImage" class="com.netctoss.action.CreateImageAction">
			<!--使用stream类型的result -->
			<result name="success" type="stream">
				<!--指定输出的内容 -->
				<param name="inputName">imageStream</param>
			</result>
		</action>
	</package>
	
</struts>

注意:上述代码中将拦截器以及公共的result提出来放到了公共的包下,然后让资费的包继承此包,这样做的好处是可以复用这些公共的代码,因为将来还会有其他的业务模块,他们也需要使用这些代码。

步骤三:测试

部署项目并重启tomcat,然后直接访问资费模块的某action,会发现页面自动跳转到了登录页,这是因为拦截器的作用所致。在正常登录之后,再去输入地址访问资费模块某action时,就可以正常访问了。

3.4 完整代码

本案例的完整代码如下所示:

LoginInterceptor完整代码

packagecom.netctoss.interceptor;

importjava.util.Map;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

/**
 * 登录检查拦截器,用于检查用户是否登录。
 */
public class LoginInterceptor implements Interceptor {

	@Override
	public void destroy() {

	}

	@Override
	public void init() {

	}

	@Override
	public String intercept(ActionInvocationai) throws Exception {
		// 获取session
		Map<String, Object> session = ai.getInvocationContext().getSession();
		// 从session中读取登录信息
		Object admin = session.get("admin");
		// 如果登录信息为空,则踢回登录页,而不调用业务Action
		if (admin == null) {
			return "login";
		} else {
			// 如果登录信息不为空,则调用业务Action
			returnai.invoke();
		}
	}

}

struts.xml完整代码

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
	<!-- 公共的包,封装了通用的拦截器、通用的result -->
	<package name="netctoss" extends="json-default">
		<interceptors>
			<!-- 登录检查拦截器 -->
			<interceptor name="loginInterceptor"
				class="com.netctoss.interceptor.LoginInterceptor"/>
			<!-- 登录检查拦截器栈 -->
			<interceptor-stack name="loginStack">
				<interceptor-ref name="loginInterceptor"/>
				<!-- 不要丢掉默认的拦截器栈,里面有很多Struts2依赖的拦截器 -->
				<interceptor-ref name="defaultStack"/>
			</interceptor-stack>
		</interceptors>
		<!-- 设置action默认引用的拦截器 -->
		<default-interceptor-ref name="loginStack"/>
		<!-- 全局的result,包下所有的action都可以共用 -->
		<global-results>
			<!-- 跳转到登录页面的result -->
			<result name="login" type="redirectAction">
				<param name="namespace">/login</param>
				<param name="actionName">toLogin</param>
			</result>
		</global-results>
	</package>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
	<package name="cost" namespace="/cost" extends="netctoss">
		<!-- 查询资费数据 -->
		<action name="findCost" class="com.netctoss.action.FindCostAction">
			<!-- 
				正常情况下跳转到资费列表页面。
				一般一个模块的页面要打包在一个文件夹下,并且文件夹以模块名命名。
			 -->
			<result name="success">
				/WEB-INF/cost/find_cost.jsp
			</result>
			<!-- 
				错误情况下,跳转到错误页面。
				错误页面可以被所有模块复用,因此放在main下,
				该文件夹用于存放公用的页面。
			 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
		<!-- 删除资费 -->
		<action name="deleteCost" 
			class="com.netctoss.action.DeleteCostAction">
			<!-- 删除完之后,重定向到查询action -->
			<result name="success" type="redirectAction">
				findCost
			</result>
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
		<!-- 打开资费新增页 -->
		<action name="toAddCost">
			<result name="success">
				/WEB-INF/cost/add_cost.jsp
			</result>
		</action>
		<!-- 资费名唯一性校验 -->
		<action name="checkCostName" 
			class="com.netctoss.action.CheckCostNameAction">
			<!-- 使用json类型的result把结果输出给回调函数 -->
			<result name="success" type="json">
				<param name="root">info</param>
			</result>
		</action>
		<!--  打开修改页面 -->
		<action name="toUpdateCost" 
			class="com.netctoss.action.ToUpdateCostAction">
			<result name="success">
				/WEB-INF/cost/update_cost.jsp
			</result>
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
	</package>
	
	<!-- 登录模块 -->
	<package name="login" namespace="/login" extends="struts-default">
		<!-- 
			打开登录页面:
			1、action的class属性可以省略,省略时Struts2
			   会自动实例化默认的Action类ActionSupport,
			   该类中有默认业务方法execute,返回success。
			2、action的method属性可以省略,省略时Struts2
			   会自动调用execute方法。
		-->
		<action name="toLogin">
			<result name="success">
				/WEB-INF/main/login.jsp
			</result>
		</action>
		<!-- 登录校验 -->
		<action name="login" class="com.netctoss.action.LoginAction">
			<!-- 校验成功,跳转到系统首页 -->
			<result name="success">
				/WEB-INF/main/index.jsp
			</result>
			<!-- 登录失败,跳转回登录页面 -->
			<result name="fail">
				/WEB-INF/main/login.jsp
			</result>
			<!-- 报错,跳转到错误页面 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
		<!-- 生成验证码 -->
		<action name="createImage" class="com.netctoss.action.CreateImageAction">
			<!-- 使用stream类型的result -->
			<result name="success" type="stream">
				<!-- 指定输出的内容 -->
				<param name="inputName">imageStream</param>
			</result>
		</action>
	</package>
	
</struts>

4 上传文件

4.1 问题

在Struts2框架下实现文件上传。

4.2 方案

Struts2提供了拦截器可以自动实现文件上传,并且该拦截器存在于defaultStack中,是每个action默认使用的。

我们可以复用StrutsDay04的项目,在此基础上做文件上传示例。

4.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:打开上传页面

在struts.xml中,配置打开上传页面的action,代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>

	<!--客户配置信息 -->
	<package name="customer" 
		namespace="/customer" extends="struts-default">
		<interceptors>
			<!--注册拦截器 -->
			<interceptor name="first"
				class="interceptor.FirstInterceptor"/>
			<interceptor name="second" 
				class="interceptor.SecondInterceptor"/>	
			<!--注册拦截器栈 -->
			<interceptor-stack name="myStack">
				<interceptor-ref name="first"/>
				<interceptor-ref name="second"/>
			</interceptor-stack>
		</interceptors>
		<!--设置当前包下所有Action默认引用的拦截器 -->
		<default-interceptor-ref name="myStack"/>
		
		<!--打开修改页面 -->
		<action name="toUpdateCustomer" 
			class="action.ToUpdateCustomerAction">
			<!--引用拦截器 -->
			<!--<interceptor-ref name="first"/> -->
			<result name="success">
				/WEB-INF/customer/update_customer.jsp
			</result>
		</action>
	</package>
	
#cold_bold	<!--文件上传示例 -->
#cold_bold	<package name="demo" namespace="/demo" extends="struts-default">
#cold_bold		<!--打开上传文件页面 -->
#cold_bold		<action name="toUpload">
#cold_bold			<result name="success">/WEB-INF/jsp/upload.jsp</result>
#cold_bold		</action>
#cold_bold	</package>	

</struts>

在WEB-INF下创建文件夹jsp,并在此文件夹下创建文件上传页面upload.jsp,代码如下:

<%@page pageEncoding="utf-8"%>
<html>
<head></head>
<body>
	<form action="" method="post">
		<input type="file" name="some" />
		<input type="submit" value="提交" />
	</form>
</body>
</html>

重新部署并重启tomcat,访问此页面,效果如下图:

图-4

步骤二:导包

上传文件需要依赖新的包 commons-io-1.3.2.jar,将其引入到项目中后,包结构如下图:

图-5

步骤三:Action中处理上传请求

由于Action需要接收拦截器传入的临时文件,并对临时文件进行复制,因此需要提供一个文件操作的工具类FileUtil,代码如下:

packageutil;

importjava.io.BufferedInputStream;
importjava.io.BufferedOutputStream;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileOutputStream;
importjava.io.IOException;

/**
 * 文件处理工具
 */
public class FileUtil {

	public static boolean copy(File src, File dest) {
		BufferedInputStreambis = null;
		BufferedOutputStreambos = null;
		try {
			bis = new BufferedInputStream(new FileInputStream(src));
			bos = new BufferedOutputStream(new FileOutputStream(dest));
			byte[] bts = new byte[1024];
			intlen = -1;
			while ((len = bis.read(bts)) != -1) {
				bos.write(bts, 0, len);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		} finally {
			if (bis != null) {
				try {
					bis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}
}

然后创建上传文件Action类UploadAction,并根据拦截器传入的临时文件,将其赋值到某路径下。代码如下:

package action;

importjava.io.File;
import org.apache.struts2.ServletActionContext;
importutil.FileUtil;

/**
 * 文件上传Action
 */
public class UploadAction {

	/**
	 * 接收拦截器传入的临时文件
	 */
	private File some;
	/**
	 * 接收拦截器注入的原始文件名
	 */
	private String someFileName;

	publicString execute() {
		if (some == null)
			return "error";

		// 将文件放于项目部署路径下的upload文件夹下
		String path = "WEB-INF/upload/" + someFileName;
		// 根据相对部署路径计算完整路径
		path = ServletActionContext.getServletContext().getRealPath(path);
		// 将临时文件复制到上述路径下
		FileUtil.copy(some, new File(path));

		return "success";
	}

	public File getSome() {
		return some;
	}

	public void setSome(File some) {
		this.some = some;
	}

	public String getSomeFileName() {
		returnsomeFileName;
	}

	public void setSomeFileName(String someFileName) {
		this.someFileName = someFileName;
	}

}

上述代码中,用意是将文件复制部署的项目路径下的WEB-INF/upload文件夹下,这样就可以不在项目中写死上传文件的路径了,而是随着项目部署位置的不同而自动变化,比较灵活。因此这里要求我们在项目中的WEB-INF下创建新的文件夹upload,创建后该文件夹结构如下图:

图-6

步骤四:struts.xml中配置上传文件action

在struts.xml中配置该action,并且设置文件上传的最大值,代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>

	<!--客户配置信息 -->
	<package name="customer" 
		namespace="/customer" extends="struts-default">
		<interceptors>
			<!--注册拦截器 -->
			<interceptor name="first"
				class="interceptor.FirstInterceptor"/>
			<interceptor name="second" 
				class="interceptor.SecondInterceptor"/>	
			<!--注册拦截器栈 -->
			<interceptor-stack name="myStack">
				<interceptor-ref name="first"/>
				<interceptor-ref name="second"/>
			</interceptor-stack>
		</interceptors>
		<!--设置当前包下所有Action默认引用的拦截器 -->
		<default-interceptor-ref name="myStack"/>
		
		<!--打开修改页面 -->
		<action name="toUpdateCustomer" 
			class="action.ToUpdateCustomerAction">
			<!--引用拦截器 -->
			<!--<interceptor-ref name="first"/> -->
			<result name="success">
				/WEB-INF/customer/update_customer.jsp
			</result>
		</action>
	</package>
	
#cold_bold	<!--设置上传文件最大值 -->
#cold_bold	<constant name="struts.multipart.maxSize" value="5000000"/>
	
	<!--文件上传示例 -->
	<package name="demo" namespace="/demo" extends="struts-default">
		<!--打开上传文件页面 -->
		<action name="toUpload">
			<result name="success">/WEB-INF/jsp/upload.jsp</result>
		</action>
#cold_bold		<!--上传文件 -->
#cold_bold		<action name="upload" class="action.UploadAction">
#cold_bold			<result name="success">/WEB-INF/jsp/ok.jsp</result>
#cold_bold			<result name="error">/WEB-INF/jsp/error.jsp</result>
#cold_bold		</action>
	</package>	

</struts>

步骤五:JSP

上述配置中,如果上传成功则转至ok.jsp,该页面代码如下:

<html>
<head></head>
<body>
	<h1>OK!</h1>
</body>
</html>

如果上传失败则转至error.jsp,该页面代码如下:

<html>
<head></head>
<body>
	<h1 style="color:red">Error!</h1>
</body>
</html>

修改上传文件页面upload.jsp的表单属性值,代码如下:

<%@page pageEncoding="utf-8"%>
<html>
<head></head>
<body>
#cold_bold	<!-- 
#cold_bold		上传文件对表单有2个要求:
#cold_bold		 1、method="post"
#cold_bold		 2、enctype="multipart/form-data"
#cold_bold	 -->
#cold_bold	<form action="upload" method="post" enctype="multipart/form-data">
		<input type="file" name="some" />
		<input type="submit" value="提交" />
	</form>
</body>
</html>

步骤六:测试

重新部署项目并重启tomcat,打开上传文件页面,选择一个文件后点提交按钮,文件被正确上传至项目部署路径下的WEB-INF/upload文件夹下了,效果如下图:

图-7

4.4 完整代码

本案例的完整代码如下。

FileUtil完整代码:

packageutil;

importjava.io.BufferedInputStream;
importjava.io.BufferedOutputStream;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileOutputStream;
importjava.io.IOException;

/**
 * 文件处理工具
 */
public class FileUtil {

	public static boolean copy(File src, File dest) {
		BufferedInputStreambis = null;
		BufferedOutputStreambos = null;
		try {
			bis = new BufferedInputStream(new FileInputStream(src));
			bos = new BufferedOutputStream(new FileOutputStream(dest));
			byte[] bts = new byte[1024];
			intlen = -1;
			while ((len = bis.read(bts)) != -1) {
				bos.write(bts, 0, len);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		} finally {
			if (bis != null) {
				try {
					bis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}
}

struts.xml完整代码:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>

	<!-- 客户配置信息 -->
	<package name="customer" 
		namespace="/customer" extends="struts-default">
		<interceptors>
			<!-- 注册拦截器 -->
			<interceptor name="first"
				class="interceptor.FirstInterceptor"/>
			<interceptor name="second" 
				class="interceptor.SecondInterceptor"/>	
			<!-- 注册拦截器栈 -->
			<interceptor-stack name="myStack">
				<interceptor-ref name="first"/>
				<interceptor-ref name="second"/>
			</interceptor-stack>
		</interceptors>
		<!-- 设置当前包下所有Action默认引用的拦截器 -->
		<default-interceptor-ref name="myStack"/>
		
		<!-- 打开修改页面 -->
		<action name="toUpdateCustomer" 
			class="action.ToUpdateCustomerAction">
			<!-- 引用拦截器 -->
			<!--<interceptor-ref name="first"/> -->
			<result name="success">
				/WEB-INF/customer/update_customer.jsp
			</result>
		</action>
	</package>
	
	<!-- 设置上传文件最大值 -->
	<constant name="struts.multipart.maxSize" value="5000000"/>
	
	<!-- 文件上传示例 -->
	<package name="demo" namespace="/demo" extends="struts-default">
		<!-- 打开上传文件页面 -->
		<action name="toUpload">
			<result name="success">/WEB-INF/jsp/upload.jsp</result>
		</action>
		<!-- 上传文件 -->
		<action name="upload" class="action.UploadAction">
			<result name="success">/WEB-INF/jsp/ok.jsp</result>
			<result name="error">/WEB-INF/jsp/error.jsp</result>
		</action>
	</package>	

</struts>

UploadAction完整代码:

package action;

importjava.io.File;
import org.apache.struts2.ServletActionContext;
importutil.FileUtil;

/**
 * 文件上传Action
 */
public class UploadAction {

	/**
	 * 接收拦截器传入的临时文件
	 */
	private File some;
	/**
	 * 接收拦截器注入的原始文件名
	 */
	private String someFileName;

	publicString execute() {
		if (some == null)
			return "error";

		// 将文件放于项目部署路径下的upload文件夹下
		String path = "WEB-INF/upload/" + someFileName;
		// 根据相对部署路径计算完整路径
		path = ServletActionContext.getServletContext().getRealPath(path);
		// 将临时文件复制到上述路径下
		FileUtil.copy(some, new File(path));

		return "success";
	}

	public File getSome() {
		return some;
	}

	public void setSome(File some) {
		this.some = some;
	}

	public String getSomeFileName() {
		returnsomeFileName;
	}

	public void setSomeFileName(String someFileName) {
		this.someFileName = someFileName;
	}

}

upload.jsp完整代码:

<%@page pageEncoding="utf-8"%>
<html>
<head></head>
<body>
	<!-- 
		 上传文件对表单有2个要求:
		 1、method="post"
		 2、enctype="multipart/form-data"
	 -->
	<form action="upload" method="post" enctype="multipart/form-data">
		<input type="file" name="some" />
		<input type="submit" value="提交" />
	</form>
</body>
</html>

ok.jsp完整代码:

<html>
<head></head>
<body>
	<h1>OK!</h1>
</body>
</html>

error.jsp完整代码:

<html>
<head></head>
<body>
	<h1 style="color:red">Error!</h1>
</body>
</html>