2020-12-16
Struts2介绍
Struts2是在WebWork2基础发展而来的。和Struts1一样,Struts2也属于mvc框架。不过有一点大家要注意的是:尽管Struts2和Struts1名字上相差不大,但是在代码编写风格上几乎是不一样的,那么既然有了Struts1,为何还要推出Struts2.主要是因为Struts2有以下优点:
- 在软件设计上Struts2没有像Struts1那样跟Servletapi和Strutsapi有紧密的耦合,Struts2的应用可以不依赖Servlet api和struts api,Struts2的这种设计属于无侵入式设计,而Struts1却属于侵入式设计。
- Struts2提供了拦截器,利用拦截器可以进行aop编程,实现权限拦截等功能。
- Struts2提供了类型转换器,我们可以把特殊的请求参数转成需要的类型。在Struts1中,如果我们要实现同样的功能,就必须向Struts1的底层实现beanutil注册类型转换器才行。
- Struts2提供支持多种表现层技术,如:jsp,freemarker,velocity等。
- Struts2的输入校验可以对指定方法进行校验,解决了Struts1的痛点。
- 提供了全局范围、包范围和action范围的国际化资源文件管理实现。
搭建Struts2开发环境
搭建Struts2环境时,我们一般需要做以下几个步骤的工作:
1.找到开发Struts2应用需要使用到的jar包。
2.编写Struts2的配置文件。
3.在web.xml中加入Struts2 mvc框架启动配置。
搭建Struts2开发环境-----开发Struts2应用依赖的jar文件
可以到Struts官网查看那些包是必须引入的,下面的这写有点版本老了。
开发struts2应用需要依赖的jar文件在解压目录的lib文件夹下,不同的应用需要的jar包是不同的,下面给出了开发Struts2程序最少需要的jar。
Struts2-core-2.x.x.jar: Struts2 框架的核心类库
xwork-2.x.x.jar : XWork类库,Struts2在其上构建
ognl-2.6.x.jar:对象图导航语言(object graph navigation language),Struts2框架通过其读写对象的属性。
freemarker-2.3.x.jar:Struts2的UI标签的模板使用FreeMarker编写。
commons-logging-1.1.x.jar:ASF出品的日志包,Struts2框架使用这个日志包来支持Log4J和JDK1.4+的日志记录。
commons-fileupload-1.2.1.jar 文件上传组件,2.1.6版本后必须要加入此文件。
搭建Struts2开发环境—Struts2应用的配置文件
Struts2默认的配置文件为struts.xml,该文件需要存放在WEB-INF/classes下,该文件的配置模板如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<constant name="struts.devMode" value="false" />
<package name="myjson" namespace="/" extends="json-default">
<action name="transfer" class="cn.itcast.action.TransferAction">
<result type="json"></result>
</action>
<action name="queryOrders" class="cn.itcast.action.QueryOrdersAction">
<result type="json"></result>
</action>
</package>
</struts>
搭建Struts2开发环境—Struts2在web中的启动配置
在Struts1.x中,Struts框架是通过Servlet启动的。在Struts2中,Struts框架是通过Filter启动的。他在web.xml中配置如下:
<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>
在StrutsPrepareAndExecuteFilter的init()方法中将会读取类路径下默认的配置文件struts.xml完成初始化操作。
注意:struts2读取到struts.xml的内容后,以javabean形式存放在内存中,以后struts2对用户的每次请求处理将使用内存中的数据,而不是每次都读取struts.xml文件。
Struts.xml配置中等包介绍
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<package name="basicstruts2" namespace="/test" extends="struts-default">
<action name="index">
<result>/index.jsp</result>
</action>
</package>
</struts>
在struts2框架中使用包来管理Action,包的作用和java中的类包是非常类似的,它主要用于管理一组业务功能相关的action,在实际应用中,我们应该把一组业务功能相关的action放在同一个包下。
配置包时必须指定name属性,该属性值可以任意取名,但必须唯一,他对应java的类包,如果其他包要继承该报,必须通过该属性进行引用,包的namespace属性用于定义包的命名空间,命名空间的作为访问该报下的action的路径的一部分。如访问上面的action,访问路径为:/test/index。namespace属性可以不设置,如果不指定该属性,默认的命名空间为“”(空字符串)。
通常每个包都应用继承struts-default包,因为struts2很多核心的功能都是拦截器来实现,如:从请求中把请求参数封装到action、文件上传和数据验证等等都是通过拦截器实现的。struts-default定义了这些拦截器和Result类型。可以这么说:当包继承了struts-default才能使用struts2提供的核心功能。struts-default包是在struts2-core-2.x.jar文件中的struts-default.xml中定义。struts-default.xml也是struts2默认配置文件。struts2每次都会自动加载struts-default.xml文件。
包还可以通过abstract=“true”定义为抽象包,抽象包中不能包含action。
第一个struts程序:
struts.xml配置
<package name="wgp" extends="struts-default">
<action name="helloworld" class="com.wgp.action.HelloWorldAction" method="execute" >
<result name="success">/WEB-INF/page/hello.jsp</result>
</action>
</package>
action类:
package com.wgp.action;
public class HelloWorldAction {
private String message;
public String execute(){
message = "我的第一个struts2应用!";
return "success";
}
public String getMessage() {
return message;
}
}
jsp页面:hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>第一个struts2应用</title>
</head>
<body>
${message}
</body>
</html>
浏览器的访问路径:http://localhost:8080/struts2demo_war_exploded/test/helloworld
Action名称的搜索顺序
1.获得请求路径的URI,例如url是:htttp://server/struts2/path1/path2/path3/test.action
2.首先寻找namespace为/path1/path2/path3的package,如果没找到则执行步骤3;如果存在这个package,则在这个包中寻找对应的action,当在这个package下找不到action时就会直接跑到默认namespace的package中寻找,默认命名空间为空空字符串,如果默认中还是找不到,页面提示找不到action。
3.寻找namespace为/path1/path2的package,如果不存在,则转至步骤4;如果存在,则在这个package中训中名字为test的action,当该package寻找不到,则会去默认命名空间的package中寻找action,默认空间找不到,页面提示action找不到。
4.寻找namespace为/path1的package,如果不存在就执行步骤5;存在则在该package下寻找action,如果找不到action,就去默认空间找action,默认空间找不到,页面就提示该action找不到。
5.寻找namespace为/的package,如果存在这个package,则在该package下寻找action,如果找不到或不存在该package时,去默认空间的package里面寻找action,如果还是找不到,页面提示找不到该action。
总结:一个方位url路径,会先去找全路径的package,如果找不到就找上一个路径的package,依次类推,如果都不到就会去默认空间的package中寻找action,默认空间还是找不到,那么页面就会提示找不到该action。
Action配置中的各项默认值
- 如果没有为action指定class,默认是ActionSupport。
- 如果没有为action指定method,默认执行action中的execute()方法。
- 如果没有指定result的name属性,默认值为success。
action中的子节点result配置的各种试图转发类型
struts2中提供了多种结果类型,常用的类型有:dispatcher(默认配置)、redirect(重定向到某个路径)、redirectAction(重定向到某个action)、plainText。
在result中还可以使用${属性名}表达式访问action中的属性,表达式里的属性名对应action中的属性。如下:
<result type="redirect">/view.jsp?id=${id}</result>
下面是redirectAction结果类型的例子,如果重定向的Action中同一个包下:
<result type="redirectAction" > helloworld</result>
如果重定向的action在别的命名空间下:
<result type="redirectAction">
<param name="actionName">hello world</param>
<param name="namespace">/test</param>
</result>
plaintext:显示原始文件内容,例如:当我们需要原样显示jsp文件源代码的时候,我们可以使用此类型。
<result name="source" type="plainText">
<param name="location">/xx.jsp</param>
<param name="charSet">UTF-8</param>
</result>
定义全局消息页面的方式:其他package继承这个base就行了。
<package name="base" extends="struts-default">
<global-results>
<result name="message">/WEB-INF/page/message.jsp</result>
</global-results>
</package>
为action属性注入值:
public class HelloWorldAction {
private String message;
public String execute(){
return "success";
}
public String getMessage() {
return message;
}
//为要注入的属性设置set方法
public void setMessage(String message) {
this.message = message;
}
}
<package name="wgp" namespace="/test" extends="struts-default">
<action name="helloworld" class="com.wgp.action.HelloWorldAction" method="execute" >
<!--为action类中的属性 设置值-->
<param name="message">这是我第一个struts2程序</param>
<result name="success">/WEB-INF/page/hello.jsp</result>
</action>
</package>
指定struts2处理的请求后缀
我们都是默认使用的.action后缀访问action,其实默认后缀是可以通过常量“struts.action.extension”进行修改的,例如我们可以配置struts2只处理以.do为后缀的请求路径:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<!--如果用户需要指定多个请求后缀,则多个后缀直接以英文逗号分隔-->
<constant name="struts.action.extension" value="do,go"/>
<struts>
细说常量定义:
常量可以定义在struts.xml或者struts.properties中配置,建议在struts.xml中配置,两种配置方式如下:
在struts.xml文件配置:
<struts>
<!--如果用户需要指定多个请求后缀,则多个后缀直接以英文逗号分隔-->
<constant name="struts.action.extension" value="do,go"/>
<struts>
在struts.properties中配置常量
struts.action.extension=do
因为常量可以在下面多个配置文件中进行定义,所以需要了解struts2加载常量的搜索顺序:
struts-default.xml
struts-plugin.xml
struts.xml
struts.properties
web.xml
如果在多个文件中配置了同一个常量,则后一个文件中配置会覆盖前面文件中配置的常量值。
常用的常量介绍
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<!--常用的常量-->
<!--指定默认编码集,作用于HttpServletRequest的setCharacterEncoding方法和freemarker、velocity的输出,post提交过来的数据-->
<constant name="struts.custom.i18n.resources" value="UTF-8"/>
<!--该属性指定需要struts2处理的请求后缀,该属性值默认是action,及匹配*.action的请求由struts2处理。
如果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开。
-->
<constant name="struts.action.extension" value="do"/>
<!--设置浏览器是否缓存静态内容,默认值为true(生产环境下使用),开发阶段最好关闭-->
<constant name="struts.serve.static.browserCache" value="false"/>
<!--当struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false(生产环境下使用),开发阶段最好打开-->
<constant name="struts.configuration.xml.reload" value="true"/>
<!--开发模式下使用,这样可以打印出更详细的错误信息-->
<constant name="struts.devMode" value="true" />
<!--默认的视图主题-->
<constant name="struts.ui.theme" value="simple"/>
<!--与spring集成时,指定由spring负责action对象的创建-->
<constant name="struts.objectFactory" value="spring"/>
<!--该属性设置struts2是否支持动态方法调用,该属性的默认值是true。
如果需要关闭动态方法调用,则可设置属性为false。-->
<constant name="struts.enable.DynamicMethodInvocation" value="false"/>
<!--上传文件的总大小限制-->
<constant name="struts.multipart.maxSize" value="10701096"/>
<struts>
struts2的处理流程
[图片上传失败...(image-d688f5-1608134184287)]
为应用指定多个struts配置文件
在大部分应用里,随着应用规模的增加,系统中action的数量也会大量增加,导致struts.xml配置文件变得非常臃肿。为了避免struts.xml文件过于庞大、臃肿,提高struts.xml文件的可读性,我们可以将一个struts.xml配置文件分解成多个配置文件,然后中struts.xml文件中包含其他配置文件。下面的struts.xml通过<include>元素指定多个配置文件;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<include file="struts-user.xml"/>
<include file="struts-order.xml"/>
<struts>
通过这种方式,我们就可以将struts2的action按模块添加在多个配置文件中。
动态方法调用和使用通配符定义action
动态方法调用有两种方式:
1.如果action中存在多个方法时,我们可以使用!+方法名调用指定方法。官方不推荐!!
2.使用通配符定义action
<package name="wgp" namespace="/test" extends="struts-default">
<action name="helloworld_*" class="com.wgp.action.HelloWorldAction" method="{1}" >
<result name="success">/WEB-INF/page/hello.jsp</result>
</action>
</package>
public class HelloWorldAction{
private String message;
...
public String execute()throws Exception{
this.message = "我的第一个struts2程序";
}
public String other()throws Exception{
this.message = "第二个方法";
return "success";
}
}
要访问other()方法,可以通过这样的url方法:/test/helloworld_other.action或/test/helloworld_other;
接收请求参数
采用基本数据类型接收参数(get/post)
在action类中定义与请求参数同名的属性,struts2便能自动接收请求参数并赋予给同名属性。
请求路径:http://localhost:8080/test/view.action?id=78
public class ProductAction{
private Integer id;
//struts2通过反射技术调用与请求参数同名的属性的setter方法来设置获取到的请求参数。
public void setId(Integer id){
this.id = id;
}
public Integer getId(){
return id;
}
}
采用复合类型接收请求参数
请求路径:http://localhost:8080/test/view.action?product.id=78
public class ProductAction{
private Product product;
public void setProduct(Product product){
this.product=product;
}
public Product getProduct(){
return product;
}
}
//struts2首先通过反射接收调用Product的默认构造器创建product对象,然后再通过发射技术调用product中与请求参数同名的属性的setter方法来设置获取到的请求参数。
关于struts2.1.6接收中文请求参数乱码问题
Struts2.1.6版本中存在一个bug,接收到的中文请求参数为乱码(post方式提交),原因是该版本在获取并使用了请求参数后才调用HttpServletRequest的setCharacterEncoding()方法进行编码设置,导致应用使用的就是乱码请求参数。要在该版本中解决这个问题,我们可以这样做:定义一个Filter过滤器,把我们自定义的过滤器放在struts2的过滤器之前。
自定义类型转换器
当我们当action中有个Date类型的属性 ,需要接受一个那么当我们客户端传过来的是“20181221”这样一个字符串就会报错,这时我们就需要自定义类型转换器,把该字符串转换成Date类型。
类型转换器作用范围分为局部类型转换器(只作用于action)和全局类型转换。
自定义类型转换器:
//自定义日期转换器
public class DateConverter extends DefaultTypeConverter {
@Override
public Object convertValue(Map<String, Object> context, Object value, Class toType) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
try {
if (toType== Date.class){//当要转换的类型是Date类型
//value是Request.getParameterValues();
String[] params = (String[]) value;
return dateFormat.parse(params[0]);
}else if (toType==String.class){//当要转换成的类型是字符串时
Date date = (Date) value;
return dateFormat.format(date);
}
}catch (ParseException e){
e.printStackTrace();
}
return null;
}
}
局部类型转换器的配置
将上面的类型转换器注册为局部类型转换器:
在action类所在的包下创建一个ActionClassName(action的类名)-conversion.properties文件,ActionClassName是action的类名,后面的-conversion.properties是固定写法。
在properties文件中的内容为:
属性名字=类型转换器的全类名
自定义全集类型转换器的配置
在WEB-INF/classes下放置xwork-conversion.properties文件,在properties文件中内容为:待转换的类型=类型转换器的全类名
例如:java.util.Date=com.wgp.conversion.DateConverter
访问或添加Request、Session、application属性
ActionContext ctx = ActionContext.getContext();
ctx.getApplication().put("app","应用范围");//ServletContext域中
ctx.getSession().put("ses","sesssion范围");//session域中
ctx.put("req","request范围");//Request域中
<!--页面中获取域中属性,用之前学习javaweb时的正常方式也是可以拿到的-->
${applicationScope.app}
${sesssionScope.ses}
${requestScope.req}
获取HttpServletRequest、HttpSession、ServletContext、HttpServletRequest对象
两种方式:
方式一,通过ServletActionContext类直接获取
//获取Request对象
HttpServletRequest request = ServletActionContext.getRequest();
//获取ServletContext对象
ServletContext servletContext = ServletActionContext.getServletContext();
//获取Session对象
request.getSession()
//获取Response对象
HttpServletResponse response = ServletActionContext.getResponse();
方式二,实现指定接口,有struts框架运行时自动注入
public class HelloAction implements ServletRequestAware,ServletResponseAware,ServletContextAware{
private HttpServletRequest request;
private ServletContext servletContext;
private HttpServletResponse response;
public void setServletRequest(HttpServletRequest req){
this.request=req;
}
public void setServletResponse(HttpServletResponse resp){
this.response=resp;
}
public void setServletContext(ServletContext ser){
this.servletContext=ser;
}
}
文件上传
第一步:在WEB-INF/lib下加入commons-fileupload.jar、commons-io.jar。这两个包可以去Apache官网下载。
第二步:把form表单的enctype设置为:“multipart/form-data”
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<form enctype="multipart/form-data" action="${pageContext.request.contextPath}/test/fileUpload" method="post">
文件:<input type="file" name="uploadImage"><br>
<input type="submit" value="上传">
</form>
</body>
</html>
第三步:在action类中添加以下属性,属性部分对应表单中文件字段的名称
public class FileUploadAction {
//得到上传的文件
/**
* 该字段名称必须和页面上传的文件的参数名称一致,这样struts框架就会自动接收该文件
* 必须设置该字段的setter方法,
*/
private File uploadImage;
//得到文件的类型
/**
* 上传文件的类型,struts框架会自动帮我们获取文件的MIME类型,
* 该字段的命名规则是 "提交的文件字段名+ContentType" 固定写法,必须设置该字段的setter方法
*/
private String uploadImageContentType;
//得到文件的名称
/**
* 上传文件的文件名,带后缀的文件名。
* 该字段struts框架会帮我们字段获取并设置上信息,该字段也必须设置setter方法
* 该字段的命名规则是 "提交的文件字段名+FileName" 固定写法
*/
private String uploadImageFileName;
public File getUploadImage() {
return uploadImage;
}
public void setUploadImage(File uploadImage) {
this.uploadImage = uploadImage;
}
public String getUploadImageContentType() {
return uploadImageContentType;
}
public void setUploadImageContentType(String uploadImageContentType) {
System.out.println(uploadImageContentType);
this.uploadImageContentType = uploadImageContentType;
}
public String getUploadImageFileName() {
return uploadImageFileName;
}
public void setUploadImageFileName(String uploadImageFileName) {
System.out.println(uploadImageFileName);
this.uploadImageFileName = uploadImageFileName;
}
public String upload()throws Exception{
ServletContext servletContext = ServletActionContext.getServletContext();
String realPath = servletContext.getRealPath("/images");
File file = new File(realPath);
if (!file.exists()){//如果该目录不存在就创建
file.mkdirs();
}
FileUtils.copyFile(uploadImage,new File(file,uploadImageFileName));
return "success";
}
}
多文件上传:对应的字段改成数组类型或集合类型就可以自动接收多文件上传
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>多文件上传</title>
</head>
<body>
<form enctype="multipart/form-data" action="${pageContext.request.contextPath}/test/fileUpload" method="post">
文件1:<input type="file" name="uploadImage"><br>
文件2:<input type="file" name="uploadImage"><br>
文件3:<input type="file" name="uploadImage"><br>
<input type="submit" value="上传">
</form>
</body>
</html>
public class FileUploadAction {
//得到上传的文件
/**
* 该字段名称必须和页面上传的文件的参数名称一致,这样struts框架就会自动接收该文件
* 必须设置该字段的setter方法,
*/
private File[] uploadImage;
//得到文件的类型
/**
* 上传文件的类型,struts框架会自动帮我们获取文件的MIME类型,
* 该字段的命名规则是 "提交的文件字段名+ContentType" 固定写法,必须设置该字段的setter方法
*/
private String[] uploadImageContentType;
//得到文件的名称
/**
* 上传文件的文件名,带后缀的文件名。
* 该字段struts框架会帮我们字段获取并设置上信息,该字段也必须设置setter方法
* 该字段的命名规则是 "提交的文件字段名+FileName" 固定写法
*/
private String[] uploadImageFileName;
public File[] getUploadImage() {
return uploadImage;
}
public void setUploadImage(File[] uploadImage) {
this.uploadImage = uploadImage;
}
public String[] getUploadImageContentType() {
return uploadImageContentType;
}
public void setUploadImageContentType(String[] uploadImageContentType) {
this.uploadImageContentType = uploadImageContentType;
}
public String[] getUploadImageFileName() {
return uploadImageFileName;
}
public void setUploadImageFileName(String[] uploadImageFileName) {
this.uploadImageFileName = uploadImageFileName;
}
public String upload()throws Exception{
ServletContext servletContext = ServletActionContext.getServletContext();
String realPath = servletContext.getRealPath("/images");
File file = new File(realPath);
if (!file.exists()){//如果该目录不存在就创建
file.mkdirs();
}
for (int i = 0; i < uploadImage.length; i++) {
FileUtils.copyFile(uploadImage[i],new File(file,uploadImageFileName[i]));
}
return "success";
}
}
自定义拦截器
我们可以用拦截器去进行权限拦截等功能。
要自定义拦截器需要实现com.opensymphony.xwork2.interceptor.Interceptor这个接口。
示例:这个种方式把struts自带的拦截器都给屏蔽了,这样不好,我们还需要使用到struts自带的核心功能
public class PermissionInterceptor implements Interceptor {
@Override
public void destroy() {
}
@Override
public void init() {
}
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
//在该方法中做拦截处理
Map<String, Object> session = ActionContext.getContext().getSession();
Object user = session.get("user");
if (user!=null){
//actionInvocation.invoke() 这个方法就是调用的action中的用户调用的方法,我猜是用动态代理搞的
String invoke = actionInvocation.invoke();//放行 用户调用的方法
return invoke;//吧方法返回的字符串返回
}
return "failed";//用户没有权限
}
}
<package name="wgp2" namespace="/test" extends="struts-default">
<!--拦截器配置-->
<interceptors>
<interceptor name="permission" class="com.wgp.action.PermissionInterceptor"/>
</interceptors>
<action name="fileUpload" class="com.wgp.action.FileUploadAction" method="upload">
<!--为这个action配置拦截器-->
<interceptor-ref name="permission"></interceptor-ref>
<result name="success">/WEB-INF/page/hello.jsp</result>
</action>
</package>
struts本身的默认的拦截和我们自定义的拦截器都使用上:
示例:
只需要更改配置struts配置文件
<package name="wgp2" namespace="/test" extends="struts-default">
<!--拦截器配置-->
<interceptors>
<!--配置自定义的拦截器-->
<interceptor name="permission" class="com.wgp.action.PermissionInterceptor"/>
<!--第定义一个拦截器栈-->
<interceptor-stack name="permissionStack">
<!--添加struts的默认拦截器,这行代码要在自己的自定义的拦截器之前-->
<interceptor-ref name="defaultStack"/>
<!--添加自己定义的拦截器-->
<interceptor-ref name="permission"/>
</interceptor-stack>
</interceptors>
<action name="fileUpload" class="com.wgp.action.FileUploadAction" method="upload">
<!--为这个action配置拦截器,引用拦截器栈-->
<interceptor-ref name="permissionStack"/>
<result name="success">/WEB-INF/page/hello.jsp</result>
</action>
</package>
当我自定义拦截器的时候,在struts的配置文件中配置了我们的自定义的拦截器,那么struts框架的默认拦截器就不会再起作用了,我们需要显示的在配置文件添加上struts框架的默认拦截器,因为struts2中如文件上传、数据验证、获取客户端提交的参数、国际化等,都是struts2的默认拦截器帮我们做的,如果我们不引用就不能用这些功能了。系统默认的拦截的名字是defaultStack。
如果希望包下的所有action都使用自定义拦截器,可以通过<default-interceptor-ref name="permissionStack"/>把拦截器定义为默认拦截器。注意:每个包智能指定一个默认拦截器,另外,一旦我们为该包中的某个action显式指定了某个拦截器,则默认拦截器不会起作用了。如果即想让默认拦截器permissionStack起作用 还要给action配置另一个拦截器,那么可以在action标签内配置多个拦截器标签指定。
输入校验
在struts2中,我们可以实现对aciton的所有方法进行校验或者action的指定方法进行校验。
对于输入校验,struts2提供了两种实现方法:
1.采用手工编写代码实现。
2.基于xml配置方式实现。
手工代码实现方式
通过重新validate()方法实现,validate()方法会校验action中所有与execute方法签名相同的方法。当狗哥数据校验失败时,我们应该调用addFieldError()方法往系统的fieldErrors添加校验失败信息(为了使用addFieldError()方法,action可以继承ActionSupport),如果系统的fieldErrors包含失败信息,struts2会将请求转发到名为input的result。在input试图中可以通过< s:fielderror />显示失败信息。
示例:该示例中对action中的所有方法都会执行校验
//继承ActionSupport类
public class PersonAction extends ActionSupport {
private String username;
private String mobile;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String update(){
ActionContext.getContext().put("message","更新成功");
return "success";
}
public String save(){
ActionContext.getContext().put("message","保存成功");
return "success";
}
/**
*这个方法进行自动校验的,我们在复写这个方法来进行判断
*/
@Override
public void validate() {
super.validate();
if (this.username==null || "".equals(username.trim())){
addFieldError("username","用户名不能空!");
}
if (this.mobile==null || "".equals(mobile.trim())){
addFieldError("mobile","手机号不能为空!");
}else {
if (!Pattern.compile("^1[358]\\d{9}$").matcher(mobile).matches()){
addFieldError("mobile","手机号格式不对!");
}
}
}
}
<package name="wgp" namespace="/test" extends="struts-default">
<action name="person_*" class="com.wgp.action.PersonAction" method="{1}" >
<!--验证失败后,请求转发到input试图-->
<result name="input">/user.jsp</result>
<result name="success">/WEB-INF/page/hello.jsp</result>
</action>
</package>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>输入校验</title>
</head>
<body>
<%-- 在该页面中使用<s:fielderror /> 标签来显示失败信息--%>
<s:fielderror />
<form action="${pageContext.request.contextPath}/test/person_save" method="post" >
用户名:<input type="text" name="username"><br>
手机号:<input type="text" name="mobile"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
对action指定方法校验
通过validateXxx()方法实现,validateXxx()方法只会校验action中的方法名为xxx的方法。其中Xxx第一个字母要大写,当某个数据校验失败时,我们应该调用addFieldError()方法往系统的fieldErrors添加校验失败信息,如果系统的fieldError包含了失败信息,struts2会将请求转发到名为input的result标签。在input视图中可以通过< s:fielderror />显示失败信息。
示例:如上面的validate方法改成validateUpdate(),那么就会只对访问update()方法时才会进行校验。
输入校验的流程
- 类型转换器对请求参数执行类型转换,并把转换后的值赋给action中的属性。
- 如果在执行类型转换的过程中出现了异常,系统会将异常信息保存到ActionContext,conversionError拦截器将异常信息添加到fieldErrors里,不管类型转换是否出现异常,都会进入第3步
- 系统通过发射技术先调用action中的validateXxx()方法,Xxx为方法名。
- 再调用action中的validate()方法。
- 经过上面4步,如果系统中的fieldErrors存在错误信息,系统会自定将请求转发至名称为input视图。如果系统中的fieldErros没有任何错误信息,系统将执行action中的处理方法。
XML配置方式实现action的所有方法进行输入校验
使用xml配置方式实现输入校验时,Action也需要继承ActionSupport,并且提供校验文件,校验文件和action类放在同一个包下,文件的取名格式为:ActionClassName-validation.xml,其中ActionClassName为action的简单类名,-validation为固定写法。如果action类为com.wgp.UserAction,那么该文件的取名应为:UserAction-validation.xml。
下面是校验文件的模板:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.3//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<validators>
<field name="username">
<field-validator type="requiredstring">
<!--高版本不用配置该属性 默认就是去除空格的-->
<param name="trim">true</param>
<message>用户名不能为空!</message>
</field-validator>
<field-validator type="regex">
<!--这个regexExpression 其实就是regex这个自带标签对应的类的属性-->
<param name="regexExpression"><![CDATA[^1[358]\d{9}$]]></param>
<message>手机格式不正确!</message>
</field-validator>
</field>
</validators>
< field>指定action中要校验的属性,<field-validator>指定校验器,上面指定的校验器requiredstring是由系统提供的,系统提供了能满足大部分校验需求的校验器,这些校验器的定义可以在xwork-2.x.jar中com.opensymphony.xwork2.validator.validators下的default.xml中找到。高版本的比如struts2.5版本的中吧xwork2包集成到了struts2-core-2.5.18.jar包中。
<message>为校验失败后的提示信息,如果需要国际化,可以为message指定key属性,key的值为资源文件中的key。
在这个校验文件中,对action中字符串类型的username属性进行验证,首先要求调用trim()方法去掉空格,然后判断用户名是否为空。
[图片上传失败...(image-96e0c6-1608134184287)]
基于XML配置方式对action类中指定的方法实现输入校验
如果想要对action中的某个action方法实施校验,那么,校验文件的取名应为:ActionClassName-ActionName-validation.xml,其中ActionName为struts.xml中action的名称。
示例:
<action name="person_*" class="com.wgp.action.PersonAction" method="{1}" >
<result name="input">/user.jsp</result>
<result name="success">/WEB-INF/page/hello.jsp</result>
</action>
PersonAction中有以下两个方法:
public String add(){}
Public String update(){}
要对add()方法实施验证,校验文件的取名为:UserAction-person_add-validation.xml
要对update()方法实施验证,校验文件取名为:UserAction-person_update-validation.xml
基于XML校验的一些特点
当为某个action提供了ActionClassName-validation.xml和ActionClassName-ActionName-validation.xml两种规则的校验文件时,系统按下面顺序寻找校验文件:
先找ActionClassName-validation.xml ,然后再找ActionClassName-ActionName-validation.xml。
系统找到第一个校验文件时会继续找后面的校验文件,当搜索到所有校验文件时,会吧校验文件里的所有校验规则汇总,然后全部应用于action方法的校验。如果两个校验文件中指定的校验规则冲突,则只使用后面文件的校验规则。
当action继承了另一个action,父类action的校验文件会先被搜索到。也遵循以上规则,也就是说把父类的校验文件和子类的校验文件汇总,然后应用action的方法的校验。有冲突的后面的覆盖前面的。
国际化
Struts2 提供了全局范围的、包范围的、action范围的国际化文件。
准备资源文件,资源文件的命名格式如下:
baseName_language_country.properties
baseName_language.properties
baseName.properties
其中baseName是资源文件的基本名,我们可以自定义,但language和country必须是java支持的语言和国家。
如: 中国大陆:baseName_zh_CN.properties 美国:baseName_en_US.properties
现在为应用添加两个资源文件:
第一个存放中文:wgp_zh_CN.properties 内容为:welcome=欢迎
第二个放英语(美国):wgp_en_US.properties 内容为:welcome=welcome
对于中文的属性文件,我们编写好后,应该使用jdk提供的native2ascii命名把文件转换为unicode编码的文件。命令的使用方式如下:native2ascii 源文件.properties 目标文件.properties
这些属性文件放在和struts.xml同目录下。
配置全局资源与输出国际化信息
当准备好资源文件后,我们可以在struts.xml中通过<constant name="struts.custom.i18n.resources" value="wgp"/> wgp为资源文件的基本名。
后面我们就可以在页面或action中访问国际化信息:
- 在jsp页面中使用<s:text name="" />标签输出国际化信息:name为资源文件中的key
- 在action类中,可以继承ActionSupport,使用getText()方法得到国际化信息,该方法的第一个参数用于指定资源文件中的key。
- 在表单标签中,通过key属性指定资源文件中的key,如:<s:textfield name="realname" key="user"/>
国际化:输出带有占位符的国际化信息
资源文件中的内容如下:welcome={0},欢迎来到中国{1}
在jsp页面中输出带有占位符的国际化信息
<s:text name="welcome">
< s:param> < s:property value="realname" /> </ s:param>
< s:param> 学习</ s:param>
</s:text >
在action类中获取带占位符的国际化信息,可以使用getText(key,String[] args)或getText(String aTextName,List args)。
国际化:包范围资源文件
在一个大型项目中,整个应用有大量的内容需要实现国际化,如果我们把国际化的内容都放在全局的资源属性文件中,显然会导致资源文件过于庞大臃肿,不便于维护,这个时候我们可以只对不同模块,使用包范围来组织国际化文件。
方法如下:
在java的包下放置packege_language_country.properties资源文件,package为固定写法,处于该包及子包下的action都可以访问该资源文件。当炒作指定key的消息时,系统会先从package资源文件中查找,当找不到对应的key时,才会从常量<constant name="struts.custom.i18n.resources" value="wgp"/>指定的资源文件中查找。
国际化:Action范围的资源文件
我们可以为某个action单独指定资源文件,方法如下:在action类所在的路径,放置ActionClassName_language_country.properties资源文件,ActionClassName为action类的简单名称。
当查找指定key消息时,系统会先从ActionClassName_language_country.properties资源文件中查找,如果没有找到对应的key,然后沿着当前包往上炒作基本名为package的资源文件,一直找到最顶层包。如果还没有找到对应的key,最后从
<constant name="struts.custom.i18n.resources" value="wgp"/>指定的资源文件中寻找。
OGNL表达式语言
ognl是Object graphic Navigation Language(对象图导航语言)的缩写,它是一个开源项目。Struts2框架使用ognl作为默认的表达式语言。
对于el表达式,ognl提供了平时我们需要的一些功能更,如:
- 支持对象方法调用,如 xxx.sayHello();
- 支持类静态方法调用和值访问,表达式的格式为:@[类全名(包括包路径)] @[方法名 | 值名],例如:@java.lang.String@format('foo %s','bar')或@com.wgp.Constant@APP_NAME;
- 操作集合对象。
OGNL有一个上下文(Context)概念,说白了上下文就是一个MAP结构,它实现了java.utils.Map接口,在Struts2中上下文(Context)的实现为ActionContext,下面是上下文的结构示意图
[图片上传失败...(image-3c6ee2-1608134184288)]
访问上下文中的对象需要使用#符号标注命名空间,如#application、#session。
另外ognl会设定一个根对象(root对象),在Struts2中根对象就是ValueStack(值栈),如果要访问跟对象也就是值栈中的对象的属性,则可以省略#命名空间,直接访问该对象的属性即可。
在Struts2中,根对象ValueStack的实现类为OgnlValueStack,该对象不是我们想象的只存放单个值,而是存放一组对象。在OgnlValueStack类里面有一个List类型的root变量,就是使用他存放一组对象。
在root变量中处于第一位的对象叫栈顶对象。通常我们在ognl表达式里直接写上属性的名称即可访问root变量里对象的属性,搜索顺序是从栈顶对象开始寻找,如果栈顶对象不存在该属性,就会从第二个对象寻找,如果没有找到就从第三个对象寻找,依次往下访问,直到找到为止。
注意:在Struts2中,ognl表达式需要配合Struts标签才可以使用。如:<s:property value="name" />、<s:property value="#request.user" />,获取request域对象中user属性
由于ValueStack是Struts2中ognl的根对象,如果用户需要范围值栈中的对象,在jsp页面中可以直接通过下面的el表达式访问ValueStack中的对象的属性:${foo} //获得值栈中某个对象的foo属性
如果访问其他的Context中的对象,由于他们不是根对象,所以在访问时,需要添加#前缀。
- application对象:用于访问ServletContext,例如#application.username或者#application['username'],相当于调用了ServletContext的getAttribute("username")。
- session对象:用来访问HttpSession,例如#session.username 或者#session['username'],相当于调用了session.getAttribute("username")。
- request对象:用来访问HttpServletRequest属性(attribute)的map,例如#request.username或者#request['username'],相当于调用了request.getAttribute("username")。
- parameters对象:用于访问Http的请求参数,例如#parameters.username或者#parameters['username'],相当于调用了request.getParameter("username")。
- attr对象:用于按page->request->session->application顺序访问期属性。
之前我们写的action类里面的属性生成setter和getter方法后,jsp页面可以用el表达获取到属性值呢?,我们知道el表达式是获取域对象中的属性的${name}就相当于域对象调用getAttribute("name"),那么为什么Struts框架中的action中属性也可以被el表达式获取呢,是因为action是被放到valueStack中的,而Struts框架对HttpServletRequest进行了进一步的封装,
为什么使用EL表达式能够范围valueStack中的对象属性呢?
[图片上传失败...(image-e67666-1608134184288)]
采用ONGL表达式创建List/Map集合对象
如果需要一个集合元素的时候,我们可以使用ognl中同集合相关的表达式。使用如下代码直接生产一个List对象:
<s:set name="list" value="{'zhang','wang','li'}" />
<s:iterator value="#list">
<s:property />
</s:iterator >
Set标签用于将某个值放入指定范围。
scope:指定变量被放置的范围,该属性可以接受application、session、request、page或action。如果没有指定该属性,则默认放置在OGNLContext中。
value:赋给变量的,如果没有设置该属性,则将ValueStack栈顶的值赋值给变量。
生成一个Map对象:
<s:set name="foobar" value="#{'foo1':'bar1','foo2':'bar2'}" />
<s:iterator value="#foobar">
<s:property value="key" /> = <s:property value="value"/>
</s:iterator >
property标签
property标签用于输出指定值:
<s:set name="name" value="kk" />
<s:property value="#name" />
default:可选属性,如果需要输出的属性值为null,则显示属性指定的默认值。
escape:可选属性,指定是否格式化HTML代码。
value:可选属性,指定需要输出的属性值,如果没有指定该属性,则默认输出ValueStack栈顶的值。
id: 可选属性,指定该元素的标识。
采用OGNL表达式判断对象是否存在集合中
对于集合类型,ognl表达式可以使用in和not in 连个元素符号。其中,in表达式用来判断某个颜色是否在指定的集合对象中;not in判断某个元素是否不在指定的集合对象中,如下所示:
in表达式:
<s:if test=" 'foo' in {'foo','bar'} ">
在
</s:if >
<s:else >
不在
</ s:else>
not in 表达式:
<s:if test=" 'foo' not in {'foo','bar'} ">
不在
</s:if >
<s:else >
在
</ s:else>
OGNL表达式的投影功
除了in和not in之外,ognl还允许使用某个规则获得集合对象的子集,常用的有一下3个相关操作符。
?: 获得所有符号逻辑的元素
^: 获得符合路径的第一个元素
$: 获得符合逻辑的最后一个元素
例如代码:
<s:iterator value="books.{?#this.price >35}">
<s:property value="title" /> -$<s:property value="price" />
</ s:iterator>
在上面代码中,直接在集合后面紧跟.{}运算符表明取出该集合的子集,{}内的表达式用于获取符合添加的元素,this指的是为了从大集合books筛选数据到小集合,需要对大集合books进行迭代,this代表当前迭代的元素。本例的表达式用于获取集合中价格大于35的书集合。
public class BookAction extends ActionSupport{
private List<Book> books
//省略 books的setter和getter方法
public String excute(){
books = new LinkedList<Book>();
books.add(new Book("A123","spring"),67)
books.add(new Book("A456","ejb3.0"),15)
}
}
OGNL表达式使用比较少,我们完全可以使用jstl和el表达式想以前学习javaweb一样,
Struts2常用标签
iterator标签用于对集合进行迭代,包含list、set和数组。
value:可选属性,指定被迭代的集合,如果没有设置该属性,则使用ValueStack栈顶的集合。
id:可选属性,指定集合里元素的id(已过时)
status:可选属性,该属性指定迭代是的IteratorStatus实例,该实例包含如下几个方法:
int getCount() :返回当前迭代了几个元素
int getIndex(): 返回当前迭代元素的索引
boolean isEven(): 返回当前被迭代元素的索引是否是偶数
boolean isOdd(): 返回当前被迭代元素的索引是否是奇数
boolean isFirst(): 返回当前被迭代元素是否是第一个元素
boolean isLast(): 返回当前被迭代元素是否是最后一个元素
url标签
<s:url action="hello_add" namspace="/test" >
<s:param name="personid" value="23"/>
</ s:url>
生成的路径是/struts/test/hello_add.action?personid=23
当标签的属性值作为字符串类型处理时,”%“符号的用途是计算ognl表达式的值。
<s:set name="myurl" value=" 'http://www.baidu.com'" />
<s:url value="#myurl" /> 输出结果是:#myurl
<s:url value="%{#myurl}" /> 输出结果是:http://www.baidu.com
表单标签—chackboxlist复选框
如果集合为list
<s:checkboxlist name="list" list="{'java','.net','php'}" value="{'java','.net'}" />
上面这句代码会生成如下HTML代码:
<input type="checkbox" name="list" value="java" checked="checked" /><label>java</label>
<input type="checkbox" name="list" value=".net" checked="checked" /><label>.net</label>
<input type="checkbox" name="list" value="php" /><label>php</label>
如果集合是Map
那么<s:checkboxlist name="map" list="#{1:'瑜伽用品', 2:'户外用品', 3:'球类',4:'自行车'}" listKey="key" listValue="value" value="{1,2,3}" />
生成的html代码如下:
<input type="checkbox" name="map" value="1" checked="checked" /><label>瑜伽用品</label>
<input type="checkbox" name="map" value="2" checked="checked" /><label>户外用品</label>
<input type="checkbox" name="map" value="3" checked="checked" /><label>球类</label>
<input type="checkbox" name="map" value="4" /><label>自行车</label>
如果集合里存放的是javabean
<%
Person p1 = new Person(1,"第一个");
Person p2 = new Person(2,"第二个");
list.add(p1);
list.add(p2);
request.setAttribute("persons",list);
%>
那么<s:checkboxlist name="beans" list="#request.persons" listKey="personid" listValue="name" />
personid和name为Person的属性
会生成如下html代码:
<input type="checkbox" name="beans" value="1" /><label>第一个</label>
<input type="checkbox" name="beans" value="2" /><label>第二个</label>
表单标签—radio单选框
该标签的使用和CheckBoxlist复选框相同。
表单标签-select下拉框
<s:select name="list" list="{'java','.net'}" value="java" />
生成的html页面代码:
<select name="list" id ="list">
<option value="java" selected="selected">java</option>
<option value=".net" >.net</option>
</select>
如果集合里是javabean数据或map数据 都是类似的。
防止表单重复提交<s:token />
<s:token />标签防止重复提交,用法如下:
第一步:在表单中加入<s:token />
<s:form action="hello_add" method="post" namespace="/test">
<s:textfield name="person.name"/>
<s:token />
<s:submit />
</s:form >
第二步:
<action name="hello_*" class="com.wgp.HelloAction" method="{1}">
<interceptor-ref name="defaultStack" />
<interceptort-ref name="token" />
<result name="invalid.token">/WEB-INF/page/message.jsp</result>
<result>/WEB-INF/page/result.jsp</result>
</ation>
以上配置加入了token拦截器和 invalid.token结果,因为token拦截器在会话的token与请求的token不一致时,将会直接返回invalid.token结果。
在debug状态,控制台出现错误信息,因为Action中并没有Struts.token和Struts.token.name属性,我们不用关心这个错误。
Struts2+Spring+Hibernate整合
先集成spring,再集成hibernate,最后Struts。