第一节 Struts标签与OGNL的配合使用
1.1 iterator遍历标签
- 先提供一个Student.java,name、age、city的getters、setters、有参构造。
- Demo03Action.java
package com.it.web.action;
import java.util.ArrayList;
import java.util.List;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsRequestWrapper;
import com.it.model.Student;
import com.it.model.User;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.util.ValueStack;
public class Demo03Action extends ActionSupport{
//action里提供属性,并提供get方法,这个属性数据才会被存储在值栈
private List<Student> stuList;
public List<Student> getStuList() {
return stuList;
}
public String list(){
//jsp一般从值栈取数据
//暂不使用数据库
stuList = new ArrayList<Student>();
stuList.add(new Student("shu", 18, "广州"));
stuList.add(new Student("zhangsan", 28, "汕头"));
stuList.add(new Student("lisi", 38, "汕尾"));
stuList.add(new Student("wangwu", 48, "潮州"));
stuList.add(new Student("zhaoliu", 58, "合肥"));
stuList.add(new Student("wanger", 8, "南京"));
return SUCCESS;
}
}
- struts.xml
<!-- 开发模式 -->
<constant name="struts.devMode" value="true"></constant>
<package name="p1" extends="struts-default">
<action name="list" class="com.it.web.action.Demo03Action" method="list">
<result>/demo03.jsp</result>
</action>
</package>
- demo03.jap
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Demo03.jsp</title>
</head>
<body>
<table border="1">
<tr>
<td>状态</td>
<td>名字</td>
<td>年龄</td>
<td>城市</td>
</tr>
<!-- 使用struts的遍历标签:s:iterator遍历,如同for循环
1.value:写值栈变量名,是OGNL表达式,先从值栈中找
2.var:如果指定var属性,会把当前遍历的变量名,也就是var的值,存到contextMap
3.status:用于记录一些遍历时的状态或属性
a.boolean isOdd(); 奇数
b.boolean isEven(); 偶数
c.boolean isFirst(); 第一个
d.boolean isLast(); 最后一个
e.int getIndex(); 索引,从0开始
f.int getCount(); 遍历的个数,从1开始
-->
<s:iterator value="stuList" var="stu" status="st">
<tr>
<!--遍历取值的方式:
左边:第一种OGNL表达式:<s:property value="#stu.name"/>表示从contextMap取值
右边:第二种EL表达式:${stu.name},也会从contextMap取值,更为简便,推荐使用
-->
<td>${st.count}</td><!-- EL取遍历的个数、取name、age、city-->
<td><s:property value="#stu.name"/>=${stu.name}</td>
<td><s:property value="#stu.age"/>=${stu.age}</td>
<td><s:property value="#stu.city"/>=${stu.city}</td>
</tr>
</s:iterator>
</table>
<s:debug></s:debug>
</body>
</html>
-
效果
-
一些解释
每遍历一次从值栈中取stuList(所以上面代码中不用加#号)放入contextMap,再从contextMap中取stu(要加#号)点上要的值(key),一直遍历到最后一个,也就是上图
1.2 OGNL投影(了解)
使用过滤条件投影
- 修改demo03.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Demo03.jsp</title>
</head>
<body>
<table border="1">
<tr>
<td>状态</td>
<td>名字</td>
<td>年龄</td>
<td>城市</td>
</tr>
<!--
一、OGNL投影∶是指可以给定过滤条件
OGNL表达试加条件
.?#过滤所有符合条件的集合如: stuList.{?#this.age>30}
.^#过滤第一个符合条件的集合如: stuList.{#this.age>303
.$#过滤最后一个符合余的集合如: stuList.{$#this.age>30}
-->
<!--所以年龄大于等于18的-->
<s:iterator value="stuList.{?#this.age>=18}" var="stu" status="st">
<tr>
<td>${st.count}</td>
<td><s:property value="#stu.name"/> </td>
<td><s:property value="#stu.age"/></td>
<td><s:property value="#stu.city"/></td>
</tr>
</s:iterator>
</table>
<s:debug></s:debug>
</body>
</html>
- 效果
使用指定属性投影
- 修改demo03.jsp
<table border="1">
<tr><td>姓名</td></tr>
<!--
1、只需要name属性
2、遍历时,不用指定var属性,默认的值就放在值栈的栈顶
3、由于.{name}把student的name属性直接放在值栈的栈顶
4、所以遍历取值时,不指定value,默认从值栈的栈顶取值
-->
<s:iterator value="stuList.{name}">
<tr>
<td><s:property/></td>
</tr>
</s:iterator>
</table>
<s:debug></s:debug>
- 效果
- 一些解释
<!--此时这样写会报错-->
<s:property value="#stu.name"/>
这时变量stu的类型为字符串,不是一个对象,再点name是取不到的
这样写就能取到,也可以像上面一样不写value
<table border="1">
<tr><td>姓名</td></tr>
<s:iterator value="stuList.{name}" var="stu">
<tr>
<td><s:property value="#stu"/></td>
</tr>
</s:iterator>
</table>
1.3 回顾Struts2中#,$,%符号的使用(重要)
1. #号
- 取contextMap中key时使用,例如:<s:property value="#name" />
- OGNL中创建Map对象时使用,例如:<s:radio list="#{‘male’:‘男’,‘female’:‘女’}" />
2. $
- 在JSP中使用EL表达式时使用,例如:${name}
- 在xml配置文件中,编写OGNL表达式时使用,例如文件下载时,文件名编码。
struts.xml——>${@java.net.URLEncoder.encode(filename)}
3. %
- 在struts2中,有些标签的value属性取值就是一个OGNL表达式,例如:<s:property value=“OGNL Expression” />
- 还有一部分标签,value属性的取值就是普通字符串,例如:<s:textfield value=“username”/>,如果想把一个普通的字符串强制看成时OGNL,就需要使用%{}把字符串套起来。
- 例如<s:textfield value="%{username}"/>。
- <s:property value="%{OGNL Expression}" />也可以使用,但不会这么用。
- %起的是一个强调的作用
1.4 Struts的其它标签
s:set标签
- 作用:存储变量
- value:存入map中的值,是一个OGNL表达式
- var:存在map中的key
- scope:存储的范围application session request page和action
- 不写scope,默认是action,它是在contextMap中和request中各存一份
- Demo04Action.java
package com.it.web.action;
import java.util.ArrayList;
import java.util.List;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsRequestWrapper;
import com.it.model.Student;
import com.it.model.User;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.util.ValueStack;
public class Demo04Action extends ActionSupport{
private String username;
private String password;
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
System.out.println(username);
System.out.println(password);
return super.execute();
}
}
- struts.xml添加
<action name="demo04" class="com.it.web.action.Demo04Action">
<result>/demo04.jsp</result>
</action>
- demo04.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Demo04.jsp</title>
</head>
<body>
<!-- s:set标签
往作用域存数据
value:值
var:变量名
scope:作用域,范围,可写类型:application,session,request,page和action
不写,就是action
-->
<s:set value="'shu01'" var="username1" scope="application"></s:set>
<s:set value="'shu02'" var="username2" scope="session"></s:set>
<s:set value="'shu03'" var="username3" scope="request"></s:set>
<s:set value="'shu04'" var="username4" scope="action"></s:set>
<!-- 取值 -->
<s:property value="#application.username1"/>
<s:property value="#session.username2"/>
<s:property value="#request.username3"/>
<s:debug></s:debug>
</body>
</html>
- 效果
注意:value="‘shu01’",如果不加单引号则取不到,双引号是当成OGNL表达式来使用去取值,但这里是要用它们本来的字符串,key的值来使用,所以要加单引号
s:action标签
- 作用:在jsp里访问一个action
- demo05.jsp
<!--1. s:action标签
在jsp里面访问一个action
代表把list.action的数据显示在当前页面
-->
--------------------<br>
<s:action name="list" executeResult="true"></s:action>
- 效果
s:if标签、s:elseif标签、s:else标签
- 相当于jstl中的c:choose、c:wher、c:otherwise
<!--存一个级别数据A︰高级讲师 B:中级讲师 C:初级讲师 D:工程师-->
<s:set value="'D'" var="level"/>
<s:if test="#level == 'A'">高级讲师</s:if>
<s:elseif test="#level == 'B'">中级讲师</s:elseif>
<s:elseif test="#level == 'C'">初级讲师</s:elseif>
<s:else>工程师</s:else>
s:url和s:a标签
<!--
2.s:url 【不好用】
声明一个url路径,存在contextMap
url里面只能传一个参数给后台,第一个参数
-->
<s:url action="demo04" var="myurl">
<s:param name="password">123</s:param>
<s:param name="username">shu</s:param>
</s:url>
<a href="<s:property value='#myurl'/>">点击</a>
只获得了第一个参数
- s:a标签
<!-- 3:s:a a标签-->
<s:a action="demo04">
<s:param name="password">123</s:param>
<s:param name="username">shu</s:param>
点击
</s:a>
<s:debug></s:debug>
参数都能获取到,写法也简便
第二节 表单案例
- checkboxlist的使用
- 模型驱动的原理
day03已实现
https://blog.csdn.net/qq_43414199/article/details/107815098.
表单补充:
<s:form>
<!-- 两种写法 -->
<s:select list="#{'gz':'广州','sz':'深圳'}" label="城市" name="city" headerKey="" headerValue="-请选择城市-"></s:select>
<s:select list="#{'':'-请选择城市-','gz':'广州','sz':'深圳'}" label="城市" name="city"></s:select><br>
<s:textarea label="备注" rows="10" cols="50" name="remark"></s:textarea><br>
<s:submit value="提交"></s:submit>
<s:reset value="重置"></s:reset>
</s:form>
<s:debug></s:debug>
模型驱动原理补充:
- struts的ModelDrivenInterceptor 这个拦截器来完成的了模型驱动的工作
- 这个可以从struts-defualt.xml找到这个拦截器
- ModelDrivenInterceptor.java源码
struts主题的设置
- 默认主题的名称是XHTML,都是在struts的默认属性文件default.properties中定义着
更改默认的主题方式
- 第一步:在标签内加theme属性
- 第二步:配置全局的theme
- 效果:
第三节 防止表单重复提交(拦截器)
防止表单重复提交原理图
Servelt的表单重复提交的一种解决方案
- 在登录时,可以通过验证码存在session中来解决
- 登录页面login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/loginServlet" method="post">
用户名:<input type="text" name="username"><br>
密 码:<input type="password" name="password"><br>
验证码:<input type="text" name="code">
<img alt="" src="${pageContext.request.contextPath}/validateCodeServlet"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
- 创建验证码ValidateCodeServlet.java,导入ValidateCode.jar
package com.it.web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.dsna.util.images.ValidateCode;
@WebServlet("/validateCodeServlet")
public class ValidateCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//创建验证码对象
ValidateCode code = new ValidateCode(100,30,4,4);//(宽,高,验证码个数,干扰线条数)
//存储服务端验证码
request.getSession().setAttribute("serverCode", code.getCode());
System.out.println("ServerCode:" + code.getCode());
//响应客户端
code.write(response.getOutputStream());
}
}
- 登录LoginServlet.java
package com.it.web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet{
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//有中文,设置内容编码,防中文乱码
response.setHeader("content-type", "text/html;charset=utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
//获取客户端的验证码
String clientCode = request.getParameter("code");
String serverCode = (String) request.getSession().getAttribute("serverCode");
//equalsIgnoreCase忽略大小写比较
if(clientCode.equalsIgnoreCase(serverCode)){
//验证码一致,表单提交,这里就只是简单的在页面写一句话
response.getWriter().write("第一次表单提交");
//删除session中存储的验证码,防止表单刷新重复提交和恶意提交
request.getSession().removeAttribute("serverCode");
}else{
//不一致,可以给出提示或直接重定向
response.getWriter().write("表单重复提交");
}
System.out.println(username);
System.out.println(password);
}
}
- 效果
输入正确的验证码提交后(用户名密码随便填):
刷新一下页面:
Struts的表单重复提交解决方案
1. 使用重定向
- login.jsp
<form action="${pageContext.request.contextPath}/login.action" method="post">
用户名:<input type="text" name="username"><br>
密 码:<input type="password" name="password"><br>
<input type="submit" value="提交">
</form>
- struts.xml
<action name="login" class="com.it.web.action.UserAction" method="login">
<result type="redirect">/success.jsp</result>
</action>
- UserAction.java
package com.it.web.action;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsRequestWrapper;
import com.it.model.Student;
import com.it.model.User;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.util.ValueStack;
public class UserAction extends ActionSupport{
private String username;
private String password;
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String login(){
System.out.println(username);
System.out.println(password);
return SUCCESS;
}
}
- 效果
点击提交后:
无论刷新多少次:控制台都只打印登录成功那一次的username和password
2. 使用<s:token/>生成令牌配合token拦截器
此种解决方式不太符合逻辑,它是产生了错误之后再告知用户,应该一直停留在当前界面
- login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login.action" method="post">
用户名:<input type="text" name="username"><br>
密 码:<input type="password" name="password"><br>
<!-- 添加一个token令牌 -->
<s:token></s:token>
<input type="submit" value="提交">
</form>
</body>
</html>
查看源码:struts生成了一个隐藏的token令牌
- UserAction.java
package com.it.web.action;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsRequestWrapper;
import com.it.model.Student;
import com.it.model.User;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.util.ValueStack;
public class UserAction extends ActionSupport{
private String username;
private String password;
private String token;
public void setUsername(String username) {
this.username = username;
}
public String getToken() {
return token;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void setToken(String token) {
this.token = token;
}
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
System.out.println(username);
System.out.println(password);
return SUCCESS;
}
}
- struts.xml
<action name="login" class="com.it.web.action.UserAction">
<!-- 进行token拦截 -->
<!-- token不属于defaultStack,所以要添加defaulStack默认拦截器,不然struts默认的拦截功能失效 -->
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="token"/>
<result >/success.jsp</result>
<!-- 配置表单重复提交时的结果页面 -->
<result name="invalid.token">/invalidtoken.jsp</result>
</action>
- 效果
点击提交后:登录成功
但是,再次刷新会出现表单重复提交
我们想要的是它停留在登录成功的页面,所有要使用tokensession拦截器
使用<s:token/>生成令牌配合tokensession拦截器
- 修改struts.xml
<action name="login" class="com.it.web.action.UserAction">
<!-- 进行token拦截 -->
<!-- token不属于defaultStack,所以要添加defaulStack默认拦截器,不然struts默认的拦截功能失效 -->
<interceptor-ref name="defaultStack"/>
<!-- tokenSession只会处理第一次,当重复提交时不会处理 -->
<interceptor-ref name="tokenSession"/>
<result >/success.jsp</result>
<!-- 配置表单重复提交时的结果页面 -->
<result name="invalid.token">/invalidtoken.jsp</result>
</action>
- 效果
再刷新,也不会到表单重复提交页面,tokenSession只处理一次。