文章目录
- Struts2 的核心开发包
- Struts2 配置文件
- Struts2 域对象
- Struts2 编程流程
- Action 组件
- Result 组件
- ValueStack 对象
- struts2 标签/ognl表达式/el表达式
- Struts2 的 MVC 结构图
- Struts2 的原理图
- 表单的请求资源路径写法
Struts2 的核心开发包
struts2-core.jar
:Struts2 的控制器就在里面
xwork-core.jar
:WebWork 的内核,Struts2 就是基于 WebWork 升级而来的
ognl.jar
:使用 Struts2 的标签和 ognl 表达式来编写显示逻辑,输出模型层的数据
freemarker.jar
:模板框架开发包,相当于 JSP,同样可以输出 HTML 静态页面,属于视图技术,实际中很少使用
Struts2 配置文件
struts-default.xml
定义了 struts2 在底层运行的时候所需要使用到的组件。
struts.xml
我们自定义的 xml 文件,名称可以随便起。这个 xml 文件用来声明、定义我们所写的 action 组件和哪个请求名对应。
使用 Eclipse 工具开发,那么 struts.xml 放在 src 目录下,如果使用 IDEA 工具开发,那么 struts.xml 文件放在 resources 目录下。编译后该文件位于 WEB-INF/classes/
目录下。
这个配置文件里面的标记名称以及属性都是约定好,我们必须按照这样的规则去配置。
必须在 package 元素内声明 action 组件。
package 用来定义和管理 action、result、interceptor 等组件。
package 继承父包 struts-default,在父包定义好的组件,子包都继承过来了。
package 的 namespace 属性是用来限定 action 的请求名,如果有两个 action 中的请求名是一样的,那么通过 namespace 就可以区别开来。
如果有多个 package 元素,那么 package 的 name 属性值必须唯一。
在一个 package 标签内有多个 action,那么 action 的 name 属性值必须唯一。
action 元素的 name 属性定义请求名,class 属性用来指定 action 组件的全路径名,method 属性指定调用action 组件的方法名
result 元素用来定义响应视图,name 属性与 action 组件中的业务方法的返回值对应,type 属性用来指定响应类型,默认值 dispatcher,表示转发。
控制器调 action 组件的业务方法后,得到 String,然后控制器通过该 String 到 struts.xml 找到对应的 result,那么控制器就会通过转发的机制调 result 中定义的响应组件。
如果访问的 web 应用是 Struts 2 架构的,那么请求资源路径末尾的字符串会被服务器解释成 Action 的名称,控制器会根据这个 Action 名称(请求名称)到 struts.xml 文件中找到对应的 Action 组件。
例如:https://www.dpqyw.com/javaex/projectlist
,会把 projectlist
解释成 Action
组件的名称。
Struts2 域对象
Servlet 中常使用到的 request、session、application 对象,在 struts2 中被重新封装过,变成 Map 类型的。但是在 jsp 页面中的 el 表达式仍旧是访问原始的 request、session、application 对象。
当然在 struts2 中的 Action 组件中也还是可以获取到原始的 request、session、application 对象的。
Action 组件如何使用 request/session/application 对象?
第一种方式:利用ActionContext工具类来获取
第二种方式:利用Aware接口来获取,只有Action组件去实现Aware接口,框架底层才会注入request/session/application对象到Action
组件的属性中,其它类是不可以这样做的。
总结:在 Servlet 组件中获取到 reques 对象、session 对象、application 对象和 struts2 的 Action 组件中所获取到的是不一样的,默认情况下 struts2 中获取到的是封装成 Map 类型的对象,当然通过某些 API 或者 Action 类去实现特定的 Aware 接口,也是可以获取到 Http 类型的对象的。
Struts2 编程流程
首先要导入 struts2 的开发包(5个核心包),我们不需要写控制器,以前我们会写一个 servlet 类作为控制器,一般命名为
ActionServlet,然后在service方法中写控制逻辑(if…else语句),现在 struts2 框架已经封装好一切,它提供了一个
由filter充当的控制器,所以我们就不要写了,就连控制器的控制逻辑也不要写了,只要在 web.xml 配置好控制器就可以了。
配置的步骤和以前配置 servlet、filter 一样,不过 struts2 提供的控制器的类名比较长,可以在 struts2-core-2.1.8.jar 中找到。
url-pattern 的写法是 /*
,表示处理所有的请求,所有的请求都要先经由该控制器处理。
然后给控制器配一个 struts.xml 文件,这个文件的作用就是让控制器根据用户的请求名找到对应的 Action 组件,然后去处理请求。
其实这个 struts2.xml 文件就是封装了原来 ActionServlet 组建中的控制逻辑而已,控制器的某个约定的方法会去解析这个 struts2.xml文件,从而得知用户请求的是哪个 Action 组件,这个用户的请求名不是 url-pattern。
然后开始写 Action 组件了,就是一个普通的 Java 类而已,不过要注意 Action 组件中的属性的作用,如果用户的请求有参数,struts2 会先把参数值赋给 Action 组件的属性上,其实是通过内部的一个拦截器来实现的。
接着控制器会去调用 Action 组件的某个方法,该方法可以在 struts.xml 文件中进行配置指定,该方法一般是去调 M 层的组件,获得数据后进行相应的计算,最后将数据赋给自己的属性上,并返回一个字符串给控制器,控制器再根据获得的字符串去解析 struts.xml,从而去调用相应的 V 层组件,由 result 标签配置。
Action 组件
通常情况下,当用户的请求到达 Web 服务器时,服务器会根据请求路径的应用名(即应用的虚拟目录)找到对应的 Web 应用,再解析存放在该应用 WEB-INF 目录下的 web.xml 文件,找到对应的资源组件处理请求。找到后容器会实例化该组件,再去调用这个对象约定的方法。
而对 Struts2 架构的 Web 应用来讲,就是找到 Struts2 框架内置的控制器,那么这个控制器的约定方法就会被调用,这个方法其实就是去解析我们自己写的 struts.xml 文件,找到处理请求的 Action 组件,当控制器找到了对应的组件后,就会创建一个 Action 对象。
在创建 Action 对象之前会先创建一个 ValueStack 对象,ValueStack 对象会存放在 request 对象中。接着创建 Action 对象,再将 Action 对象压入 ValueStack 对象中的 root 栈顶,同时把 pageContext 对象、request 对象、session 对象、application 对象的引用地址存放在 ValueStack 对象中的 context 对象里。
当请求处理完毕时 ValueStack 对象和 Action 对象会随着 request 对象的销毁而销毁。
每次请求都会开启一个线程,每次请求创建一个新的 Action 对象,所以多个请求就开启多个线程,多个请求就会创建多个 Action 对象来处理。因此不会存在线程安全的问题。
如果 struts.xml 文件中的 <action/>
标签的 class 属性没有指定 Action 组件,那么控制器会调 Struts2 内置的默认 Action 组件,其中的execute 方法返回的就是字符串 success。
Action 组件的核心功能:
Action 的功能就是获取前端页面传入的参数数据,再调用 DAO 有关的方法,DAO完成任务后,Action 再告诉控制器任务完成的结果(返回success 或者 fail),控制器根据任务完成的结果再调度相应的 Result 组件(视图层组件)返回结果给客户端。
例如,新增项目的 Action,这个 Action 的功能就是获取前端页面传入的新项目数据,再调用 DAO 的新增项目方法,DAO 完成项目新增后,Action 再告诉控制器结果(返回 success 或者 fail),控制器再调度相应的 Result 组件(视图层组件)返回结果给客户端。
使用通配符配置 Action
一个Action类处理多个请求,是指在类里面定义多个业务方法,不同的请求调用不同的方法;而在 struts2 框架中,其实多个请求会创建多个Action 对象,所以实质上是通过不同的 Action 对象来处理并发请求的。
<action name="opt_*" method="{1}" class="com.tarena.action.OptAction">
<result>/ok.jsp</result>
</action>
上述的配置中中,name属性中使用了一个通配符 *
,而 method 属性值中的 {1}
表示引用第 1 个通配符所代表的字符串。
用户的请求 localhost:8080/struts03/opt_add.action
或者 localhost:8080/struts03/opt_update.action
对应的 Action 组件都是com.tarena.action.OptAction
,不过当请求为 localhost:8080/struts03/opt_add.action
时,name="opt_*"
的 *
代表 add
,那么 method="{1}"
获取的就是 add
,所以调的是 com.tarena.action.OptAction
的 add
方法。当请求为 localhost:8080/struts03/opt_update.action
时,name="opt_*"
的 *
代表 update
,那么 method="{1}"
获取的就是 update
,所以调的是 com.tarena.action.OptAction
的 update
方法。
<action name="opt_*_*" method="{2}" class="com.tarena.action.OptAction">
<result>/ok.jsp</result>
</action>
上述的配置中,当用户的请求为 localhost:8080/struts03/opt_a_add.action
,那么 method="{2}"
获取的就是第 2 个通配符所代表的字符串即 add。
使用通配符的好处就是避免在 struts.xml 中写多个 <action/>
标签。
在 Result 组件中也可以引用通配符的值,如下所示:
<action name="opt_*_*" method="{1}"
class="com.tarena.action{1}.OptAction"
<result>/{2}ok.jsp</result>
</action>
"***"
不建议这样写,星号之间要有字符隔开较好!!!
Action 中如何访问 Session
通过 ActionContext 对象访问 Session 对象(不推荐)
ActionContext act = ActionContext.getContext();
Map<String, Object> session = act.getSession();
// 把数据存放在session中
session.put("user", user);
session 将存放在 ValueStack 的 context 中。
不推荐使用 ActionContext 访问 Session 的方式,因为这种方式的“侵入性”较强。
ActionContext 是 Struts2 的 API,如果使用其他框架代替目前的 Struts2 框架,而我们实际项目中的 Action 的数量非常庞大,每个类都修改会很费劲。
通过实现 SessionAware 接口访问 session
写一个 BaseAction 去实现接口 SessionAware,然后我们写的业务类的 Action 去继承 BaseAction,当然业务类的 Action 也可以直接去实现 接口 SessionAware,但是每个业务类 Action 都要去写实现接口抽象方法的代码,而且完全是重复的代码,不仅麻烦而且极为不合理了。
实现接口 SessionAware 的 Action,控制器实例化 Action 后,会调用 Action 对象的 setSession 方法,把 session 传入给 Action 对象。
package priv.lwx.struts2.action;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
/**
* description
*
* @author liaowenxiong
* @date 2022/2/14 21:22
*/
public class BaseAction implements SessionAware {
// 为了可以被子类继承使用,所以访问控制符可以默认或者protected,不建议public。
// private的变量不会被子类继承,不过子类对象中含有父类私有变量数据,无法直接访问。
Map<String, Object> session;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
}
LoginAction 继承 BaseAction:
package priv.lwx.struts2.action;
import priv.lwx.struts2.action.dao.UserDAO;
import priv.lwx.struts2.action.entity.User;
/**
* description
*
* @author liaowenxiong
* @date 2022/2/14 11:54
*/
public class LoginAction extends BaseAction {
private User user;
private UserDAO userDAO = new UserDAO();
public String login() {
System.out.println(user.getUserName());
System.out.println(user.getPassword());
User user = userDAO.login(this.user.getUserName(), this.user.getPassword());
if (user != null) {
// 登录成功
// 继承自父类的变量session,LoginAction实例化后,控制器会调用setSession(Map session)方法,
// 将session对象赋值给变量session
session.put("user", user);
return "success";
}
// 登录失败
return "fail";
}
public String form() {
return "success";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
LoginAction 实例化后,继承而来的 setSession() 会被调用,将传入 session 对象会存入到变量 session 中。
按照这样的机制,我们可以让所有的 Action(如 LoginAction)继承实现了 SessionAware 接口的 BaseAction,当需要更换 Struts2 框架为其他框架时,只需要修改 BaseAction 即可(另外的框架只要提供一个类似 SessionAware 接口的接口,由 BaseAction 继承)。
Action 如何访问 request 对象
BaseAction 去实现接口 ServletRequestAware,那么继承自 BaseAction 的业务类 Action 就可以直接使用 request 对象了。
BaseAction 的代码如下:
package priv.lwx.struts2.action;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.SessionAware;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* description
*
* @author liaowenxiong
* @date 2022/2/14 21:22
*/
public class BaseAction implements SessionAware, ServletRequestAware {
// 为了可以被子类继承使用,所以访问控制符可以默认或者protected,不建议public。
// private的变量不会被子类继承,不过子类对象中含有父类私有变量数据,无法直接访问。
Map<String, Object> session;
HttpServletRequest request;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
@Override
public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
}
Action 如何访问 application 对象
BaseAction 去实现接口 ServletContextAware,那么继承自 BaseAction 的业务类 Action 就可以直接使用 application 对象了。
Action 如何访问 response 对象
BaseAction 去实现接口 ServletResponseAware,那么继承自 BaseAction 的业务类 Action 就可以直接使用 response 对象了。
Action 中如何访问 application
ActionContext act = ActionContext.getContext();
Map<String, Object> application = act.getApplication();
// 把数据存放在application中
application.put("user", user);
application 将存放在 ValueStack 的 context 中。
Action 组件属性注入
所谓 Action 组件属性的注入,是指 Action 中声明的属性,可以在 struts.xml 中通过配置进行赋值。
例如在 ProjectListAction 中声明 page 和 rowsPerPage 两个属性,代码如下:
package priv.lwx.struts2;
import priv.lwx.struts2.dao.ProjectDAO;
import priv.lwx.struts2.entity.Project;
import java.util.List;
/**
* description
*
* @author liaowenxiong
* @date 2022/2/7 15:19
*/
public class ProjectListAction {
// input
private int page;
private int rowsPerPage;
// output
private List<Project> projects;
private int totalPages;
public int getTotalPages() {
return totalPages;
}
public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}
public String execute() {
ProjectDAO pdao = new ProjectDAO();
// projects = pdao.findAll();
projects = pdao.findAll(page, rowsPerPage);
totalPages = pdao.getTotalPages(rowsPerPage);
return "success";
}
public List<Project> getProjects() {
return projects;
}
public void setProjects(List<Project> projects) {
this.projects = projects;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public int getRowsPerPage() {
return rowsPerPage;
}
public void setRowsPerPage(int rowsPerPage) {
this.rowsPerPage = rowsPerPage;
}
}
然后在 struts.xml 中通过标签 配置属性值,如下所示:
<action name="projectlist" class="priv.lwx.struts2.ProjectListAction">
<param name="page">1</param>
<param name="rowsPerPage">3</param>
<result name="success">/WEB-INF/jsp/projectlist3.jsp</result>
</action>
在 Action 组件对象创建的时候,Action 对象中的属性 page 被赋值 1;rowsPerPage 就被赋值为 5,那么 Action 对象在调用 DAO 时,将页数和每页行数两个参数传入 DAO 的相关方法中,从而得到需要的数据。
请求参数数据自动封装到 Action 对象中
参数值注入到 Action 对象对应的属性中
在控制器调 Action 组件之前会先通过一组拦截器,其中一个拦截器会获得请求参数值,然后将参数值赋给 Action 组件中的对应属性上,其实是调用 Action 对象的 setter 方法给对应的成员变量赋值。
例如,页面上有用户名称和密码的文本输入框,提交表单数据后,用户名称和密码会自动封装到 Action 对象中,即赋值给 Action 对象中对应的成员变量。
页面的示例代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/sa/demo01/login1.action" method="post">
<table>
<tr>
<td>
用户名:
</td>
<td>
<input name="userName" type="text"/>
</td>
</tr>
<tr>
<td>
密码:
</td>
<td>
<input name="password" type="text"/>
</td>
</tr>
<tr colspan="2">
<td>
<input value="提交" type="submit"/>
</td>
</tr>
</table>
</form>
</body>
</html>
Action 的示例代码:
package priv.lwx.struts2.action;
/**
* description
*
* @author liaowenxiong
* @date 2022/2/16 11:22
*/
public class LoginAction1 {
private String userName;
private String password;
public String execute() {
// 因为Struts2的拦截器会自动将参数值赋值给Action对象中对应的成员变量,所以这里可以直接访问变量,获取变量的值
System.out.println(userName);
System.out.println(password);
return "success";
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
参数值封装成 JavaBean 对象,再注入到 Action 对象中对应的属性中
如果 Action 中的成员变量是 JavaBean 类型,可以将参数值赋值给 Bean 对象中对应的属性。
如下所示,页面的示例代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/sa/demo01/login.action" method="post">
<table>
<tr>
<td>
用户名:
</td>
<td>
<input name="user.userName" type="text"/>
</td>
</tr>
<tr>
<td>
密码:
</td>
<td>
<input name="user.password" type="text"/>
</td>
</tr>
<tr colspan="2">
<td>
<input value="提交" type="submit"/>
</td>
</tr>
</table>
</form>
</body>
</html>
Action 的示例代码:
package priv.lwx.struts2.action;
import priv.lwx.struts2.action.dao.UserDAO;
import priv.lwx.struts2.action.entity.User;
/**
* description
*
* @author liaowenxiong
* @date 2022/2/14 11:54
*/
public class LoginAction extends BaseAction {
private User user;
private UserDAO userDAO = new UserDAO();
public String login() {
System.out.println(user.getUserName());
System.out.println(user.getPassword());
User user = userDAO.login(this.user.getUserName(), this.user.getPassword());
if (user != null) {
// 登录成功
// 继承自父类的变量session,LoginAction实例化后,控制器会调用setSession(Map session)方法,
// 将session对象赋值给变量session
session.put("user", user);
return "success";
}
// 登录失败
return "fail";
}
public String form() {
return "success";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
提交表单时传递的参数是 user.userName 和 user.password,而 Action 的成员变量就是 User 类型的,那么Struts2的相关拦截器就会先构造一个 User 对象,然后将参数值封装到 User 对象中,再将 User 对象复制给 Action 的成员变量 user。
日期数据也会自动封装到 JavaBean 对象中对应的 java.util.Date 类型的成员变量中。
例如,ProjectCreateAction 有 Project 类型的成员变量:
public class ProjectCreateAction {
private Project project;
...
Project 对象含有 Date 类型的成员变量 startDate 和 endDate:
public class Project {
private Integer id;
private String no;
private String name;
private Date startDate;
private Date endDate;
...
JSP 页面的表单项:
<tr>
<td class="altbg1" width="20%"><b>开始时间:</b></td>
<td class="altbg2"><input type="date" name="project.startDate"/></td>
</tr>
<tr>
<td class="altbg1" width="20%"><b>结束时间:</b></td>
<td class="altbg2"><input type="date" name="project.endDate"/></td>
</tr>
注意参数名称的格式:Action属性名称.JavaBean的属性名称
。
复选框的参数值可以自动封装成数组对象,注入 Action 对应的成员变量中
JSP 的示例代码:
<form action="projectdelete.action" method="post">
<table class="tableborder" cellspacing="0" cellpadding="0" width="100%" border="0">
<tbody>
<tr class="header">
<td class="altbg1">
<b> </b>
</td>
<td class="altbg1">
<b>编号</b>
</td>
<td class="altbg1">
<b>名称</b>
</td>
<td class="altbg1">
<b>开始时间</b>
</td>
<td class="altbg1">
<b>结束时间</b>
</td>
</tr>
</tbody>
<tbody>
<s:iterator value="projects">
<tr>
<td class="altbg2">
<%--提交数据时,参数projects的值会自动封装成Integer数组,注入到Action对象的成员变量projects--%>
<input type="checkbox" name="projects" value="${id}"/>
</td>
<td class="altbg2" width="20%">
<b><s:property value="no"/></b>
</td>
<td class="altbg2">
<b><s:property value="name"/></b>
</td>
<td class="altbg2">
<b><s:property value="startDate"/></b>
</td>
<td class="altbg2">
<b><s:property value="endDate"/></b>
</td>
</tr>
</s:iterator>
</tbody>
</table>
<br/>
<center>
<input class="button" type="submit" value="删除项目"/>
<input class="button" type="button" value="新建项目" onclick=""/>
</center>
</form>
Action 组件的示例代码:
package priv.lwx.struts.memory.action;
import priv.lwx.struts.memory.dao.ProjectDAO;
/**
* description
*
* @author liaowenxiong
* @date 2022/2/18 11:03
*/
public class ProjectDeleteAction extends BaseAction {
private Integer[] projects;
private ProjectDAO projectDAO = new ProjectDAO();
public String execute() {
projectDAO.delete(projects);
return "success";
}
public Integer[] getProjects() {
return projects;
}
public void setProjects(Integer[] projects) {
this.projects = projects;
}
}
数据的传送(Action 对象中的属性值输出到页面)
Action 组件的属性可以接收请求参数值,也可以把属性值向外传送(即输出到页面中),在转发的 JSP 中使用 EL 表达式来获取并输出。
Result 组件
在 Struts2 中,Result 的功能非常强大的。Reslut 也是类,职责是生成视图。视图可以是多种多样的(比如 JSP、JSON、报表、freeMarker 等),这些视图都可以由 Reslut 负责。
查看 struts-default.xml
文件,“struts-default” 共定义了 10 个 <result-type>
<result-types>
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
<result-type name="dispatcher" class="org.apache.struts2.result.ServletDispatcherResult" default="true"/>
<result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>
<result-type name="httpheader" class="org.apache.struts2.result.HttpHeaderResult"/>
<result-type name="redirect" class="org.apache.struts2.result.ServletRedirectResult"/>
<result-type name="redirectAction" class="org.apache.struts2.result.ServletActionRedirectResult"/>
<result-type name="stream" class="org.apache.struts2.result.StreamResult"/>
<result-type name="velocity" class="org.apache.struts2.result.VelocityResult"/>
<result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/>
<result-type name="plainText" class="org.apache.struts2.result.PlainTextResult" />
<result-type name="postback" class="org.apache.struts2.result.PostbackResult" />
</result-types>
Result 常用类型:
1.dispatcher
2.redirect
3.redirectAction
4.stream
5.json
dispatcher
不写 type,默认就是 dispatcher。
<action name="form" class="priv.lwx.struts2.action.LoginAction" method="form">
<result name="success">/login.jsp</result>
</action>
redirect
在 Struts2 中,重定向的地址都是从应用名(虚拟目录)之后开始写(即不含应用名),如下所示:
<action name="result" class="priv.lwx.struts2.action.ResultAction">
<result name="success" type="redirect">/demo01/form.action</result>
</action>
上述的demo01是命名空间,前面的正斜杠可有可无。
chain
要转发到另一个Action,必须使用这个类型。dispatcher 只能转发到视图组件。
<result name="success" type="chain">
<!--/WEB-INF/jsp/uploadimage1.jsp-->
display
</result>
而且只能指定 Action 的名称,不能加后缀 .action
,也不能在前面加正斜杠。
redirectAction
与 redirect 效果相同,只要写 Action 的名字即可:
<action name="result" class="priv.lwx.struts2.action.ResultAction">
<result name="success" type="redirectAction">form</result>
</action>
json
如果想使用 json,必须引用 struts2-json-plugin.jar
。接着 struts.xml 文件中,action 所在的 package 的 extends 填写“json-default”。这里的 json-default 是 struts2-json-plugin 项目下的 struts-plugin.xml 中的 package 的名称。json-default 包继承了 struts-default,在此基础上增加了 json 的 result-type。
LoginAction 的示例代码:
package priv.lwx.struts2.action;
import priv.lwx.struts2.action.dao.UserDAO;
import priv.lwx.struts2.action.entity.User;
/**
* description
*
* @author liaowenxiong
* @date 2022/2/14 11:54
*/
public class LoginAction extends BaseAction {
private User user;
public String login() {
System.out.println(user.getUserName());
System.out.println(user.getPassword());
return "success";
}
public String form() {
return "success";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
JSP 的示例代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/sa/demo01/login.action" method="post">
<table>
<tr>
<td>
用户名:
</td>
<td>
<input name="user.userName" type="text"/>
</td>
</tr>
<tr>
<td>
密码:
</td>
<td>
<input name="user.password" type="text"/>
</td>
</tr>
<tr colspan="2">
<td>
<input value="提交" type="submit"/>
</td>
</tr>
</table>
</form>
</body>
</html>
struts.xml 中配置 result 的 type 为“json”:
<action name="login" class="priv.lwx.struts2.action.LoginAction" method="login">
<result name="success" type="json">
</result>
</action>
最后服务端返回给客户端 json 字符串:
json串的数据哪里来的?
相关的拦截器会去获取Action对象成员变量的值,拼接成json串,再写入 Response 对象中,容器再取出生成响应数据包返回给客户端。
ValueStack 对象
struts2 的控制器接收到请求后,会先创建 request 和 response 对象,接着会创建 session 对象,接着会创建 HttpServletRequest 的另外一种实现类的对象 root,接着会创建 ValueStack 对象,接着创建 Action 组件的对象,将 Action 对象存储在 ValueStack 对象中,而 ValueStack 对象则会存储在 request 对象中,ValueStack 这个对象内部有个 Ognl 引擎,这个引擎有两个属性,属性名分别为 root 和 context,root 属性指向的是一个CompoundRoot 类的实例,是栈结构。context 属性指向的是一个 Map 对象。而 Action 对象则存储在 root 对象中,同时把 request 对象、session 对象、application 对象的引用地址存放在 ValueStack 对象中的 context 对象里。
struts2 标签/ognl表达式/el表达式
摘要:
1.ognl 表达式,就是由 Ognl 引擎解析表达式,然后到 ValueStack 对象中查询指定属性的值。
2.struts2 中的 el 表达式,底层是到 pageContext、request、root、session、application 等域对象中查询指定属性的值。root 对象其实 HttpServletRequest 的实现类的实例对象,这个对象中的某个方法会去 request 对象取数据,取不到则会调用 Ognl 引擎到 ValueStack 对象中取数据,所以 struts2 中的 el 表达式底层有使用到 ognl 表达式。
在 JSP 中使用 struts2 标签 <property/>
,如下所示:
<s:property value="user.userId"/>
这个 struts2 标签和 ognl 组合的含义:
Ognl 引擎会解析 ognl 表达式"user.userId",直接到 ValueStack 对象中 root 栈顶去获取属性 user 的值,而这个值是 user 对象,接着获取 user 对象的属性 userId 的值并输出。
在 struts.xml 配置文件中使用 el 表达式,如下所示:
<result name="success" type="dispatcher">
/WEB-INF/jsp/user.jsp?userId=${user.userId}
</result>
这个 el 表达式可以获取到 Action 对象中 user 属性的值,这个属性指向的是一个 user 对象,从而得到 user 对象的 userId 属性的值。
el 表达式 ${user.userId}
底层的运行过程:
我们想象有一个 el 引擎,它先到 pageContext、request 找属性 user 的值,其实是找不到的,然后调用 Ognl 引擎,将 el 表达式转换成 ognl 表达式,直接到 ValueStack 对象中的 root 对象的栈顶查找,结果找到了,因为 Action 对象就在栈顶,它有 user 这个属性,而 user 属性引用的是一个 User 对象,该对象含有 userId 属性,于是可以获取到该属性的值。
接着控制器将请求转发给 user.jsp 文件,在转发路径中携带参数 userId 值,在 JSP 文件中含有下面的代码:
<%
out.print("<h1>"+request.getParameter("userId")+"</h1>");
%>
转发路径中携带参数,那么参数值会存放在 request 对象中,jsp组件可以共享同个请求中的 request 对象中的数据,所以可以通过 request对象获取到 userId 参数值。
result 组件的 type="dispatcher"
,jsp 中的 el 表达式和 ognl 表达式才可以获得 Action 对象的属性值,因为 Action 对象实际上是在 request 对象中,转发的组件可以共享 request 对象中的数据;如果改成 redirect 类型,是重新发送请求,而且只是调用 jsp 组件,根本就没有调用 Action 组件,新的请求中 ValueStack 对象是重新创建的,request、session 等对象也是新建的,ValueStack 对象中的 root 栈顶根本就没有 Action 对象,所以 jsp 中的 el 表达式和 ognl 表达式根本无法获取到前一次请求中的 Action 对象属性中的数据。
非常有意思,type=“dispatcher”,我们要获得 Action 对象的属性值,也要在 JSP 中写点东西(el表达式或ognl表达式),可是 <result name="success" type="json"></result>
并没有指定 JSP,没有写任何获取 Action 对象的属性值的代码,就可以获取到 Action 对象的属性值,并且以 json 字符串的形式发送给浏览器显示。说明 json 对应的 Class 在作怪。它已经封装了获取数据的代码并且以json字符串的格式发送出去,在 response 中形成一个 html 静态页面。
Struts2 的 MVC 结构图
注:Struts2 框架中,Action
类中的成员变量在 jsp
中可以使用 el
表达式直接访问。
Struts2 的原理图
注:Action
类的访问路径在 Struts 2 的 struts.xml
文件中配置。
表单的请求资源路径写法
JSP 页面如果不通过 Struts2 控制器转发,则必须写 URI,即必须从项目的虚拟目录开始写,如下所示:
<form action="/sa/demo01/login.action" method="post"></form>
JSP 页面如果通过 Struts2 控制器转发,则不需要写项目的虚拟目录和 package 的命名空间,如下所示:
<form action="login.action" method="post"></form>