Action的实现方式和struts.xml配置的详细解释,Struts2的简单执行过程(二)
Action的实现方式和struts.xml配置的详细解释,Struts2的简单执行过程(二)
我把你的头像,设置成我的名字,此刻你便与我同在。
我把你的名字,写进我的代码里面,以后,我的世界便存在着你。
“两个蝴蝶飞”特别喜欢”java1234知识分享网“小峰的实用主义,所以本文及其系列文章均是采用实用主义,从项目和代码的角度去分析。由于本人经验有限,嘴皮子不溜,所以学术性,概念性,底层性的知识点暂时不做介绍。文章中有错误之处,欢迎拍砖和指点。特别感谢”java1234知识分享网 “和”黑马程序员官网“,所有的资料大部分是两者提供,为了方便书写,故不一一指名出处,请谅解,非常抱歉。
上一章简单介绍了Struts2的开发”Hello World”,如果没有看过,请观看上一章
一 Action的实现方式
上一章开发的HelloAction,并没有继承任何类或者实现任何接口,但是必须有一个execute() 方法,方法返回值也必须是String类型,代码不具体侵入性,但是并不能使人看得出这个类是干什么的,甚至不能区分这个类与普通的Java类有什么区别,所以不具有规范性。 我们开发者在开发的时候,希望自己写的这个Action类能够具有规范性,且已经支持某些功能。
一.一 第一种实现方式(普通Java类,里面包含execute()方法)
package com.yjl.web.action; import org.apache.log4j.Logger; /** * @author 两个蝴蝶飞 * @version 创建时间:2018年8月23日 上午9:41:32 * @description 第一种实现方式,普通java类, * 有一个execute()方法,也可以多写几个方法,用action中的标签method来控制,可以正常访问。 */ public class Hello1Action { private static Logger logger=Logger.getLogger(Hello1Action.class); public String execute() { logger.info("两个蝴蝶飞,web层你好"); return "success"; } }
没有侵入性,但不具有开发时要求的规范性,且不支持某些struts自带的功能。
一.二 第二种实现方式(实现Action接口)
package com.yjl.web.action; import com.opensymphony.xwork2.Action; /** * @author 两个蝴蝶飞 * @version 创建时间:2018年8月23日 上午10:54:03 * @description 第二种实现方式,实现Action接口,重写里面的execute()方法 * 有一个execute()方法和五个String类型的常量 */ public class Hello2Action implements Action{ @Override public String execute() throws Exception { return Action.SUCCESS; //return Action.ERROR; //return Action.LOGIN; //return Action.NONE; //return Action.INPUT; } }
注意,Action接口是xwork2包下的接口。
实现了Action接口,使开发者能够看出来这是一个Action,具有了一定程度上的开发规范,但是是Action接口,所以必须要重写execute()方法。 一般自己写Action,构思好之后上来就直接add(), edit(), delete(). select() 这些业务方法,每次都要重写execute()方法,不太方便。 而且这种方式不具有struts2中某些功能,如验证框架和国际化。 Action中接口中有五个常用的结果字符串(好多方法都返回success,error,login,故将其封装了一下) .这些字符串虽然是大写,然而真实的值是全部小写.
package com.opensymphony.xwork2; public abstract interface Action { public static final String SUCCESS = "success"; public static final String NONE = "none"; public static final String ERROR = "error"; public static final String INPUT = "input"; public static final String LOGIN = "login"; public abstract String execute() throws Exception; }
一.三 继承ActionSupport类(官方推荐)
package com.yjl.web.action; import com.opensymphony.xwork2.ActionSupport; /** * @author 两个蝴蝶飞 * @version 创建时间:2018年8月23日 上午11:04:20 * @description 第三种方式,继承ActionSupport类。 * ActionSupport类实现了Action接口,也有Action中的五个常量. */ public class Hello3Action extends ActionSupport{ public String list() { return "list"; } }
继承了ActionSupport类,不需要重新写execute()方法,具备Action中的五个常量,并且这个ActionSupport类还实现了其他接口,
public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable{ }
如验证框架(Validateable,ValidationAware),国际化(LocaleProvider)。
以后开发中,使用ActionSupport框架。
二 struts.xml中节点的详细解释
在src下有一个struts.xml的配置文件,它配置所有开发者自己编写实现的Action,是struts2的核心,不能改变名称。注意,是struts.xml,并不是struts2.xml,并没有那个2。(Strut2虽然相对于struts是一个飞跃,与webwork更像一些,但有些基本的东西还是保留了,赞一个)。
在struts.xml中,最上面是一个约束,<struts></struts>是一个根节点。
二.一 修改常量节点 <constant></constant>
在struts-core.jar核心包下,有一个包org.apache.struts2包下,有一个default.properties属性文件,里面记录了很多常用的常量,
其中常见的有:
struts.i18n.encoding=UTF-8 struts.multipart.maxSize=2097152 struts.action.extension=action,, struts.enable.DynamicMethodInvocation = false struts.devMode = false struts.ui.theme=xhtml struts.ognl.allowStaticMethodAccess=false
建议修改后的值为:
###国际化操作,编码格式为UTF-8 struts.i18n.encoding=UTF-8 ###上传文件时最大的上传大小,默认为2M. 根据项目情况具体填写值,建议后面加两个00 struts.multipart.maxSize=209715200 ###struts的访问后缀名 struts.action.extension=action ###struts是否可以访问静态方法 struts.enable.DynamicMethodInvocation =true ###struts是否是开发者模式 struts.devMode =true ###struts中ui标签的主题,建议为simple struts.ui.theme=simple ###ognl中是否可以访问静态方法,为true struts.ognl.allowStaticMethodAccess=true
可以在struts.xml中进行相应的修改,如
<!--修改国际化编码 --> <constant name="struts.i18n.encoding" value="UTF-8"></constant> <!--修改是否为开发者模式 --> <constant name="struts.devMode" value="true"></constant>
按照name,value值的形式进行填写。
也可以在src下新建一个struts.properties,然后将这些值放置进去,struts也会自动struts.propeties中的常量值的。
也可以在web.xml中,在<filter></filter>中,以<init-param></init-param>局部参数的形式传递进去。
建议使用第一种方式,在struts.xml中用<constant></constant>,毕竟这个文件常常打开,出错了也容易发现。
二.二 分模块开发<include file=”” />
在实际的项目中,有很多的模块,如果所有的配置都放在一个struts.xml,那么一旦这个struts.xml被其他人误改错了一下,那么其他人的项目也是无法运行的,而且想配置自己模块的信息的时候,struts.xml太长,也不容易找,所以最好是分模块开发,利用<include file=”具体文件名” /> ,类似 于jsp中的<include file=”” />静态包含一样。 所以建议每一个模块都写一个模块.xml,然后在struts.xml中引入即可。如有三个模块 User模块和Class,Course,那么可以将User的配置放置在user.xml中,Class配置放置在class.xml中,course模块放置在course.xml,在struts.xml中只需要
<include file="user.xml"></include> <include file="class.xml"></include> <include file="course.xml"></include>
静态包含即可。 注意,file的文件路径引用。 正确的位置引用,点击ctrl+模块.xml时,可以正确地跳转到相应的.xml文件中。如果没有跳转和反应,那说明位置引用错误,重新检查一下。
二.三 最牛逼的节点<package>
在struts.xml配置文件中,最牛的节点就是package节点。 package,分包。 可以将action进行分包处理。将每一个action或者每一组action进行隔开,类似于java中package的概念。
<package> 节点的使用
<package name="hello" extends="struts-default" namespace="/"> <!--具体的Action--> </package>
package中name节点是package的名字,是独一无二的,不能够重复。 最好与模块名相同或者起一个有意义的名称。
extends节点表示继承,即package之间可以相互的继承,来避免重复化功能的编写。 默认为struts-default。 struts-default中struts已经定义了很多功能,开发者自己写的包只需要extends 这个包名struts-default,就拥有了struts已经定义好的功能。 如拦截器中的大量的功能。 用户也可以自己继承自己所写的包 。如父包名为<package name=”parent” extends=”struts-default”>
那么子包只需要<package name=”child” extends=”parent”>, child包不但拥有struts-default的功能,也拥有parent包中的特殊功能,这也是Java的多重继承的体现。 所以package的name也不能随便起。
namespace节点表示命名空间,以/开头,默认是”/” 。是为了在访问路径和访问请求url方面体现package的分包作用. package中的name是在配置文件中体现分包,namespace是在url中体现分包。 如果action过多的话,namespace 最好与name名称一样,如果action少的话,也可以直接”/” . package中的namespace的值与action中name的值,共同构成了完整的访问请求路径。
<package></package> 子节点<action></action>节点的使用
在Hello3Action中定义两个方法,一个是list()查询,一个是add()添加的方法。
package com.yjl.web.action; import org.apache.log4j.Logger; import com.opensymphony.xwork2.ActionSupport; /** * @author 两个蝴蝶飞 * @version 创建时间:2018年8月23日 上午11:04:20 * @description 测试action标签中method的方法访问 */ public class Hello3Action extends ActionSupport{ private static final long serialVersionUID = 8737138848863458260L; Logger logger=Logger.getLogger(Hello3Action.class); public String list() { logger.info("执行list方法"); return "list"; } public String add() { logger.info("执行add方法"); return "add"; } }
<action></action> 标签,有三个基本的属性,
<action name="list" class="com.yjl.web.action.Hello3Action" method="list"> </action>
其中name为action的名字,表示区别一个package包下的不同的action。 其中这个name的值,不应该随便取,应该是要访问的方法名。 在浏览器客户端输入的url:= /项目名/package的namespace/action的name值.action;
class为要访问的那个Action的全限定名称,是class,用.(点)进行分隔,不是/分隔。
method为要访问的那个方法,类 extends ActionSupport 后,有很多很多的方法,如list(), add(), delete()等,那么怎么知道具体要访问哪个方法呢? 用method这个属性. method=”要方法的方法名” ,是方法名。
action还有一个节点是converter,表示所用的是哪一个类型转换器。(后面会有相应的解释)
在本实例了有两个方法,所以要进行写两个Action, 一个Action类中会有多个方法,难道要一个个配置多个Action吗?
配置Action的三种形式:
第一种形式,通过配置method的属性完成
<action name="list" class="com.yjl.web.action.Hello3Action" method="list"> </action> <action name="add" class="com.yjl.web.action.Hello3Action" method="add"> </action>
缺点: 有几个方法,就要配置有几个action,然而方法一般有很多。 而且action的子节点<result>, 会造成一个action配置一个或者俩个result,造成大量的重复。
第二种形式, 通过通配符的配置完成。
<action name="Hello3_*" class="com.yjl.web.action.Hello3Action" method="{1}"> </action>
name的值为: 类简写名(去掉Action后)_* method中的值取第一个{1},从1开始,不是从0开始。
这样访问Hello3Action中的list方法,访问路径就是 Hello3_list
访问Hello3Action中的add方法,访问路径就是Hello3_add
非常简化了action的配置。
也有的人配置的更狠, 会配置成*_*, 即:
<action name="*_*" class="com.yjl.web.action.{1}Action" method="{2}"> </action>
这样虽说更加简化了开发
User类中的list就是User_list, User类中的add就是User_add,
Class类中的list就是Class_list,Class类中的add就是Class_add
不建议这么写,如果这么写的话,访问的url必须是小写(即使与spring整合后可以写成小写,也不建议)。
action中配置的主要作用是关于对子节点result 的处理, 要具体要跳转到哪个路径或者哪个新的action。
这样配置的话,会造成result也必须要设置成这个模样(其实result也不建议设置成{} 占位符的方式),这样会乱。
User类的list方法的返回值与Class类中的方法返回值,无法区别,单纯的{1}是不行的,要result name=”{1}_{2}”, 如果是type=”chain”或者type=”redirectAction”会更加麻烦。 建议只用一个通配符进行访问,Hello3_*这样形式,那么result就直接name={1}即可。
第三种形式: 动态方法访问
不是用{} 通配符,而是用! 号。 即:
UserAction中list方法() url: userAction!list.action UserAction中add方法() url: userAction!add.action ClassAction中list方法() url: classAction!list.action ClassAction中add方法() url: classAction!add.action
这样访问也特别的方便。
这样的话, action中只需要配置name和class即可。 method已经由外部指定了,不需要写method的值了。
如果是UserAction的话,配置应该是:
<action name="userAction" class="com.yjl.web.action.UserAction" >
</action>
ClassAction的话,配置应该是
<action name="classAction" class="com.yjl.web.action.ClassAction" >
</action>
注意,动态方法访问默认是关闭的,想要开启需要改变一下struts.enable.DynamicMethodInvocation的值。
<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant>
action子节点result的配置
result表示结果,是对方法的返回值进行相应的分析。有两个属性,name和type
<result name="success" type="dispatcher">/index.jsp</result>
其中name的值要与方法的返回值相同,如 list方法返回值是return SUCCESS,那么这个list方法的返回值对应的result的值就是
<result name=”success”> ,如果返回是”hello”, 那么这个name的返回值就是 <result name=”hello”>
如果在action中配置通配符, name=Hello3_*形式,method=”{1}”, 那么为了简化result的配置,可以将result配置成 name={1},
相应的.jsp, 也可以配置成/{1}.jsp。这样需要保证Action中方法的名称与返回值的名称相同,并且与跳转到的jsp的名称也要相同,
建议最好result还是一个个写出来吧。
result中type五种常见的形式, dispatcher(转发到jsp),redirect(重定向到jsp), chain(转发到另外一个方法),redirectAction(重定向到另外一个方法),stream(上传和下载流)
其中dispathcer和redirect是对jsp的操作,如果传递了数据,用dispather,没有传递数据,用redirect (区别见重定向与转发的区别)
chain,redirectAction是对action的操作,一般用于这同一个类中两个方法之间的跳转,即同一个package包下的跳转,都是用redirectAction的。
如add()添加成功之后,需要跳转到list()方法进行显示,这时为:
<result name="add" type="redirectAction">Hello3_list</result>
地址url也会相应的改变,如果是chain的话,地址栏是不会改变的,重新点击的话,会重新添加一个一样的数据。(当然id主键还是不同的)。
如果是不同包之间的action的跳转呢?
<result name="add" type="redirectAction">
<!-- 要跳转到哪一个命名空间,即哪一个包 -->
<param name="namespace">/class</param>
<!-- 要跳转到哪一个Action 不加后缀 -->
<param name="actionName">Hello2Action</param>
<!-- 跳转到哪一个方法 -->
<param name="method">list</param>
<!-- 可能要传递的参数. 用ognl表达式,根据情况添加 -->
<param name="id">${id}</param>
</result>
可以带参,也可以不带参数传递。
全局结果页面与局部结果页面。
这个全局是相对于package来说的,是package中的全局,并不是所有的struts.xml中的全局,所以全局结果的节点位置应该放在package节点里面,与action节点平行。 用<global-results> </global-result> 节点。
常用的全局结果页面有, error错误页面,页面出错了都显示这个页面,
login 登录页面, 如果没有登录,输入任何url都会跳转到login页面
noprivilege 没有权限页面,如果用户没有权限访问了某一个页面,会给出相应的提示。
<global-results> <result name="error">/error/error.jsp</result> <result name="login">/login.jsp</result> <result name="noprivilege">/noprivilege.jsp</result> </global-results>
当全局结果页面与局部结果页面发生冲突时,以局部结果页面为准。 与全局变量与局部变量一致。
三 Struts2的执行流程
当用户在客户端发送一个请求后,如常用的标准的http://localhost:8080/Struts_Hello/user/User_add.action时,
会经过前端控制器(StrutsPrepareAndExecuteFilter) 过滤器,执行一连串的过滤器链,然后根据user 找到了对应的package的namespape,进入到具体的package包下。 利用通配符的方式进行访问,User_add会进行匹配相应的action,根据class和method找到是哪一个类的哪一个方法,在实例化类Action之前,会先执行拦截器。通过反射实例化类,运行方法, 方法运行成功之后,有一个返回值,这个返回值会与刚才action下的<result> 中的name进行相应的匹配,匹配到哪一个,就执行哪一个result。 如果是diapatcher或者redirect,就显示到相应的.jsp页面(带有数据), 如果是chain或者redirectAction,那么就去执行那一个方法(如A,给的是action的name,会去找action),那个方法(A)执行完毕回来后,就找A下面的result进行匹配,匹配成功之后进行返回。
执行过程图如下: