仓库地址: https://github.com/biuaxia/struts
Struts
基础
hello struts
-
创建动态web项目,idea可选Struts2,Eclipse可选择dynamic web project的方式
-
导入jar包
由于我使用的是idea,我先移动lib文件夹到了web-inf下,然后配置了FileSet即可
-
配置web.xml
首先经过tomcat容器,所以先配置struts的过滤器,拦截全部请求
<?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.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
-
配置struts.xml文件
该文件配置了具体的请求内容,例如简单的实现访问index服务器跳转index.jsp
<?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> <!--其效果是当访问index路径的时候,服务端跳转到index.jsp--> <package name="basicStruts" extends="struts-default"> <action name="index"> <result>index.jsp</result> </action> </package> </struts>
-
设置index.jsp内容
这里随便写点html内容即可
<%-- Created by IntelliJ IDEA. User: Administrator Date: 2019/2/10 Time: 21:50 在这里添加文件的描述 --%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> <!DOCTYPE html> <html> <head> <title>/index</title> </head> <body> 你好struts </body> </html>
-
启动tomcat并测试访问/index
-
基本思路
访问/index -> 被web.xml配置的struts拦截器处理 -> 根据struts.xml的配置,访问/index将跳转到index.jsp文件 -> 显示index.jsp
练习 访问hello跳转hello.jsp
准备一个hello.jsp文件
通过配置struts.xml实现访问路径 /hello 跳转到 hello.jsp
显示数据到JSP
把Model的数据显示在视图JSP上
-
准备产品Product实体类
package vip.javer.bean; import java.io.Serializable; /** * @author Administrator */ public class Product implements Serializable { int id; String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
准备产品ProductAction
package vip.javer.action; import vip.javer.bean.Product; /** * @author Administrator */ public class ProductAction { private Product product; public String show() { product = new Product(); product.setName("OnePlus A5000"); return "show"; } public Product getProduct() { return product; } public void setProduct(Product product) { this.product = product; } }
-
修改struts.xml配置
<action name="showProduct" class="vip.javer.action.ProductAction" method="show"> <result name="show">show.jsp</result> </action>
这里需要说明一下代码
<action name="showProduct" class="vip.javer.action.ProductAction" method="show">
中的name表示访问路径,class表示该路径被访问时会查询的类,method表示查询该类的show方法
<result name="show">show.jsp</result>
中的name表示方法show的返回值为show时调用视图show.jsp
练习 访问/showTime跳转showTime.jsp并且显示当前时间
提交数据到action
- show.jsp添加表单控件,方便提交
<form action="addProduct">
<input type="text" name="product.name">
<br/>
<input type="submit" value="submit">
</form>
- 在ProductAction类添加add方法
public void add(){
return "success";
}
- 修改struts.xml配置
<action name="addProduct" class="vip.javer.action.ProductAction" method="add">
<result name="success">show.jsp</result>
</action>
- 测试
流程
- 携带输入框参数名为product.name的值访问addProduct
- web.xml的struts过滤器拦截
- 根据配置文件struts.xml, 会执行ProductAction的add方法
- 在add方法执行之前,Struts生成一个新的product对象,并把页面传递过来的name设置在该对象上,接着把该对象通过setProduct()方法,注入ProductAction
- 服务端跳转到show.jsp
- 在show.jsp中,访问ProductAction.getProduct() 获取注入的product,并显示其名称
练习
在页面上新增加age字段,提交到Action
为product新增加一个age属性,并提供setter和getter
最后在show.jsp上显示页面提交的age数据
解决struts中文问题
Struts的中文问题,由3部分组成
- jsp提交数据的时候,必须是UTF-8编码的
- struts拿到数据后进行UTF-8解码
- 服务端跳转到jsp进行显示的时候,要指定浏览器使用UTF-8进行显示
UTF-8可以换成GBK或者GB2312,但是必须统一,不能混用
我这里由于idea的jsp模板已被我修改,所以不用配置<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%>
- 修改前端表单方法为post
- 指定struts.xml的解码方式为utf-f
<struts>
<!--指定解码方式为UTF-8-->
<constant name="struts.i18n.encoding" value="UTF-8"/>
...
</struts>
- 测试
STRUTS 启动失败,出现错误ERROR FILTERSTART 应该如何调试
在tomcat启动struts web应用的时候,如果出现了struts配置上的错误,你可能只能看到一个 Error FilterStart的提示,而看不到详细的错误原因。
这样就加大了定位和解决问题的难度
这是因为默认配置下,struts把日志输出关闭了
为了把日志输出开启便于调试,需要增加log4j.xml这个配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration PUBLIC "-//log4j/log4j Configuration//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p %c.%M:%L - %m%n"/>
</layout>
</appender>
<!-- specify the logging level for loggers from other libraries -->
<logger name="com.opensymphony">
<level value="DEBUG"/>
</logger>
<logger name="org.apache.struts2">
<level value="DEBUG"/>
</logger>
<!-- for all other loggers log only debug and above log messages -->
<root>
<priority value="INFO"/>
<appender-ref ref="STDOUT"/>
</root>
</log4j:configuration>
关闭日志
有了log4j.xml日志输出可以帮助调试,但是也会导致struts的启动变慢。
关闭log4j.xml日志输出很简单,直接把log4j.xml命名成其他文件即可,以后要用再把名字改回来,很方便
request与response
想要在struts中使用request与response很简单
ServletActionContext.getRequest()
ServletActionContext.getResponse()
在Tomcat的控制台输出 可以看到Struts使用类StrutsRequestWrapper对HttpServletRequest进行了封装
session
在struts中session有两个
- HttpSession,通过之前的request对象获取得到session对象
- Session,该对象为一个map集合,其中的值与httpsession同步
HttpSession session = ServletActionContext.getRequest().getSession();
Map<String, Object> sessionMap = ActionContext.getContext().getSession();
我们可以在add方法中把添加的把内容存入到session中看看效果
public String show() {
/*测试request与response对象*/
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
System.out.println(request);
System.out.println(response);
/*测试session对象*/
HttpSession session = ServletActionContext.getRequest().getSession();
Map<String, Object> sessionMap = ActionContext.getContext().getSession();
session.setAttribute("name1", "name1");
sessionMap.put("name2", "name2");
/*四大域存放测试: application,request,session,pageContext*/
ActionContext requestMap = ActionContext.getContext();
Map<String, Object> application = requestMap.getApplication();
Map<String, Object> session1 = requestMap.getSession();
requestMap.put("requestField", "request域内容");
application.put("applicationField", "application域");
session1.put("sessionField", "session域");
/*正常代码逻辑*/
product = new Product();
product.setName("OnePlus A5000");
String result;
int i = (int) (Math.random() * 50);
product.setId(i);
if (i % 2 == 0) {
result = "show";
} else {
result = "hide";
}
return result;
}
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2019/2/10
Time: 22:28
在这里添加文件的描述
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false" %>
<!DOCTYPE html>
<html>
<head>
<title>show</title>
</head>
<body>
show.jsp
<br>
${product.id} - ${product.name} - ${product.age}
<br>
Session: ${session_id} - ${session_name} - ${session_age}
<br>
四大域测试: ${requestField} - ${applicationField} - ${sessionField}
<br>
<form action="addProduct" method="post">
<label for="product.name">昵称:</label><input id="product.name" type="text" name="product.name" placeholder="请输入name">
<br/>
<label for="product.age">年龄:</label><input id="product.age" type="text" name="product.age" placeholder="请输入年龄">
<br/>
<input type="submit" value="submit">
</form>
<br>
<h3>测试session</h3>
session中的name1: ${name1}
<br>
session中的name2: ${name2}
</body>
</html>
上传文件
使用struts框架上传文件
-
准备upload.jsp
<%@ taglib prefix="s" uri="/struts-tags" %> <%-- Created by IntelliJ IDEA. User: Administrator Date: 2019/2/12 Time: 8:18 在这里添加文件的描述 --%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> <!DOCTYPE html> <html> <head> <title>upload</title> </head> <body> <%--<s:file name="doc" label="upload"/>--%> <form action="upload" method="post" enctype="multipart/form-data"> 上传文件 : <input type="file" name="doc"/> <br> <input type="submit" value="上传"> </form> </body> </html>
-
准备对应的上传处理action类
package vip.javer.action; import java.io.File; /** * 在upload.jsp中file字段对应的name是"doc" * 所以在action中,必须准备3个属性,分别是 * File doc; * String docFileName; * String docContentType; * 属性名字不能使用其他的,必须基于“doc" * 然后为这3个属性提供getter setter * * @author Administrator */ public class UploadAction { File doc; String docFileName; String docContentType; /** * 实际上传方法 * 这里并不是真正的上传,而是打印了一下上传的文件信息 * <p> * 接着就为upload路径配置UploadAction,并返回success.jsp * * @return */ public String upload() { System.out.println(doc); System.out.println(docFileName); System.out.println(docContentType); return "success"; } public File getDoc() { return doc; } public void setDoc(File doc) { this.doc = doc; } public String getDocFileName() { return docFileName; } public void setDocFileName(String docFileName) { this.docFileName = docFileName; } public String getDocContentType() { return docContentType; } public void setDocContentType(String docContentType) { this.docContentType = docContentType; } }
-
配置struts.xml
<struts> <action name="upload" class="vip.javer.action.UploadAction" method="upload"> <result name="success">uploadSuccess.jsp</result> </action> </struts>
-
准备uploadSuccess.jsp
<%-- Created by IntelliJ IDEA. User: Administrator Date: 2019/2/12 Time: 8:23 在这里添加文件的描述 --%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> <!DOCTYPE html> <html> <head> <title>uploadSuccess</title> </head> <body> <h1>上传成功</h1> ${doc} <br> ${docFileName} <br> ${docContentType} </body> </html>
-
测试
上传文件最大值
struts上传文件的大小默认是比较小的只有2M,可以进行设置
<struts>
<!--设置上传文件最大值100M-->
<constant name="struts.multipart.maxSize" value="104857600"/>
</struts>
标签
form标签
与jstl标准标签库类似的,struts有专属标签库
form标签用于提交数据
-
修改hide.jsp \ show.jsp 的表单上传框为
<%@ taglib prefix="s" uri="/struts-tags" %> <s:form action="addProduct"> <s:textfield name="product.name" label="product name" /> <s:submit value="Submit" /> </s:form>
会发现自动转换为了html的表单代码
<form id="addProduct" name="addProduct" action="/struts_war_exploded/addProduct.action" method="post">
<table class="wwFormTable">
<tr>
<td class="tdLabel"><label for="addProduct_product_name" class="label">product name:</label></td>
<td
><input type="text" name="product.name" value="OnePlus A5000" id="addProduct_product_name"/></td>
</tr>
<tr>
<td colspan="2"><div align="right"><input type="submit" id="addProduct_0" value="Submit"/>
</div></td>
</tr>
</table></form>
iterator标签
与JSTL标准标签库的c:forEach类似的,struts也提供了一个s:iterator用于遍历一个集合中的数据
为ProductAction增加list方法
-
为ProductAction增加一个products属性,类型是List,并提供getter setter
private List<Product> products; public List<Product> getProducts() { return products; } public void setProducts(List<Product> products) { this.products = products; }
-
为ProductAction增加一个list()方法,为products添加3个product对象,并返回“list"
public String list() { products = new ArrayList<>(); for (int i = 0; i < 3; i++) { Product p = new Product(); p.setId(i); p.setName("商品第" + i + "号"); p.setAge(i * 10); products.add(p); } return "list"; }
-
struts.xml配置
<action name="listProduct" class="vip.javer.action.ProductAction" method="list"> <result name="list">listProduct.jsp</result> </action>
-
listProduct.jsp
<%@ taglib prefix="s" uri="/struts-tags" %>
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2019/2/12
Time: 8:49
--------------------------
使用s:iterator标签进行遍历
value 表示集合
var 表示遍历出来的元素
st 表示遍历出来的元素状态
st.index 当前行号 基0
st.count 当前行号 基1
st.first 是否是第一个元素
st.last 是否是最后一个元素
st.odd 是否是奇数
st.even 是否是偶数
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false" %>
<!DOCTYPE html>
<html>
<head>
<title>listProduct</title>
<style>
table {
border-collapse: collapse;
}
td {
border: 1px solid gray;
}
</style>
</head>
<body>
<table align="center">
<tr>
<th>id</th>
<th>name</th>
<th>st.index</th>
<th>st.count</th>
<th>st.first</th>
<th>st.last</th>
<th>st.odd</th>
<th>st.even</th>
</tr>
<s:iterator value="products" var="p" status="st">
<tr>
<td>${p.id}</td>
<td>${p.name}</td>
<td>${st.index}</td>
<td>${st.count}</td>
<td>${st.first}</td>
<td>${st.last}</td>
<td>${st.odd}</td>
<td>${st.even}</td>
</tr>
</s:iterator>
</table>
</body>
</html>
- 测试
check标签
遍历3个product成为checkbox
并且第2个和第3个是默认选中的
private List<Integer> selectedProducts;
public String list() {
selectedProducts = new ArrayList();
for (int i = 0; i < 3; i++) {
if ((i + 1) == 2 || (i + 1) == 3) {
selectedProducts.add(i);
}
products.add(p);
}
}
除了前例中准备的products
再新增一个属性ListselectedProducts,用于存放哪些产品被选中了
注意: ListselectedProducts 里放的是id,而不是对象
<s:checkboxlist value="selectedProducts" name="product.id" list="products" listValue="name" listKey="id"/>
radio标签
遍历products成为radio,并选中第二个
在前例checkbox标签的基础上增加s:radio标签
value表示:哪项被选中
name表示:提交到服务端用的名称
list:用于遍历的集合
listValue:显示的radio的名称
listKey:radio的value
<s:radio value="1" name="product.id" list="products" listValue="name" listKey="id"/>
select 标签
遍历products成为select标签
默认选中第2个和第3个
使用s:select标签
name表示:提交到服务端用的名称
list:用于遍历的集合
listKey:每个option的value
listValue:显示的名称
multiple:true表示可以选中多行
size="3"表示默认显示3行
value表示:哪些被选中
注: 可以增加一个属性 theme=“simple” 使得最后生成的最简单的风格的html,否则就会有一些奇奇怪怪的tr td
或者直接在struts.xml中加一句 Servlet下载文件
注: 如果要增加class,需要使用属性: cssClass
<s:select label="products"
name="product.id"
list="products"
listKey="id"
listValue="name"
multiple="true"
size="3"
value="selectedProducts"
/>
多重迭代
有部分业务需求需要遍历list中的list
比如当前页面需要显示多个category,每个分类下又对应多个product
-
Category除了有id和name属性外,还有List属性 表示category和product是一对多关系
package vip.javer.bean; import java.util.List; /** * @author Administrator */ public class Category { private int id; private String name; private List<Product> products; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Product> getProducts() { return products; } public void setProducts(List<Product> products) { this.products = products; } }
-
在ProductAction -> list()中准备数据 两个category,并且每个category对应3个product
private List<Category> categories; public List<Category> getCategories() { return categories; } public void setCategories(List<Category> categories) { this.categories = categories; } public String list() { products = new ArrayList<>(); selectedProducts = new ArrayList(); categories = new ArrayList<>(); for (int i = 0; i < 2; i++) { Category category = new Category(); category.setId(i); category.setName("分类" + i); List<Product> products = new ArrayList<>(); for (int j = 0; j < 3; j++) { Product p = new Product(); p.setId(i); p.setName("商品第" + (i + 1) + "号"); p.setAge(i * 10); products.add(p); } category.setProducts(products); categories.add(category); } }
-
展示
<table align="center" border="1" cellspacing="0">
<tr>
<th>id</th>
<th>name</th>
<th>products</th>
</tr>
<s:iterator value="categories" var="c">
<tr>
<td>${c.id}</td>
<td>${c.name}</td>
<td>
<s:iterator value="#c.products" var="p">
${p.id} - ${p.name} - ${p.age}<br/>
</s:iterator>
</td>
</tr>
</s:iterator>
</table>
<s:debug/>
调试
开启调试
导入jar包struts2-config-browser-plugin-2.2.3.1.jar
访问config-browser/actionNames
即可
查看action到底有没有传递数据
有的时候jsp上不显示action传递过来的数据,有可能的原因是action并没有传递任何数据
这个时候我们就需要工具来查看action到底有没有传递数据
修改办法很简单,直接增加s:debug
<s:debug/>
通配符匹配链接
学习到目前为止,add,show, list分别需要进行配置
通过通配符匹配可以把这3个配置整合在一个配置中实现
<action name="*Product" class="vip.javer.action.ProductAction" method="{1}">
<result name="show">show.jsp</result>
<result name="hide">hide.jsp</result>
<result name="success">show.jsp</result>
<result name="list">listProduct.jsp</result>
</action>
这样就能满足多个需求
解释一下xml配置
name="*Product"
表示匹配所有以Product结尾的请求
method="{1}"
表示以*位置的内容作为方法,比如name = "showProduct"
那么就是method = "show"
<result name="show">show.jsp</result>
<result name="hide">hide.jsp</result>
<result name="success">show.jsp</result>
<result name="list">listProduct.jsp</result>
表示对应的方法返回值跳转对应页面
拦截器
拦截器可以简单地看成是Struts中的"filter"
拦截器可以拦截指定的Action,并且对Action进行相应的操作
在本例里,拦截了ProductAction,并且注入了当前时间
-
在ProductAction添加date属性
-
在显示界面追加${date}
-
创建拦截器
package vip.javer.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; import vip.javer.action.ProductAction; import java.util.Date; /** * 时间拦截器 * * @author Administrator */ public class DateInterceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation actionInvocation) throws Exception { ProductAction productAction = (ProductAction) actionInvocation.getAction(); productAction.setDate(new Date()); return actionInvocation.invoke(); } }
-
配置struts.xml
<package name="basicStruts" extends="struts-default"> <!--声明时间拦截器--> <interceptors> <interceptor name="dateInterptor" class="vip.javer.interceptor.DateInterceptor"/> </interceptors> <action name="*Product" class="vip.javer.action.ProductAction" method="{1}"> <!--对ProductAction使用拦截器dateInterceptor --> <interceptor-ref name="dateInterptor"/> <!--一旦使用了自定义的拦截器,本来配置在action上的默认的拦截器就失效了。 所以需要再加上defaultStack--> <interceptor-ref name="defaultStack"/> </action> </package>
5.测试
客户端跳转
struts默认是服务端跳转,想要实现客户端302跳转很简单
在struts.xml中的result标签加入`type="redirect"即可
客户端跳转带参数
这个也很简单,首先准备好action的属性,例如name,然后get\set,然后在方法内赋值,再在result
标签的内容区域追加即可,例如<result name="success" type="redirect">index.jsp?name=${name}</result>
String name;
get/set
public void show(){
name = "233";
}
<result name="show" type="redirect">show.jsp?name=${name}</result>
获取参数也很简单,上面的方法会直接展示url,还可以在页面获取
${param.name}
表单验证
- 在ProductAction中添加validate方法
public void validate() {
if (product.getName().length() == 0) {
addFieldError("product.name", "昵称不能为空");
}
}
- 配置struts.xml
比如我这里是表单的验证,那么就需要在addProduct的action下配置result
<result name="input">add.jsp</result>
这个input可以说是内置的方法吧,只管用就好了
大概意思就是如果在addProduct提交表单时出现错误返回到add.jsp
- 显示错误信息
<s:head/>
xml实现表单验证
-
去掉validate()方法
-
新建文件{ActionName}-validation.xml文件,一定要放在ActionName相同包下面
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd"> <validators> <validator type="requiredstring"> <param name="fieldname">product.name</param> <message>使用xml方式的提示,昵称不能为空</message> </validator> </validators>
-
重启测试
Action是多实例的
测试只需要多访问几次,然后在action的无参构造打印this就可以看到了
public ProductAction() {
System.out.println(this);
}
注解
以上的教程都是基于XML进行配置的,除此之外,Struts还能够基于注解进行配置
-
为了支持注解,需要导包
- 为了使struts支持注解,需要用到struts2-convention-plugin-x.x.x.jar 这个jar包,在前面的教程中是没有使用的,所以这里需要从右侧下载
- 下载好了之后,放在WEB-INF/lib 下
- 不仅如此,还要在项目导入jar,以使得eclipse能够编译通过
-
注释掉struts.xml的内容
-
在对应的Action类追加三个注解
- @Namespace("/")
- @ParentPackage(“struts-default”)
@Results({ @Result(name = "show", location = "/showTime.jsp") })
-
在对应的方法追加@Action(“showTime”)
其他常用注解
Namespace:指定命名空间。
ParentPackage:指定父包。
Result:提供了Action结果的映射。(一个结果的映射)
Results:“Result”注解列表
ResultPath:指定结果页面的基路径。
Action:指定Action的访问URL。
Actions:“Action”注解列表。
ExceptionMapping:指定异常映射。(映射一个声明异常)
ExceptionMappings:一级声明异常的数组。
InterceptorRef:拦截器引用。
InterceptorRefs:拦截器引用组。
一般说来,不是所有的注解都会用到,真正用到哪个的时候再来查一下就知道怎么回事了。