- 浏览: 16806 次
- 性别:
- 来自: 重庆
最近访客 更多访客>>
最新评论
-
starshine_java:
朋友... 我开发 C++ 与 Flex 通信。你的第四种方法 ...
Flex与VC++交互通信
struts2的学习心得,稍做一些记录 ,如下:
1. 首先,struts2实在WebWork2基础上发展而来的,同时也属于MVC框架。struts2 和struts1的一些区别,你可以自己去揣摩,我自己看了一些资料,总结几点如下:
a. struts2不像struts1一样与servletAPI和strutsAPI之间存在紧密的耦合性,既不依赖于strutsAPI的Action,也不依赖于servletAPI的HttpServletRequest 和HttpServletResponse类,也可以说它不像struts1属于侵入式设计,而它属于非侵入的设计;
回顾一下struts1的Action:
public class TestAction extends Action{
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest, HttpServletResponse response) throws Exception
{
if (...)
return mapping.xxx;
}
b. struts2提供了拦截器,利用拦截器可以进行AOP(切面)编程,实现如权限拦截等功能;而要实现struts1的拦截功能,相对比较复杂;
c. struts2提供了类型转换器,可以将一些特殊的请求参数转换为所需的类型;而struts1中如果要实现类似功能,就必须向struts1底层实现BeanUtils注册类型转换器才行;
d. struts2除了提供JSP,还提供了如freeMarker ,Velocity等其他页面表现技术;
e. struts2提供了对指定方法的校验,解决了struts1的校验之痛,因为struts1中的校验是针对所有的方法,而不能单独提出对某些方法的校验;
f. 提供了几种实用的国际化资源文件管理方式,如全局的资源文件,包范围和Action范围,这样就更加灵活;
2. 对于struts2的搭建,首先需要下载struts2的资源包,可以在apache的官网上面下载相关资料:http://struts.apache.org/download.cgi ; 然后对其进行解压缩;其中常用的jar包和一些支持第三方的jar包都可以在lib目录下面找到,同时apps包提供了一些例子,并且我们在配置环境时,所用到的web.xml和struts.xml配置文件都可以从这些app中拷贝过来;步骤有3,如下:
a. 在MyEclipse里面新建一个web project,然后在项目属性中的java build path里的Libraries tab里面添加以下jar包作为Referenced Libraries:(我使用的是struts2.2.1)
struts2-core-2.2.1.jar
xwork-core-2.2.1.jar
ognl-3.0.jar
commons-logging-1.0.4.jar
commons-fileupload-1.2.1.jar
commons-io-1.3.2.jar (有些童鞋会忘记这个包)
freemarker-2.3.16.jar
javassist-3.7.ga.jar (这个包也很关键,据说是在2.1之后需要使用到,而且在lib包里面找不到,你需要在apps里面的struts2-blank-2.2.1.war里面的lib中copy出来)
b. 上面是最基本的struts2所需jar包,接下来进行struts.xml和web.xml配置;可以拷贝apps里面项目的web.xml覆盖自建项目中WebRoot/WEB-INF的web.xml,然后去掉其他的一些配置,剩下内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
这里想说两句的是,struts1框架是通过Servlet来启动的,而struts2环境是通过Filter来启动的,所以可以看到上面配置中对filter进行了配置,该类名StrutsPrepareAndExecuteFilter也可以看出是为启动struts2做准备;另外,这个filter类在2.1.13之前是采用另一个类,叫做org.apache.struts2.dispatcher.FilterDispatcher,现在它已经过时了;
c. 配置上面的web.xml,其中的filter类的init()方法会在服务器容器启动项目的时候,调用类路径下面的struts.xml文件,然后把struts.xml中的内容以javaBean的形式加载到内存里面,所以这个struts.xml文件的读取是一次性的,而不是每次请求都让filter去读取;我觉得这一点在编程中很重要,因为使用了cache的方式,可以提高访问效率,但是值得注意的是,如果大量使用服务器内存cache,可能使服务器资源大量消耗,影响其运作效率;可以从apps的项目中copy struts.xml来编辑一下,且看struts.xml最基本配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
</struts>
完成以上3个步骤,就可以在服务器容器中启动所建项目了。
3. struts2中struts.xml的action配置:
struts2中的action配置采用了package的形式,这样方便于被其他的package继承,但是它跟java class的package没有关系,也不能体现出层的概念,但是要求package的name属性必须唯一,可以将同一模块或者同一种用途的action放在一个package里面;每个package必须继承struts-default包,因为此包是struts2的核心包,struts2
的很多核心功能都是通过拦截器来实现的,而这个包定义了拦截器和Result类型; 其中的namespace属性是定义被访问的action的命名空间,此ns属性解决了struts1中大量重复定义相同action路径的问题,比如这些action, action name如下:/user/user-page/left,/user/user-page/right,/user/user-page/up,/user/user-page/down, 在struts2中可以如下定义:namespace为/user/user-page,而各个action命名为 letf, right, up, down,减少了重复配置的代码量;可以不指定ns,或者ns的名字指定为"",这样他就成了一个默认命名空间,如果一个请求在指定的ns中没有找到对应的Action,则会到这种默认的命名空间去寻找action;其中的method和result就是action的被访问方法名和返回的结果:
<struts>
<package name="firstPackage" namespace="/hello" extends="struts-default">
<action name="helloWorldAction" class="com.bell.action.HelloWorldAction" method="returnMessage">
<result name="success">/WEB-INF/view/helloWorld.jsp</result>
</action>
</package>
</struts>
HelloWorldAction类的定义很简单,因为struts2是非入侵的架构,所以aciton的method方法返回的类型也是String类型,其必须对应struts.xml中的result的name属性,而不像struts1中返回ActionForward对象而依赖上了servletAPI,其方法参数也不需要制定HttpServletRequest和HttpServletResponse来获取和返回数据到Jsp等表现层:
package com.bell.action;
public class HelloWorldAction {
private String msg;
public String getMessage()
{
return msg;
}
public String returnMessage()
{
msg = "this is my first request to Action";
return "success";
}
}
注意的是,访问地址需要加上被访问的action的namespace,而且struts2的默认后缀是action:
http://localhost:8080/struts2/hello/helloWorldAction(.action)
4. 在struts1中的action定义中,可以通过不指定class和method属性来让请求不进行Action类的逻辑处理,而直接转向另一个页面,如下:
<action path="/user/user-page/addEmployeeUI" forward="/WEB-INF/page/addEmployee.jsp"/>
当然,struts2也提供了类似的直接跳转的方式,其中如果不指定action的class属性,则默认为xwork API中的ActionSupport类,如果不指定method,则默认为execute()方法,如果不指定result的name属性,则默认其name=”success“:
<action name="addEmployeeUI">
<result >/WEB-INF/page/addEmployee.jsp</result>
</action>
5. struts2中常用的转发类型有4种,和struts1一样,有dispatcher(默认的方式,在/WebRoot/WEB-INF内部派发)和Redirect方式,另外还有redirectAction和plainText方式;值得注意的是,dispatcher是在WebRoot/WEB-INF内部派发,而Redirect方式是浏览器重定向,所以看不到WEB-INF内部的视图,所以要重定向的URL必须是WEB-INF以外的,包括互联网上的URL;而redirectAction需要定义相应的参数来把请求转发到另一个action上面;plainText方式用的不多,主要是显示视图的源码,而不执行视图的显示,也需要定义相应的参数,例子如下:
a. redirect方式
<struts>
<package name="helloWord" namespace="/helloWorld" extends="struts-default">
<action name="helloWorldAction">
<result type="redirect">/index.jsp</result> //可以不定以name属性,而直接跳转到WEB-INF外的视图
</action>
</package>
</struts>
b. redirectAction方式
<package name="helloWord" namespace="/helloWorld" extends="struts-default">
<action name="helloWorldAction">
<result name="success" type="redirect">/index.jsp</result>
</action>
<action name="helloAction">
<result type="redirectAction">helloWorldAction</result> //定义了type属性为redirectAction之后,就可以直接跳转到对应的action
</action>
</package>
其中,如果跳转的目标action在其他包的时候,需要在result中指定两个参数,即是actionName和nameSpace,如下,其实为什么需要指定actionName和nameSpace参数,这些都是定义在了struts-default包里面,这个默认的struts2的核心包定义了拦截器和相应的result type,也就是为什么所有的包需要extends struts-default包的原因;
<package name="helloWord" namespace="/helloWorld" extends="struts-default">
<action name="helloWorldAction" class="com.bell.action.HelloWorldAction"
method="returnMessage">
<result name="success" type="redirect">/index.jsp?getMessage=${message}
</result>
</action>
</package>
<package name="hello" namespace="/hello" extends="struts-default">
<action name="helloAction">
<result type="redirectAction">
<param name="nameSpace">/helloWorld</param>
<param name="actionName">helloWorldAction</param>
</result>
</action>
</package>
c. plainText方式
<action name="helloWorldAction">
<result name="success" type="plainText">/index.jsp</result> //当请求到来时,直接显示index.jsp的源码
</action>
如果显示源码的视图index.jsp页面有中文字符,则需要定义location和charSet属性来让tomcat以UTF-8的格式去读取这个以UTF-8为编码格式的jsp页面,不指定charSet的话,就会以默认的GBK编码格式去读取这个UTF-8的jsp页面,这样就会出现乱码:
<action name="helloWorldAction">
<result name="success" type="plainText">
<param name="location">/index.jsp</param>
<param name="charSet">UTF-8</param>
</result>
</action>
6. action中的result视图可以在URL location后面加入类似于EL表达式的参数来使该视图获取action的属性:
<action name="helloWorldAction" class="com.bell.action.HelloWorldAction" method="returnMessage">
<result name="success" type="redirect">/index.jsp?message=${message}</result> // ${message}即获取了action中的getMessage()方法;而message=中的message则是request的key,在视图jsp中可以通过<%= request.getParameter("message")%>来获取其value,或者通过EL表达式${param.message}来获取;
</action>
但是,如果从action返回的参数中有中文字符时,可能返回乱码,那么就要在服务器上的action进行编码和客户端视图上面的解码,例子如下:
服务器端的Action,将默认的中文字符的ISO-8859-1进行utf-8编码:
public String returnMessage() throws Exception {
this.msg = URLEncoder.encode("这是一条消息","utf-8"); //使用URLEncoder类的encode方法对其进行utf-8编码
return "success";
}
客户端中将页面的pageCoding设置为UTF-8,这时可以通过JSP Expression来获取参数,并进行解码:
<%=URLDecoder.decode(new String(request.getParameter("message").getBytes("ISO8859-1"), "utf-8"),"utf-8")%> // 通过string类的getBytes("ISO8859-1")获取请求参数的中文编码字节字符串,然后进行utf-8编码转换,最后utf-8的解码即可输出中文字符;关于为什么需要utf-8编码和解码,我个人觉得是因为请求在通过服务器tomcat的时候进行了iso8859-1的编码,所以到达客户端视图时,需要获取iso8859-1的字节编码,然后转换成utf-8的格式,最后利用URLDecorder的decode()方法进行解码;
另外,可以定义全局的global-results,让其能够被多个action共享;可以将global-results放在一个包里面,然后其他需要使用其result的action包继承它,这样就可以使用了:
<package name="base" namespace="/base" extends="struts-default">
<global-results>
<result name="base" type="redirect">/index.jsp</result>
</global-results>
</package>
<package name="helloWord" namespace="/helloWorld" extends="base">
<action name="helloWorldAction" class="com.bell.action.HelloWorldAction"
method="returnMessage">
</action>
</package>
7. 可在struts2的配置文件中定义常量,如web请求的默认后缀名是.action,而可以通过struts2的常量设置将其后缀名位置为任意值:
<struts>
<constant name="struts.action.extension" value="do"></constant>
<struts>
以下所有的配置文件都可以添加常量,常量加载的顺序是:struts-default.xml, struts-plugin.xml, struts.xml, struts.properties, web.xml; 如果出现重复的常量名,后面加载的常量会覆盖之前的;其中,在struts.properties中定义如下,但是不建议这样定义,建议定义在struts.xml中:struts.action.extension=do
8. struts2的常用常量:
a. 指定默认编码集,作用于HttpServletRequest的setCharacterEncoding方法和freeMarker、Velocity的输出
<constant name="struts.i18n.encoding" value="UTF-8">
b. 指定请求后缀
<constant name="struts.action.extension" value="do">
c. 设置浏览器是否缓存静态页面内容,建议开发的时候设置为false
<constant name="struts.serve.static.browserCache" value="false">
d. 设置当struts2配置文件修改时,是否自动重新加载,建议开发的时候设置为true
<constant name="struts.configuration.xml.reload" value="true">
e. 设置开发模式下打印出更多的详细信息,设置为true
<constant name="struts.devMode" value="true">
f. 设置默认的视图主题
<constant name="struts.ui.theme" value="simple">
g. 设置与spring集成时,spring来负责action对象的创建
<constant name="struts.objectFactory" value="spring">
h. 设置struts2是否支持动态方法调用,默认为true,可设置为false
<constant name="struts.enable.DynamicMethodInvocation" value="false">
f. 设置上传文件的总大小,非单个上传文件大小
<constant name="struts.multipart.maxSize" value="10701096">
9. struts2中的action跟struts1中的action是不太一样的,struts1中的Action是采用单例模式设计的,也就是非线程安全的,每个用户的每个请求都是通过在服务器启动时生成的单例action实例;而struts2中的action采用的是原型设计模式,每个用户的每一个请求都会让容器(或者是spring容器)产生一个action实例供请求调用,所以它是线程安全的,但是我想这样的执行效率肯定相对struts1较慢,而且占用更多的资源,也就是牺牲了资源来实现线程安全的效果;
10. struts2也提供了类似struts1中的DispatchAction方式来通过请求参数的不同派发到同一个action里不同方法来执行请求,并且返回result;
回顾一下struts1中的DispatchAction类,DispatchAction是一个抽象的Action,它根据request 中的parameter参数来执行相应的方法。通个这个Action类可以将不同的Action集中到一个Action文件中来,还是看看具体的例子吧:
jsp页面通过href中不同的参数来调用同一个action中的不同方法:
<a href='<html:rewrite action="/which_pet_dispatchAction"/>?mymethod=dog'>Dog</a>
<a href='<html:rewrite action="/which_pet_dispatchAction"/>?mymethod=bird'>Bird</a>
Action:
public class TestDispatchAction extends DispatchAction {
public ActionForward dog(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setAttribute("pet", "dog");
return mapping.findForward("result");
}
public ActionForward bird(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setAttribute("pet", "bird");
return mapping.findForward("result");
}
}
struts1 xml配置
<action-mappings>
<action path="/which_pet_dispatchAction"
type="com.struts.TestDispatchAction"
scope="request"
parameter="mymethod">
<forward name="result" path="/WEB-INF/page/showpet.jsp"/>
</action>
</action-mappings>
目前我知道struts2提供了2种的方式,一种是通过url中加入 “感叹号+action方法名” 实现,不过这种方式已经不提倡使用,而配置文件不需要修改:http://localhost:8080/struts2/helloWorld!other.action 其中other是在struts.xml的action中没有配置的方法,一般配置的默认方法为execute;
另一种方法就是使用通配符,这个就比较好理解了,首先需要在struts.xml中action的name属性加上通配符 “星号” ’*‘,然后在method属性中定义好请求参数中该通配符的位置,一般来说是 {1}, 之所以需要定义method的位置,是因为action的name属性里面可以使用多个通配符,那么method到底是哪一个通配符对应的方法名呢,就需要定义好位置;并且在class等属性中也可以使用通配符;看看例子吧:
<action name="helloWorldAction*" class="com.bell.action.HelloWorldAction"
method="{1}"> //method的方法名就是请求中"helloWorldAction_"后面紧接着的字符串
<result name="success">/WEB-INF/view/helloWorld.jsp</result>
</action>
请求URL : http://localhost:8080/struts2/helloWorld/helloWorldActionother.action //action类中定义了other()方法
11. 关于struts2中接受请求参数的问题,与struts1不同的是,struts2不再采用ActionForm(FormBean)的形式来接受页面的数据并且让action处理对应的formBean,而是页面请求的数据可以与action中的字段直接连接起来,但是要求action类中定义与请求参数同名的属性(setXXX方法中的XXX),通过反射技术调用与请求参数同名的属性的setter()方法,对action类的字段进行赋值,其实struts2的action类就是一个带有execute等方法的POJO类,这样提高了代码的复用性;
还是先回顾一下struts1中的formBean和Action吧:
//JSP表单
<form action="dyna.do" method="post">
<input type="text" name="name" /><br/>
<input type="text" name="age" /><br/>
<input type="submit" value="提交">
</form><br>
${dynaForm.map.name }<br/>
${dynaForm.map.age }
// struts.xml的配置:
<form-beans>
<form-bean name="dynaForm" type="org.apache.struts.action.DynaActionForm">
<form-property name="name" type="java.lang.String" />
<form-property name="age" type="java.lang.Integer" />
</form-bean>
</form-beans>
<action path="/dyna" type="com.cao.action.DynaFormAction" name="dynaForm" scope="request" input="/error.jsp"> //input定义了出错时跳转的视图,name定义了ActionForm类名
<forward name="success" path="/dynaform.jsp" />
</action>
//action类:
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub
DynaActionForm daf = (DynaActionForm) form; //转型为org.apache.struts.action.DynaActionForm
System.out.println(daf.get("name")); //可以通过get属性名获取属性值
System.out.println(daf.get("age"));
return mapping.findForward("success");
}
而在struts2中,定义如下的action类:
public class HelloWorldAction
{
private String name;
private void setPerson(Person person) //setter方法中set后面的字符串要与请求中的参数同名
{
this.person=person;
}
public String execute()
{
System.out.println(person.name); //可以通过person的属性名获取属性值
System.out.println(person.age);
return "success";
}
}
class Person
{
private String name;
private String age;
//这里需要注意的是,如果没有给出Person的构造函数是可以的,如果给了出非默认的构造函数,则需要把默认的构造函数也定义出来,否则实例化person对象时会报错;
public Person () //默认构造函数
{
}
public Person (String name, String age)
{
this.name=name;
this.age=age;
}
}
//JSP页面
<body>
<form action="<%=request.getContextPath()%>/helloWorld/helloWorldActionreturnMessage"
method="post">
name:<input type="text" name="person.name"/> //可以直接引用复合对象的属性
<br />
age: <input type="text" name="person.age" />
<br />
<input type="submit" value="提交" />
</form>
</body>
//struts.xml配置:
<package name="helloWord" namespace="/helloWorld" extends="struts-default">
<action name="helloWorldAction*" class="com.bell.action.HelloWorldAction"
method="{1}"> //请注意上面jsp中的action和xml中action中的*号
<result name="success">/WEB-INF/view/helloWorld.jsp
</result>
</action>
12. struts2中也很好地支持了类型转换,而且提供了针对特定action类和全局的转换设置;需要重写DefaultTypeConverter类,一共有3个同名的类位于不同的包里面,其中这两个包下面的这个类是可以正常使用的:com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter和ognl中的这个类;
举个例子,在客户端请求中的时间参数,一般struts2可以处理这种格式:yyyy-mm-dd,但是如果客户端出现yyyymmdd格式的日期参数,则struts2在利用发射技术填充action中的对应字段时,只会把它看做String,这样的话就会报错,因为该字段对应的setter方法肯定是以Date作为参数类型,而不是String,所以需要用到类型转换器来定义;struts2中的类型转换器是双向的,比如action的属性被显示到struts标签上,struts标签需要使用到String,这样需要把Date转换成String,也就是struts的类型转换器可以从一种类型转换成另一个类型,并且能够相反地转换,且看以下定义的类型转换器:
public class DateStringConverter extends DefaultTypeConverter
{
@override //需要重写父类的convertValue方法, context是ognl表达式,value是请求参数值,toType是需要转换的类型
public Object convertValue(Map<String, Object> context, Object value, Class toType)
{
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyymmdd");
try
{
if (toType == Date.class) //字符向Date类型转换
{
String [] params = (String [])value; // 之所以这里的value是字符数组类型,是因为客户端来的请求参数可能是类似comBox等等这些同名的复合数据,所以要使用数组来存放;
return dateFormat.parse(params[0]); //parse方法是转换String到Date
}
else if (toType == String.class) //Date类型向字符转换
{
return dateFormat.format((Date)value); //format方法是转换Object(Date)到String
}
}
catch(Exception e)
{}
}
}
这样的类定义好了之后,就需要做一下配置:
a. 针对某个action类的局部类型转换,需要定义在该action的包下,并且命名格式为:actionName-conversion.properties, actionName就是对应的Action类名,不需要加上包名,里面的配置也是key-value格式,key对应action类中的需要转换的字段,value就是类型转换器类的类名,需要加上包名:
birthday=com.bell.common.DateStringConverter
b. 全局的类型转换器,转换器类是一样的,只需要把配置的属性文件重命名为:xwork-conversion.properties,并且放在项目的根路径下面,即src下面,里面的内容也是key-value形式,key为待转换的类型,value是转换器类名:
java.util.Date=com.bell.common.DateStringConverter
可能需要注意的是,如果定义了类型转换器,那么需要遵循转换器对应的格式,而之前默认识别的格式可能不能在被识别。
13. struts1中的action类继承了servletAPI中的Action,比如说Action, DispatchAction, MappingDispatchAction等等,这样的话,就可以直接获取request,session,application等范围,接着对其进行获取参数或者植入参数等操作;但是struts2中的Action类是非侵入式的类,没有依赖于servletAPI,所以不能够直接获取这些范围,而strtus2中把request, session, application封装在一些类中,可以通过这些类获取实例或者实现他们的接口来获取实例,方式有3,如下所示:在jsp页面中,可以通过EL表达式获取各个scope的参数,如${requestScope.name}, {sessionScope.name},{applicationScope.name}:
a. 如果只是想通过这些范围植入数据,然后回显到视图的话,就可以使用ActionContext类的static方法getInstance()来获取到上下文context,它是一个Map,可以调用put() 方法以key-value方式直接植入request范围的数据;context.getSession()获取到session范围的Map,同样以put()方法植入session范围的数据;context.getApplication()获取到servletContext(application)的Map,同样采用put()方法植入数据;但是通过ActionContext类是不能直接获取到request,session,application的实例,所以有局限性:
private void setScopeValue() {
ActionContext context = ActionContext.getContext();
context.put("req", "request范围");
context.getSession().put("ses", "session范围");
context.getApplication().put("app", "application范围");
}
b. 接下来的方式就是使用ServletActionContext,这个类比第一个类多了一个Servlet的实现,所以可以通过ServletActionContext.getRequest()获取request实例,ServletActionContext.getRequest().getSession()获取session实例,ServletActionContext.getServletContext()获取application实例:
private void setScopeValue() {
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession session = request. ();
ServletContext application = ServletActionContext.getServletContext();
}
c. 还有一种方式,就是实现特定接口,让struts2在运行时自动注入这些范围的实例;这样的话,action类需要实现ServletRequestAware,ServletResponseAware,ServletContextAware等接口,然后定义好HttpServletRequest, HttpServletResonse, HttpServletContext字段,通过setter方法自动注入:
public class HelloWorldAction implements ServletRequestAware,
ServletResponseAware, ServletContextAware {
private HttpServletRequest request;
private HttpServletResponse response;
private ServletContext context;
private Date birthday;
public void setServletRequest(HttpServletRequest req) {
this.request = req;
}
public void setServletResponse(HttpServletResponse res) {
this.response = res;
}
public void setServletContext(ServletContext context) {
this.context = context;
}
}
14. struts2中引入jstl标签跟struts1是一样的,下载好jstl.jar和standard.jar包,导入到/WEB-INF/lib里面进行编译,然后在jsp页面上定义标签的使用:<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>,这样就可以使用类似命名空间<c:xxx>下面的jstl组件(标签)了:
<c:forEach items="{persons}" var="person">
<c:out value="${person.name}" /><br/>
</c:forEach>
15. struts2中提供了很好的上传文件机制,首先需要在jsp页面中顶一个form, 指定action属性后,把type设置为: multipart/form-data, 其中特别注意method属性要设置为post,不然无法以二进制格式上传到服务器,接着就是顶一个input,类型为FILE,name为任意文件名uploadfile;接下来需要注意的是,action中对应的获取uploadfile的字段名必须跟jsp中file input的uploadfile一致,即: private File uploadfile; 并且设置setter方法,如果需要返回到其他页面,就要设置getter方法;如果想要获取jsp中file input的uploadfile name,则action类中对应的属性也需要同样定义,即private String uploadfileFileName; 文件类型ContextType也是一样:private String uploadfileContextType;
jsp:
<form action="${pageContext.request.contextPath}/helloWorld/helloWorldAction.action"
enctype="multipart/form-data" method="post">
文件:<input type="FILE" name="upload">
<input type="submit" value="上传">
</form>
action类:
private File upload;
private String uploadFileName;
...//省略setter方法
public String execute() {
String realPath = ServletActionContext.getServletContext().getRealPath("/upload"); //通过ServletActionContext的getServletContext方法获得application,然后获取要存放路径/upload的实地址;
File saveFile = new File(new File(realPath), uploadFileName);
if (upload != null) {
if (!saveFile.getParentFile().exists())
saveFile.getParentFile().mkdirs();
try {
FileUtils.copyFile(upload, saveFile);
} catch (IOException e) {}
}
ActionContext.getContext().put("successMsg", "上传成功!"); //设置request范围的返回参数到视图
return "success";
}
如果上传的文件太大,可能无法上传,这样的话,可以在struts.xml中设置<constant name="struts.multipart.maxSize" value="10701096">,但是如果要上传类似视频这种超大文件的时候,这种web上传的方式实行不通的,或者说是不稳定的,一般都需要采用应用程序的socket技术让文件进行二进制上传。
对于多文件上传,需要做的事情就是定义name属性相同的多个File input组件,然后在action类中把需要反射视图上的多文件的setter方法定义为List或者File数组,然后遍历list或者string []来保存文件:
jsp:
<form action="${pageContext.request.contextPath}/helloWorld/helloWorldAction.action"
enctype="multipart/form-data" method="post">
文件1:<input type="FILE" name="upload">
文件2:<input type="FILE" name="upload">
<input type="submit" value="上传">
</form>
action类:
private File [] upload;
private String [] uploadFileName;
...//省略setter方法
public String execute() {
String realPath = ServletActionContext.getServletContext().getRealPath("/upload");
File folder = new File(realPath);
if (!folder.exists())
folder.mkdirs();
if (upload != null) {
try {
for (int i = 0; i < upload.length; i++) {
File saveFile = new File(folder, uploadFileName[i]);
FileUtils.copyFile(upload[i], saveFile);
}
} catch (Exception e) {}
}
ActionContext.getContext().put("successMsg", "上传成功!");
return "success";
}
16. struts2提供了非常不错的拦截器机制,而且大多数的核心功能都是通过拦截器interceptor来实现的;拦截器是功能如其名,就是起到对用户请求拦截的作用,在其内部判断出是否接受用户请求并继续用户调用action相应的方法,并且返回结果,结果既是action的result,或者是global-result;这个自定义的Interceptor类需要实现com.opensymphony.xwork2.interceptor.Interceptor接口,然后重写其intercept方法,经过判断如果interceptor允许请求通过并且继续执行action的对应方法,则return intercept.invoke(); 否则return其他定义在action内的result字符串或者global-result:
import com.opensymphony.xwork2.interceptor.Interceptor;
public class UserInterceptor implements Interceptor {
public void destroy() {}
public void init() {}
public String intercept(ActionInvocation invocation) throws Exception {
if (ServletActionContext.getRequest().getAttribute("userName").equals("bell")) { //判断jsp上的userName是否是bell来执行返回操作
return invocation.invoke(); //invoke()方法返回的是String,这个String代表了继续执行请求的action及其方法,当然,在action的该方法中,肯定会返回一个result(视图)
}
return "fail"; //fail定义的是一个登陆错误返回页面,可以继续返回到登陆页面
}
}
定义好了interceptor类之后,我们需要把它注册到action中,一般来说,我们自定义了interceptor,然后在struts.xml中配置如下:
<interceptor name="userInterceptor" class="com.bell.interceptor.UserInterceptor" />
,然后在action中引入即可:
<action name="helloWorldAction*" class="com.bell.action.HelloWorldAction" method="{1}">
<interceptor-ref name="userInterceptor"></interceptor-ref>
<result name="success">/WEB-INF/view/helloWorld.jsp
</result>
</action>
,但是这样做有很多弊端,就如我们之前所说的那样,struts2的很多核心功能是通过interceptor实现的,如果你在action中只引入了自定义的interceptor,那么就像java类的构造器一样,默认的struts2的拦截器功能将不再对该action有效,所以你需要在引入自定义的拦截器的时候,同时加入struts2的默认拦截器;通常我们的做法是,定义一个interceptor集合, 其中首先可以自定义自己的interceptor,再自定义一个interceptor的stack,然后把默认的interceptor stack加入到此stack中,即default-stack,然后加入自己刚定义的interceptor引用进去,要注意先引入默认的stack,然后引入自己的interceptor,因为interceptor是顺序执行的,首先要确保默认的拦截器生效,然后再拦截自定义的,如下所示:
<interceptors>
<interceptor name="userInterceptor" class="com.bell.interceptor.UserInterceptor" />
<interceptor-stack name="userStack">
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="userInterceptor"></interceptor-ref>
</interceptor-stack>
</interceptors>
然后让action引用组合好的userstack:
<action name="helloWorldAction*" class="com.bell.action.HelloWorldAction" method="{1}">
<interceptor-ref name="userStack"></interceptor-ref>
<result name="success">/WEB-INF/view/helloWorld.jsp
</result>
</action>
完成上述步骤之后,当请求到达action的时候,反射机制执行完setter方法之后,不会直接进入请求的方法,而进入interceptor,让interceptor决定是否继续,或者执行另外的视图;
另外,可以让整个package下面所有的action都引用一个默认的interceptor或者interceptor stack:
<default-interceptor-ref name="userStack"/>,这样就可以了。但是如果在此package的某个action下面再定义一个其他自定义的interceptor,则会完全覆盖掉这个default interceptor,除非你在引入自定义的interceptor之前先引入default interceptor:
<default-interceptor-ref name="userStack" />
<action name="helloWorldAction*" class="com.bell.action.HelloWorldAction" method="{1}">
<interceptor-ref name="userStack" />
<interceptor-ref name="otherInterceptor" />
<result name="success">/WEB-INF/view/helloWorld.jsp
</result>
</action>
17. struts2也同样提供了validate机制,可以对action类所有的方法或者某一些方法进行校验,有两种形式:手动编写valudate方法和XML校验,先介绍下手动编写validate方法吧;首先action需要继承ActionSupport类,然后重写其validate()方法,判断输入的数据,如果检验失败,则将错误信息加入到addFieldError方法中,然后可以通过struts标签<s:fielderror>在页面上显示出来;其中,如果校验失败,会跳转到struts.xml中action的名为"input"的视图:
action类:
public class HelloWorldAction extends ActionSupport {
private String userName;
private String password;
.....
public void validate() {
if (userName == null || "".equals(userName.trim())) {
this.addFieldError(userName, "用户名为空");
}
if (password.length() < 6) {
this.addFieldError(password, "密码必须超过6位");
}
if (!Pattern.compile("^1\\d$").matcher(password).matches()) { //可以使用正则表达式格式校验
this.addFieldError(password, "数据格式不正确");
}
}
}
JSP:
<%@ taglib uri="/struts-tags" prefix="s"%> //引入struts标签
....
<body>
<s:fielderror/>
....
</body>
struts.xml:
<package name="helloWord" namespace="/helloWorld" extends="base">
<action name="helloWorldAction*" class="com.bell.action.HelloWorldAction" method="{1}">
<result name="success">/WEB-INF/view/helloWorld.jsp
</result>
<result name="input">/index.jsp</result> //input视图为校验失败跳转视图
</action>
</package>
另外,可以针对action的某一些方法进行,其他的没有太大区别,唯一的是validate方法名改为validate加上需要校验的方法名,并且此方法名首字母大写,如: validateExecute(),这样的话,只有execute方法会被校验;
总结一下,输入校验的过程:
a. 用户提交请求到struts2,struts2分配到对应action,action接受到请求参数,此时需要对请求参数进行类型转换,然后要利用反射技术对action类的字段进行注入值,如果在类型转换的过程中出现异常的话,系统会将异常情况保存到ActionContext中,conversionError拦截器将异常信息封装在fieldErrors里,但是不管是否出现异常都将进行步骤b;
b. 系统利用反射技术调用action中的validateXxx方法(如果定义了特定方法的校验方法的话),Xxx为方法名,前面已经说过了;然后再调用validate()方法;如果出现校验失败,则同样会把错误信息封装到fieldErrors里面;
c. 系统如果判断出fieldErrors(对方错误信息的集合)的长度大于0的话,就会自动跳转到struts.xml中action的input视图,如果fieldErrors中没有错误信息,则会执行action中的处理方法;
18. 以xml的方式对action的方法进行校验,首先action也必须继承ActionSupport类,并且提供校验文件,校验文件需要放在action同一目录下,名为ActionClassName-validation.xml的格式,其中ActionClassName为action类的简单类名,即不含包的类名,validation.xml是固定格式; 先看看一个例子:
//HelloWorldAction-validation.xml,如果采用的DTD是xwork-validator-1.0.3.dtd的话就如下配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator Config 1.0.3//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd">
<validators>
<field name="userName"> //对应Action类中的属性
<filed-validator type="requiredstring"> //requiredstring是struts2定义好了的验证类型
<param name="trim">true</param> //默认为true,让userName进行trim()去掉两头的空格,然后再判断
<message>用户名不能为空</message> //输出的错误信息
</filed-validator>
</field>
</validators>
顺便说说DTD文件,document type definition,DTD是一种保证XML文档格式正确的有效方法,可以比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。其实,大家可以看看xwork-core-2.x.x.jar里面的xml,dtd文件,这样就更加了解这些xml的配置和struts2提供的一些操作方式和规则;
采用如上的xml文件,然后和action放在同一包里面,就可以生效了,同上面17点所说的,如果验证不通过则进入action的input视图,否则继续执行action的请求方法。其中field-validator的type类型有很多种,常用的有:required,requiredstring,stringlength,regex(正则表达式)等等,大家在需要的时候可以去google一下他们的用法,大同小异,但是reqex要稍微了解一下,它提供了非常用验证的很多验证方式。
要通过xml形式对action的指定方法进行校验,就需要把xml定义为ActionClassName-ActionName-validation.xml,其中,ActionName就是struts.xml中配置的Action Name,也即是请求视图中action名,比如jsp中form的action是:${pageContext.request.contextPath}/helloWorld/helloWorldActionlogin.action,则这里的ActionName就是helloWorldActionlogin:HelloWorldAction-helloWorldActionlogin-validation.xml;其他的配置和所有方法验证的xml配置一样。
如果一个action既有全局的验证xml,也有对某些特定方法的xml验证,那么struts2会先搜索全局的xml校验,然后综合所有的xml验证进行验证,如果存在冲突,则采用局部的特定方法的xml验证;如果一个action继承了另一个action,则会先搜索父类action的验证文件,然后搜索子类的,同样的,全不会起作用。
19. struts2也提供了国际化信息输出的方法,首先准备好资源文件,放在src目录下,命令格式有如下几种:baseName_language_country.properties, baseName_country.properties, baseName.properties;其中baseName可以随便定义,但是language和country要是java所支持的语言和国家,可以从IE的语言选项里面看到他们的简写编码;中国大陆就是baseName_zh_CN.properties, 美国baseName_en_US.properties. 此属性文件里面的内容以key-value的形式存在,如:name=李小龙,name=Bruce Lee;但是对于中文的属性文件,我们要用jdk提供的native2acsii命令把文件转换为unicode编码的文件,命令的格式是: native2ascii 源文件 目标文件;但是幸好的是,从MyEclipse 6.6 (可能)之后,就提供了自动转换的功能,不用native2ascii命令手动去转换到unicode编码; 准备好了资源文件之后,我们就在struts.xml中通过struts.custom.i18n.resources常量把资源文件定义为全局资源文件:<constant name="struts.custom.i18n.resources" value="baseName"/>;然后呢,在JSP页面我们可以通过struts标签<s:text name="key"/>来输出国际化信息;在Action类中,可以让它继承ActionSupport,使用getText()方法来得到国际化信息,此方法有很多重写,但是第一个参数适用于指定资源文件中的key;表单标签中也是通过key指定资源文件的key,如:<s:textfield name="realname" key="user"/>;
对于使用了占位符的国际化信息,如:info={0},this is a {2};在jsp中还是使用struts的标签<s:text>中的<s:param>依次填写{0},{1}对应的value;action类中的话,通过getText()的getText(String key, String[] args)或者getText(String aTextName, List args)方法填充值;
如果只采用全局的资源文件,那样是不容易维护和查找的,所以我们可以采用包范围国际化资源文件,它能够被该包及其子包使用;格式就是package_language_country.properties,其中package是固定写法,其他的跟全局一样,然后要把这种包范围的国际化资源文件放到对应包下面。而且包范围的优先级高于全局的,对应的action或者WebRoot中的jsp会先按照key寻找包范围的文件,如果找不到,就会寻找其父类包的资源文件,最后是全局范围的文件;
同样,对于某个具体的Action,struts2也提供了action范围的国际化资源文件,同上,以ActionClassName_language_country.properties为命名的格式,然后放在action的类路径就可以了,同样,如果action类通过key来查找key的话,如果action类没有对应的key,就会在package范围的资源文件查找,然后是父包,然后是全局;
如果我们不想配置采用具体的资源文件,可以使用struts中的<s:i18n>标签来指定使用action或者package的资源文件,例如使用HelloWorldAction范围的资源文件,name属性就在包后面加上ActionClassName
<s:i18n name="com/bell/action/HelloWorldAction">
<s:text name="welcome"/>
</s:i18n>
如果采用某个包下面的资源文件,name属性中在包的后面加上package这个固定字符。
<s:i18n name="com/bell/action/package">
<s:text name="welcome"/>
</s:i18n>
20.
相关推荐
### Struts2学习心得:深入理解Struts2框架的基本配置与初学者指南 #### Struts2框架概述 Struts2是Struts框架的升级版本,它继承了Struts1的优点并在此基础上进行了改进,提供了更丰富的功能和更好的性能。Struts...
在探讨Struts2的学习心得之前,我们先来分析一下Struts2与Struts1之间的区别。 ##### 1.1 架构设计 - **Struts1**:采用MVC(Model-View-Controller)架构模式,其中控制器是由`ActionServlet`来实现的,该控制器...
### Struts2与Struts1的主要区别 #### 控制器类设计差异 在Struts2框架下,控制器类的设计思路有了显著的变化。相比于Struts1中的控制器类,Struts2中的控制器类更像是一个简单的POJO(Plain Old Java Object),...
### Struts2输入校验深度解析 #### 一、手动输入完成校验 在Struts2框架中,输入校验是确保数据完整性和安全性的重要环节。对于手动输入完成校验,Struts2提供了灵活的机制。 1. **普通处理方式**:在Action类中...
### Struts学习心得详解 #### 一、环境搭建与配置 **1.1 JDK(Java Development Kit)** JDK是所有Java程序运行的基础,包括Struts框架在内的多种工具都依赖于JDK。JDK提供了Java运行环境(JRE)以及开发工具,如...
struts2学习心得,对于新手,可能会觉得难以入门,这是我的一些学习心得,希望对你有用
Struts2的“零配置”特性是Struts2的新功能,可能会出现一些小Bug,所以企业开发者请慎重使用该特性, Struts2的“零配置”特性由struts2-codebehind-plugin-2.1.2.jar插件支持,使用时记得将其拷入WEB-INF/lib下; ...
一个简单的spring+struts+ibatis整合的实例,实现了用户登录,用户登录成功则显示欢迎信息,失败则显示用户名或密码错误,该实例非常简单基础,特别适合新人学习,工程包含了必要的资源包,部署到服务器中及可运行,...
通过学习和理解这个项目,你可以掌握Struts2框架的核心概念,了解如何在实际项目中运用MVC模式,提升你的Java Web开发技能。同时,这也是一个很好的起点,帮助你进一步学习Spring、Hibernate等框架,构建更复杂的...
2. Hibernate的主要功能:它是Java应用和数据库之间的中间件,负责Java对象的持久化,提供数据访问的封装,简化了数据库操作。在分层架构中,它位于持久化层,使得业务逻辑层只需关注业务处理,而不需关心数据存储的...
- **社区交流:**参与社区讨论,分享学习心得,与其他开发者共同成长。 通过上述知识点的介绍,我们可以看出Struts2框架不仅在设计上更加合理,而且在功能上也更为强大。对于希望深入学习Struts2的开发者而言,掌握...
### 关于Struts2实验时的临时总结 #### 概述 本文档是对Struts2实验过程中的几个关键问题及解决方案进行了归纳与整理。通过实际操作过程中遇到的问题及其解决办法,帮助其他开发者避免同样的错误,并提供高效的学习...
### Spring和Struts框架学习总结 #### 一、Spring框架简介与核心概念 Spring框架是一个开源的Java平台,主要用于简化企业级应用的开发工作。它提供了全面的基础架构支持,包括依赖注入(DI)、面向切面编程(AOP)...
在Struts2框架中,`<s:if>`标签用于条件判断,类似于Java中的if语句。本篇文章将深入解析这段代码所涉及的关键知识点,并通过实际案例帮助理解如何使用Struts2中...Struts2提供的这些功能强大且灵活,值得学习和掌握。
3. activiti学习笔记.docx:这是一份个人的学习笔记,可能记录了作者在研究和实践中遇到的问题及解决方案,对于学习者来说是一份宝贵的心得体会。 4. Activitiѧϰ.docx:这个文件名可能是笔误,但根据上下文,很...
这篇博客可能是作者在实际项目中使用 Struts 1.3.8 版本时的心得体会,分享了如何利用该框架来构建应用,同时也可能涉及了某些工具的使用技巧。 Struts 作为 MVC(Model-View-Controller)设计模式的实现,核心在于...