将root 当成arraylist放入数据sturts2 入门笔记
刚启动idea 就报出错误
[2019-04-14 03:08:22,780] Artifact 05-sturts2:war exploded: Error during artifact deployment. See server log for details.
解决办法:
file-project structure -probleam 里面有一个fix
第二个错误:
就是需要的jar包没有加全
这几个是创建idea 时自带的jar包,我使用的jar包是5版本的
commons-fileupload.jar commons-io.jar commons-lang.jar freemarker.jar log4j.jar ognl.jar struts2-core.jar
这个时候运行报错
需要再添加上几个jar包
asm-5.2.jar asm-commons-5.2.jar asm-tree-5.2.jar commons-lang3-3.8.1.jar javassist-3.20.0-GA.jar
这个时候idea 自动生生的sturts.xml文件是标错的
idea的配置文件扫描的类是
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
但是新的jar包中去掉了ng这个包,所以只需要改成下面的就可以了。学完springmvc后再看sturts2 真是挺简单的。
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
下面对jar包进行一下分析
asm-5.2.jar asm-commons-5.2.jar asm-tree-5.2.jar 红色的是用于实现代理的主要jar包
javassist-3.20.0-GA.jar
commons-lang3-3.8.1.jar javassist-3.20.0-GA.jar
commons-fileupload.jar
commons-io.jar 文件上传
commons-lang.jar 这个是定义的基本数据类型以及对它的扩展
freemarker.jar stutrs2 的ui
log4j.jar 日志
ognl.jar 对象图导航语言,是一种表达式是一个独立的项目是被sturts2引进来的
struts2-core.jar 核心jar包
X先放上一个简单的例子
jsp
<%-- Created by IntelliJ IDEA. User: lenovo Date: 2019/4/14 Time: 15:04 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <form name="f1" action="loginaction/login.action" method="post"> 姓名: <input type="text" name="name"/> 年龄: <input type="password" name="password"/> <input type="submit" value="提交"/> </form> </body> </html>
sturts.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> <package name="loginaction" namespace="/loginaction" extends="struts-default"> <action name="login" class="com.sturts2.action.loginAction"> <result name="success">/welcome.jsp</result> </action> </package> </struts>
<%-- Created by IntelliJ IDEA. User: lenovo Date: 2019/4/14 Time: 15:35 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> name =${name} password = ${password} </body> </html>
需要注意的地方就是你的表单的属性名 name 必须要和你的实体类中的名字相同
讲解一下配置文件的加载顺序
<?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> 表单中的action中的地址就是loginaction/login.action 也就是名称空间加上 标签<action>中的name 从而找到要加载的试题类loginAction ,如果没有在
<action>标签中指定method 那么 所执行的action 方法 也就是 execute 这个名字不能变否则找不到
必须要写成 public String execute(){ return "success"} 否则不会执行 正好对应标签中的 <result>标签中的success
<package name="loginaction" namespace="/loginaction" extends="struts-default"> <action name="login" class="com.sturts2.action.loginAction"> <result name="success">/welcome.jsp</result> </action> </package> </struts>
struts.properties 文件修改默认配置
struts.xml 修改访问路径的后缀名
action="loginaction/login.do"
action="loginaction/login.php"
action="loginaction/login.html"
struts.action.extension=do,php,html
web.xml 和struts.properties 和struts.xml 相比谁的优先级比较高
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class> <init-param> <param-name>struts.action.extension</param-name> <param-value>do</param-value> </init-param> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
<?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="go"></constant> <package name="loginaction" namespace="/loginaction" extends="struts-default"> <action name="login" class="com.sturts2.action.loginAction"> <result name="success">/welcome.jsp</result> </action> </package> </struts>
运行后可知,web.xml>struts.properties>struts.xml
但是我们一般不在web.xml 或者struts.properties中进行设置。
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.action.extension" value="go"></constant> <package name="aaa-bbb-loginaction" namespace="/aaa/bbb/loginaction" extends="struts-default"> <action name="login" class="com.sturts2.action.loginAction"> <result name="success">/welcome.jsp</result> </action>
<package name="aaa-loginaction" namespace="/aaa/loginaction" extends="struts-default"> <action name="login" class="com.sturts2.action.loginAction"> <result name="success">/welcome.jsp</result> </action>
</package> </struts>
一般如果我们有多个package 那么package 中的name 与namespace都是相对应的
name="aaa-bbb-loginaction" namespace="/aaa/bbb/loginaction"
name="aaa-loginaction" namespace="/aaa/loginaction"
而且,namspace中也可以不写但是namespace中至少要写上/
接下来添加配置文件的属性
<package name="register" namespace="/registeraction" extends="struts-default"> <action name="register" class="com.sturts2.action.RegisterAction" method="register"> 加上这个就意味着我们可以自定义方法名而不是execute <result name="success">/register.jsp</result> </action> </package>
我们默认的页面是进行转发
<result type="dispatcher" name="success">/register.jsp</result>
下面是进行重定向
<result type="redirect" name="success">/welcome.jsp?uname=${name}&upassword=${password}</result>
还有另外一种写法
<package name="loginaction" namespace="/loginaction" extends="struts-default"> <action name="login" class="com.sturts2.action.loginAction"> <!--<result name="success">/welcome.jsp</result>--> <!-- <result type="redirect" name="success">/welcome.jsp?uname=${name}&upassword=${password}</result>--> <result type="redirectAction" name="success"> <param name="actionMapper">actionName</param> <param name="uname">${uname}</param> <param name="upassword">${upassword}</param> </result> </action> <action name="actionName"> <result name="success">/welcome.jsp</result> </action> </package>
解决配置文件冗余
先看一个例子
<package name="some" namespace="/someAction" extends="struts-default"> <action name="first" class="com.sturts2.action.someAction" method="doFirst"> <result name="ar">/apage.jsp</result> <result name="br">/bpage.jsp</result> <result name="success"> /welcome.jsp</result> </action> <action name="second" class="com.sturts2.action.someAction" method="doSecond"> <result name="ar">/apage.jsp</result> <result name="br">/bpage.jsp</result> <result name="success">/welcome.jsp</result> </action> </package>
这些都是重复的代码下面就是简化
包范围全局视图
<package name="some" namespace="/someAction" extends="struts-default"> <global-results> <result name="ar">/apage.jsp</result> <result name="br">/bpage.jsp</result> </global-results> <action name="first" class="com.sturts2.action.someAction" method="doFirst"> <result name="success"> /welcome.jsp</result> </action> <action name="second" class="com.sturts2.action.someAction" method="doSecond"> <result name="success">/welcome.jsp</result> </action> </package>
继续抽取
<!--这个包不定义action也就不会被访问,所以名称空间也可以省略了那么也可以定义成抽象的,一旦定义成抽象的就不能再定义action--> <package name="basepackage" extends="struts-default" abstract="true"> <global-results> <result name="ar">/apage.jsp</result> <result name="br">/bpage.jsp</result> </global-results> </package> <package name="some" namespace="/someAction" extends="struts-default"> <action name="first" class="com.sturts2.action.someAction" method="doFirst"> <result name="success"> /welcome.jsp</result> </action> <action name="second" class="com.sturts2.action.someAction" method="doSecond"> <result name="success">/welcome.jsp</result> </action> </package>
值栈操作(想root中显示放入数据)
package com.sturts2.action; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.util.ValueStack; import org.apache.struts2.ServletActionContext; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; /** * Description: 05-sturts2 * Created by lenovo on 2019/4/14 22:18 */ public class resolveValueStack { public String execute(){ //从request域中获取valueStack String key = ServletActionContext.STRUTS_VALUESTACK_KEY; HttpServletRequest request = ServletActionContext.getRequest(); ValueStack value_stack = (ValueStack) request.getAttribute(key); /* Map<String, Object> context = value_stack.getContext(); */ //解析这个root属性的本质是ArrayList 特点存入的数据是无名对象,只有对象没有名称 然后将user对象进行了压栈 loginAction user = new loginAction("zhangsan", "23"); value_stack.getRoot().push(user); //有名对象就是放入到map中 HashMap<String, loginAction> map = new HashMap<>(); loginAction user1 = new loginAction("lisi", "23"); map.put("user1",user1); value_stack.push(map); //向值栈中直接放入有名对象,即map loginAction user2 = new loginAction("wangwu", "23"); value_stack.set("user2",user2); loginAction user3 = new loginAction("zhaoliu", "23"); /*取出一个进行查看将root当成arraylist*/ value_stack.getRoot().get(2); value_stack.getRoot().add(user3); return "success"; } }
<%-- Created by IntelliJ IDEA. User: lenovo Date: 2019/4/14 Time: 15:04 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="s" uri="/struts-tags"%> <html> <head> <title>$Title$</title> </head> <body> PI = <s:property value="@java.lang.Math@PI"></s:property> random = <s:property value="@java.lang.Math@random()*100 "></s:property> <%-- <form name="f1" action="loginaction/login.action" method="post"> 姓名: <input type="text" name="name"/> 年龄: <input type="password" name="password"/> <input type="submit" value="提交"/> </form> <a href="registeraction/register.action">注册</a>--%> <%--读取非根数据--%> <s:property value="#city"></s:property> <s:debug></s:debug> name: <s:property value="name"></s:property> age: <s:property value="age"></s:property> name: <s:property value="user1.name"></s:property> age: <s:property value="user1.age"></s:property> name: <s:property value="user2.name"></s:property> age: <s:property value="user2.age"></s:property> </body> </html>
向root中隐士放入数据
当请求到来的时候将数据放在了root 和context 中 带有#号的数据从contxt中获取数据,不带#的从root中获取数据
<%-- Created by IntelliJ IDEA. User: lenovo Date: 2019/4/14 Time: 15:04 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="s" uri="/struts-tags"%> <html> <head> <title>$Title$</title> </head> <body> PI = <s:property value="@java.lang.Math@PI"></s:property> random = <s:property value="@java.lang.Math@random()*100 "></s:property> <%-- <form name="f1" action="loginaction/login.action" method="post"> 姓名: <input type="text" name="name"/> 年龄: <input type="password" name="password"/> <input type="submit" value="提交"/> </form> <a href="registeraction/register.action">注册</a>--%> <%--读取非根数据--%> <s:property value="#city"></s:property> <s:debug></s:debug> name: <s:property value="name"></s:property> age: <s:property value="age"></s:property> name: <s:property value="user1.name"></s:property> age: <s:property value="user1.age"></s:property> <%--request.name 底层执行的是request.getAttribute("name")--%> request.name: <s:property value="#request.name"></s:property> request.age: <s:property value="#request.age"></s:property> name: <s:property value="user2.name"></s:property> age: <s:property value="user2.age"></s:property> <%--底层执行的是request.getParameter("name")--%> parameters.name: <s:property value="#parameters.name"></s:property> parameters.age: <s:property value="#parameters.age"></s:property> action.name: <s:property value="action.name"></s:property> action.age: <s:property value="action.age"></s:property> </body> </html>
数据的加载顺序,先找root 再找 context
request中的加载循序,当通过action将值保存在context 和root中的值不同时,分别获取到自己存放的相应的值,但是如果通过request进行获取,那么就会取到和root
中的值相同的结果。
OGNL标签
List
<s:set var="mylist" value="{'zs','li','wu'}"> <s:iterator value="#mylist"> <s:property/> 尽管没有指定但是也取到了相应的值<%--iterator默认会将当前的迭代对象放入到值栈栈顶--%> <%--properties 标签默认会输出值栈栈顶元素--%>
</s:iterator> </s:set>
Map
<s:set var="mymap" value="#{'mobile':'123456','qq':'12455'}"> <s:iterator value="#mymap" var="entity"> <s:property value="entity"/> </s:iterator> <s:iterator value="#mymap"> <s:property value="key"/> =<s:property value="value"/> </s:iterator> </s:set>
集合元素的判断
<s:property value="'zs' in #mylist"/> <s:property value="'zs' not in #mylist"/>
创建bena
<%--创建bena--%> <s:bean name="com.sturts2.java.Student" var="student2"> <s:param name="name" value="'zhangsna'"/> <s:param name="age" value="2"/> </s:bean> <s:bean name="com.sturts2.java.Student" var="student3"> <s:param name="name" value="'lisi'"/> <s:param name="age" value="2"/> </s:bean> <s:bean name="com.sturts2.java.Student" var="student4"> <s:param name="name" value="'wu'"/> <s:param name="age" value="2"/> </s:bean> <%--集合投影将前面的三个Student对象的name 属性值再组成一个list--%> <s:set var="students" value="{student2,student3,student4}"/> <s:set var="studentsnames" value="#students.{name}"/> <s:iterator value="#studentsnames"> <s:property/> </s:iterator> <s:property value="#student2"/>
集合查询
动态方法调用
public String doFirst(){ if("aaa".equals(name)){ return "ar"; } if ("bbb".equals(name)){ return "br"; } return "success"; } public String doSecond(){ if (age<15){ return "ar"; } if (age>60){ return "br"; } return "welcome"; }
<package name="some" namespace="/someAction" extends="basepackage"> <action name="first" class="com.sturts2.action.someAction" method="doFirst"> <result name="success"> /welcome.jsp</result> </action> <action name="second" class="com.sturts2.action.someAction" method="doSecond"> <result name="success">/welcome.jsp</result> </action> </package>
这种方式显得代码不够灵活,使用动态方法调用
<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant> <package name="some" namespace="/someAction" extends="basepackage"> <action name="some" class="com.sturts2.action.someAction"> <result name="success"> /welcome.jsp</result> </action> </package>
在浏览器中进行访问的时候需要在命名空间后加上!后面带上你的方法名
http://localhost:8087/soneAction!doFirst.action
请求接收参数
属性驱动方式
服务端接收客户端离散数据的方式(也即是单个值进行接收)
在 Action 类中定义与请求参数同名的属性,即,要定义该属性的 set 方法。这样就能够使 Action 自动将请求参数的值赋予同名属性
我也觉得没有什么特殊的啊,不就是正常的接收值吗,
<input type="text" name="name"/> <input type="text" name="age"/>
域驱动方式(也就是将值封装到一个对象中进行接收)
服务器端以封装好的对象方式接收来自客户端的数据方式。将用
户提交的多个数据以封装对象的方式进行整体接收。该方式要求,表单提交时,参数以对象
属性的方式提交。而 Action 中要将同名的对象定义为属性(为其赋予 getter and setter)。这
样请求将会以封装好的对象数据形式提交给 Action。
<input type="text" name="student.name"/> <input type="text" name="student.age"/>
执行过程还是比较复杂的 对象类型数据接收的内部执行过程比较复杂: (1)当将表单请求提交给服务器后,服务器首先会解析出表单元素,并读取其中的一个元 素 name 值,如读取出如下: (2)执行 Action 的 getStudent()方法以获取 Student 对象。判断 Student 对象是否为 null, 若为 null,则服务器会通过反射创建一个 Student 对象。 (3)由于此时的 Student 对象为 null,所以系统会创建一个并调用 setStudent()方法将 student 初始化。 (4)此时的 student 已非空,会将刚才解析出的表单元素,通过调用其 set 方法,这里是 setName()方法,将用户输入的值初始化该属性。 (5)再解析下一个表单元素,并读取其 name 值。 (6)再次执行 Action 的 getStudent()方法以获取 Student 对象。此时的 Student 对象已非空。 (7)将刚才解析出的表单元素,通过调用其 set 方法,这里是 setAge()方法,将用户输入的 值初始化该属性。
集合数据接收
所谓集合数据接收是指,以集合对象方式接收数据。此情况与域驱动接收数据原理是相
同的。注意,集合与数组是不同的
<form name="" action=""> <input type="text" name="name"/> <input type="text" name="age"/> <input type="text" name="student[0].name"/> <input type="text" name="student[0].age"/> <input type="text" name="student[1].name"/> <input type="text" name="student[1].age"/> </form>
public class Student { private List<Student> students; public String execute(){ return "success"; } }
此时用于接收集合数据的属性,不能定义为数组。因为数组长度是固定的,而集合长度
是可扩展的。
姓名1:${students.get(0).name} 年龄1:${students.get(0).age} 姓名2:${students[1].name)} 年龄2:${students[2].age}
类型转换
int 和 Integer
long 和 Long
float 和 Float
double 和 Double
char 和 Character
boolean 和 Boolean
Date:
可以接收 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss 格式字符串
数组:可以将多个同名参数,存放到到数组
集合:可以将数据保存到 List、Map
3 自定义类型转换器
在上面程序中,若输入非 yyyy-MM-dd 格式,如输入 yyyy/MM/dd 格式,在结果页面中
还可以正常看到 yyyy/MM/dd 的日期输出。但查看控制台,却给出了 null 值。其实底层已经
发生了类型转换失败,抛出了类型转换异常 TypeConversionException。若要使系统可以接收
其它格式的日期类型,则需要自定义类型转换器。
查看 DateConverter、NumberConverter 等系统定义好的类型转换器源码,可以看到它们
都是继承自 DefaultTypeConverter 类。所以,我们要定义自己的类型转换器,也要继承自该
类。在使用时,一般需要覆盖其父类的方法 convertValue(),用于完成类型转换。
定义 convertValue 方法时需要注意,其转换一般是定义为双向的。
就本例而言,从浏览器表单请求到服务器端 action 时,是由 String 到 Date 的转换,那
么,第一个参数 value 为 String 类型的请求参数值,第二个参数 toType 为要转换为的 Date
类型。
当类型转换成功,但服务器端其它部分出问题后,需要返回原页面。此时用户填写过的
数据应重新回显。回显则是由服务器端向浏览器端的运行,需要将转换过的 Date 类型重新
转换为 String。那么,此时的 value 则为 Date 类型,而 toType 则为 String。
注意第一个参数 value,若转换方向为从请求到 action,则 value 为字符串数组。因为请
求中是允许携带多个同名参数的,例如下面表单中的兴趣爱好项的 name 属性为 hobby,其
值就有可能为多个值。
<form name="" action=""> <input type="checkbox" name="hobby" value="fitness"/> 健身 <input type="checkbox" name="hobby" value="running"/>跑步 <input type="checkbox" name="hobby" value="swing"/>游泳 </form>
这时的这个同名参数,其实就是数组。Struts2 为了兼顾到这种多个同名参数的情况,
就将从请求到 action 方向转换的 value 指定为了 String[],而非 String。其底层使用的 API 为:
String[] value = request.getParameterValues(…);
注意,对于服务器端向浏览器端的转换,需要使用 Struts2 标签定义的表单才可演示出。
演示时,age 元素填入非数字字符,birthday 填写正确。另外,向<action/>中添加 input 视图,
Action 类需要继承 ActionSupport 类。
只所以要继承自 ActionSupport 类,是因为 ActionSupport 类实现了 Action 接口,而该接
口中定义了 INPUT 字符串常量。一旦发生类型转换异常 TypeConversionException,系统将自
动转向 input 视图
<s:form action="" method=""> <s:textfield name="age" label="年龄"></s:textfield> <s:textfield name="birthday" label="生日"></s:textfield> <s:submit value="提交"></s:submit> </s:form>
public class Student extends ActionSupport { private int age; private Date birthday; public String execute(){ System.out.println(birthday); return "success"; } }
Date 在这里自动导入的是 java.sql 包中的,而我们需要的是 java.util 包中
的 Date。
package com.sturts2.action; import ognl.DefaultTypeConverter; import javax.management.ValueExp; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; /** * Description: 05-sturts2 * Created by lenovo on 2019/4/17 9:17 */ public class MyDateConverter extends DefaultTypeConverter { @Override public Object convertValue(Map context, Object value, Class toType) { SimpleDateFormat format = new SimpleDateFormat("yyyy/MMM/ss"); //由页面到服务端方向,是由String到Date的转换 try { if (toType== Date.class){ System.out.println("从页面到服务端"); String[] params = (String[]) value; return format.parse(params[0]); }else if (toType==String.class){ System.out.println("从服务端到页面"); Date date = (Date) value; return format.format(date); } } catch (ParseException e) { e.printStackTrace(); } return super.convertValue(context, value, toType); } }
定义好类型转换器后,需要注册该转换器,用于通知 Struts2 框架在遇到指定类型变量
时,需调用类型转换器。
根据注册方式的不同及其应用范围的不同,可以将类型转换器分为两类:局部类型转换
器、全局类型转换器。
局部类型转换器
局部类型转换器,仅仅对指定 Action 的指定属性起作用。若注册方式为,在 Action 类
所在的包下放置名称为如下格式的属性文件:ActionClassName-conversion.properties 文件。
其中 ActionClassName 是 Action 类名,-conversion.properties 是固定写法。就本例而言,该
注册文件的名称应为 SomeAction-conversion.properties。
该属性文件的内容遵循如下格式:属性名称=类型转换器的全类名。就本例而言,文件
中的内容为:birthday= com.abc.converters.MyDateConverter。
全局类型转换器
全局类型转换器,会对所有 Action 的指定类型的属性生效。其注册方式为, 在 src 目
录下放置名称为 xwork-conversion.properties 属性文件。该文件的内容格式为:待转换的类
型=类型转换器的全类名。就本例而言,文件中的内容为:
java.util.Date=com.abc.converters.MyDateConverter
注意,对于服务器端向浏览器端的转换,即数据的回显功能,需要使用 Struts2 标签定
义的表单才可演示出。演示时,age 元素填入非数字字符,birthday 填写正确