创建动态web项目,idea可选Struts2,Eclipse可选择dynamic web project的方式
导入jar包
由于我使用的是idea,我先移动lib文件夹到了web-inf下,然后配置了FileSet即可
配置web.xml
首先经过tomcat容器,所以先配置struts的过滤器,拦截全部请求
<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>struts2filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilterfilter-class>
filter>
<filter-mapping>
<filter-name>struts2filter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>
配置struts.xml文件
该文件配置了具体的请求内容,例如简单的实现访问index服务器跳转index.jsp
<struts>
<package name="basicStruts" extends="struts-default">
<action name="index">
<result>index.jspresult>
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" %>
/index
你好struts
启动tomcat并测试访问/index
基本思路
访问/index -> 被web.xml配置的struts拦截器处理 -> 根据struts.xml的配置,访问/index将跳转到index.jsp文件 -> 显示index.jsp
准备一个hello.jsp文件
通过配置struts.xml实现访问路径 /hello 跳转到 hello.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.jspresult>
action>
这里需要说明一下代码
中的name表示访问路径,class表示该路径被访问时会查询的类,method表示查询该类的show方法
中的name表示方法show的返回值为show时调用视图show.jsp
<form action="addProduct">
<input type="text" name="product.name">
<br/>
<input type="submit" value="submit">
form>
public void add(){
return "success";
}
<action name="addProduct" class="vip.javer.action.ProductAction" method="add">
<result name="success">show.jspresult>
action>
在页面上新增加age字段,提交到Action
为product新增加一个age属性,并提供setter和getter
最后在show.jsp上显示页面提交的age数据
Struts的中文问题,由3部分组成
我这里由于idea的jsp模板已被我修改,所以不用配置<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%>
<struts>
<constant name="struts.i18n.encoding" value="UTF-8"/>
...
struts>
在tomcat启动struts web应用的时候,如果出现了struts配置上的错误,你可能只能看到一个 Error FilterStart的提示,而看不到详细的错误原因。
这样就加大了定位和解决问题的难度
这是因为默认配置下,struts把日志输出关闭了
为了把日志输出开启便于调试,需要增加log4j.xml这个配置文件
<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>
<logger name="com.opensymphony">
<level value="DEBUG"/>
logger>
<logger name="org.apache.struts2">
<level value="DEBUG"/>
logger>
<root>
<priority value="INFO"/>
<appender-ref ref="STDOUT"/>
root>
log4j:configuration>
有了log4j.xml日志输出可以帮助调试,但是也会导致struts的启动变慢。
关闭log4j.xml日志输出很简单,直接把log4j.xml命名成其他文件即可,以后要用再把名字改回来,很方便
想要在struts中使用request与response很简单
ServletActionContext.getRequest()
ServletActionContext.getResponse()
在Tomcat的控制台输出 可以看到Struts使用类StrutsRequestWrapper对HttpServletRequest进行了封装
在struts中session有两个
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" %>
show
show.jsp
${product.id} - ${product.name} - ${product.age}
Session: ${session_id} - ${session_name} - ${session_age}
四大域测试: ${requestField} - ${applicationField} - ${sessionField}
测试session
session中的name1: ${name1}
session中的name2: ${name2}
使用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" %>
upload
<%-- --%>
准备对应的上传处理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;
/** * 实际上传方法 * 这里并不是真正的上传,而是打印了一下上传的文件信息 * * 接着就为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.jspresult>
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" %>
uploadSuccess
上传成功
${doc}
${docFileName}
${docContentType}
测试
struts上传文件的大小默认是比较小的只有2M,可以进行设置
<struts>
<constant name="struts.multipart.maxSize" value="104857600"/>
struts>
与jstl标准标签库类似的,struts有专属标签库
form标签用于提交数据
修改hide.jsp \ show.jsp 的表单上传框为
<%@ taglib prefix="s" uri="/struts-tags" %>
会发现自动转换为了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>
与JSTL标准标签库的c:forEach类似的,struts也提供了一个s:iterator用于遍历一个集合中的数据
为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.jspresult>
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" %>
listProduct
id
name
st.index
st.count
st.first
st.last
st.odd
st.even
${p.id}
${p.name}
${st.index}
${st.count}
${st.first}
${st.last}
${st.odd}
${st.even}
遍历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,而不是对象
遍历products成为radio,并选中第二个
在前例checkbox标签的基础上增加s:radio标签
value表示:哪项被选中
name表示:提交到服务端用的名称
list:用于遍历的集合
listValue:显示的radio的名称
listKey:radio的value
遍历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
有部分业务需求需要遍历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);
}
}
展示
id
name
products
${c.id}
${c.name}
${p.id} - ${p.name} - ${p.age}
导入jar包struts2-config-browser-plugin-2.2.3.1.jar
访问config-browser/actionNames
即可
有的时候jsp上不显示action传递过来的数据,有可能的原因是action并没有传递任何数据
这个时候我们就需要工具来查看action到底有没有传递数据
修改办法很简单,直接增加s:debug
学习到目前为止,add,show, list分别需要进行配置
通过通配符匹配可以把这3个配置整合在一个配置中实现
<action name="*Product" class="vip.javer.action.ProductAction" method="{1}">
<result name="show">show.jspresult>
<result name="hide">hide.jspresult>
<result name="success">show.jspresult>
<result name="list">listProduct.jspresult>
action>
这样就能满足多个需求
解释一下xml配置
name="*Product"
表示匹配所有以Product结尾的请求
method="{1}"
表示以*位置的内容作为方法,比如name = "showProduct"
那么就是method = "show"
<result name="show">show.jspresult>
<result name="hide">hide.jspresult>
<result name="success">show.jspresult>
<result name="list">listProduct.jspresult>
表示对应的方法返回值跳转对应页面
拦截器可以简单地看成是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}">
<interceptor-ref name="dateInterptor"/>
<interceptor-ref name="defaultStack"/>
action>
package>
5.测试
struts默认是服务端跳转,想要实现客户端302跳转很简单
在struts.xml中的result标签加入`type="redirect"即可
这个也很简单,首先准备好action的属性,例如name,然后get\set,然后在方法内赋值,再在result
标签的内容区域追加即可,例如
String name;
get/set
public void show(){
name = "233";
}
<result name="show" type="redirect">show.jsp?name=${name}result>
获取参数也很简单,上面的方法会直接展示url,还可以在页面获取
${param.name}
public void validate() {
if (product.getName().length() == 0) {
addFieldError("product.name", "昵称不能为空");
}
}
<result name="input">add.jspresult>
这个input可以说是内置的方法吧,只管用就好了
大概意思就是如果在addProduct提交表单时出现错误返回到add.jsp
去掉validate()方法
新建文件{ActionName}-validation.xml文件,一定要放在ActionName相同包下面
<validators>
<validator type="requiredstring">
<param name="fieldname">product.nameparam>
<message>使用xml方式的提示,昵称不能为空message>
validator>
validators>
重启测试
测试只需要多访问几次,然后在action的无参构造打印this就可以看到了
public ProductAction() {
System.out.println(this);
}
以上的教程都是基于XML进行配置的,除此之外,Struts还能够基于注解进行配置
为了支持注解,需要导包
注释掉struts.xml的内容
在对应的Action类追加三个注解
@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:拦截器引用组。
一般说来,不是所有的注解都会用到,真正用到哪个的时候再来查一下就知道怎么回事了。