JavaWeb 之 Struts2 中的 Servlet API、封装数据、拦截器

Servlet API、封装数据、拦截器。


Struts2 框架中 Servlet API 的使用


Servlet 的 API

Action 类中也可以获取到 Servlet 一些常用的 API

  • 案例需求:提供 JSP 的表单页面的数据,在 Action 中使用 ServletAPI 接收到,然后保存到三个域对象中,最后再显示到 JSP 的页面上。

提供 JSP 注册的页面:

1
2
3
4
5
6
<h3>注册页面</h3>
<form action="${ pageContext.request.contextPath }/xxx.action" method="post">
姓名:<input type="text" name="username" /><br/>
密码:<input type="password" name="password" /><br/>
<input type="submit" value="注册" />
</form>

完全解耦合的方式

为了避免与 Servlet API 耦合在一起,方便 Action 类做单元测试,Struts2 对 HttpServletRequestHttpSessionServletContext 进行了封装,构造了三个 Map 对象来替代这三种对象,在 Action 中,直接使用 HttpServletRequestHttpSessionServletContext 对应的 Map 对象来保存和读取数据。

要获得这三个Map对象,可以使用 com.opensymphony.xwork2.ActionContext类

ActionContext请求上下文

常用的方法如下:

1
2
3
4
5
static ActionContext getContext()  										-- 获取 ActionContext 对象实例
java.util.Map<java.lang.String,java.lang.Object> getParameters() -- 获取请求参数,相当于 request.getParameterMap();
java.util.Map<java.lang.String,java.lang.Object> getSession() -- 获取的代表 session 域的 Map 集合,就相当于操作 session 域。
java.util.Map<java.lang.String,java.lang.Object> getApplication() -- 获取代表 application 域的 Map 集合
void put(java.lang.String key, java.lang.Object value) -- 注意:向 request 域中存入值。

案例演示

Demo1Action.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 完全解耦合的方式,使用 Servlet 的 API
*/
public class Demo1Action extends ActionSupport {
@Override
public String execute() throws Exception {
// 完全解耦合的方式
ActionContext context = ActionContext.getContext();
// 获取到请求的参数,封装所有请求的参数
Map<String, Object> map = context.getParameters();
// 遍历获取数据
Set<String> keys = map.keySet();
for (String key : keys) {
// 通过 Key,来获取到值
String[] vals = (String[]) map.get(key);
System.out.println(key + ":" + Arrays.toString(vals));
}
// 向 request 域中存入值
context.put("msg","小天");
// 获取其他 map 集合,并存入数据
context.getSession().put("msg","小明");
context.getApplication().put("msg","小红");

return SUCCESS;
}
}

JSP 输入页面:

1
2
3
4
5
6
<h3>完全解耦合方式</h3>
<form action="${ pageContext.request.contextPath }/demo1Action.action" method="post">
姓名:<input type="text" name="username" /><br/>
密码:<input type="password" name="password" /><br/>
<input type="submit" value="注册" />
</form>

JSP 跳转页面:

1
2
3
4
<h3>使用 EL 表达式获取值</h3>
${requestScope.msg}
${sessionScope.msg}
${applicationScope.msg}

struts.xml 配置文件:

1
2
3
4
5
6
<package name="demo1" namespace="/" extends="struts-default">
<!--完全解耦合的方式-->
<action name="demo1Action" class="com.renkaigis.demo1.Demo1Action">
<result name="success">/demo1/success.jsp</result>
</action>
</package>

案例结果:

输入信息:

跳转并取值:

控制台输出结果:

1
2
password:[12345]
username:[renkai]

使用原生 Servlet 的 API 的方式

直接访问 Servlet API 将使 Action 类与 Servlet API 耦合在一起,Servlet API 对象均由 Servlet 容器来构造,与这些对象绑定在一起,测试过程中就必须有 Servlet 容器,这样不便于 Action 类的测试,但有时候,确实需要访问这些对象,Struts2 同样提供了直接访问 ServletAPI 对象的方式。

要直接获取 Servlet API 对象可以使用 org.apache.struts2.ServletActionContext 类,该类是 ActionContext 类的子类。

  • 具体的方法如下
1
2
3
4
5
HttpServletRequest request=ServletActionContext.getRequest();
HttpSession session=request.getSession();
session.setAttribute("xxx",xxx);
HttpServletResponse response=ServletActionContext.getResponse();
HttpServletContext application=ServletActionContext.getApplication();

案例演示

Demo2Action.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 原生 Servlet API
*/
public class Demo2Action extends ActionSupport {
@Override
public String execute() throws Exception {
// 获取到 request 对象
HttpServletRequest request = ServletActionContext.getRequest();
request.setAttribute("msg", "天天");
request.getSession().setAttribute("msg", "美美");
ServletActionContext.getServletContext().setAttribute("msg", "园园");

// 获取 response 对象,可以使用输出流,输出内容
HttpServletResponse response = ServletActionContext.getResponse();

return SUCCESS;
}
}

JSP 输入页面:

1
2
3
4
5
6
<h3>ServletCActionContext 类</h3>
<form action="${ pageContext.request.contextPath }/demo2Action.action" method="post">
姓名:<input type="text" name="username" /><br/>
密码:<input type="password" name="password" /><br/>
<input type="submit" value="注册" />
</form>

JSP 跳转页面:

1
2
3
4
<h3>使用 EL 表达式获取值</h3>
${requestScope.msg}
${sessionScope.msg}
${applicationScope.msg}

struts.xml 配置文件:

1
2
3
4
<!--原生 Servlet API 方式-->
<action name="demo2Action" class="com.renkaigis.demo1.Demo2Action">
<result name="success">/demo1/success.jsp</result>
</action>

案例结果:

输入信息:

跳转并取值:


结果类型的跳转


结果页面存在两种方式

全局结果页面

条件:如果 <package> 包中的一些 action 都返回 success,并且返回的页面都是同一个 JSP 页面,这样就可以配置全局的结果页面。

全局结果页面针对的 当前包 中的所有的 Action,但是如果局部还有结果页面,会 优先局部 的。使用的标签是:

1
2
3
4
5
6
<package name="demo1" namespace="/" extends="struts-default">
<!--配置全局的结果页面-->
<global-results>
<result name="success">/demo1/success.jsp</result>
</global-results>
</package>

局部结果页面

也就是之前一直所使用的。

1
<result>/demo1/success.jsp</result>

结果页面的类型

  • 结果页面使用 <result> 标签进行配置,包含两个属性

name – 逻辑视图的名称

type – 跳转的类型,需要掌握一些常用的类型。常见的结果类型去 struts-default.xml 中查找。

  • dispatcher – 转发,type的默认值Action--->JSP
  • redirect – 重定向。 Action--->JSP
  • chain – 多个action之间跳转,从一个Action转发到另一个Action。 Action---Action
  • redirectAction – 多个action之间跳转,从一个Action重定向到另一个Action。 Action---Action
  • stream – 文件下载时候使用的

Struts2 框架的数据封装


数据的封装

  • 作为 MVC 框架,必须要负责解析 HTTP 请求参数,并将其封装到 Model 对象中
  • 封装数据为开发提供了很多方便
  • Struts2 框架提供了很强大的数据封装的功能,不再需要使用 Servlet 的 API 完成手动封装了

Struts2 中提供了两类数据封装的方式:

第一种方式:属性驱动

提供对应属性的 set 方法进行数据的封装。

  • 表单的哪些属性需要封装数据,那么在对应的 Action 类中提供该属性的 set 方法即可。

  • 表单中的数据提交,最终找到 Action 类中的 setXxx 的方法,最后赋值给全局变量。

  • 注意0:Struts2 的框架采用拦截器完成数据的封装。

  • 注意1:这种方式不是特别好:因为属性特别多,提供特别多的set方法,而且还需要手动将数据存入到对象中。

  • 注意2:这种情况下,Action 类就相当于一个 JavaBean,就没有体现出 MVC 的思想,Action 类又封装数据,又接收请求处理,耦合性较高。

Action:

1
2
3
4
5
6
7
8
9
10
11
12
13
private String username;
private String password;
private Integer age;
// 只需要提供 set 方法
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAge(Integer age) {
this.age = age;
}

struts.xml

1
2
3
4
<package name="demo2" extends="struts-default" namespace="/">
<!--属性驱动的方式-->
<action name="regist1" class="com.renkaigis.demo2.Regist1Action"/>
</package>

jsp页面

1
2
3
4
5
6
7
<h3>属性驱动的方式</h3>
<form action="${ pageContext.request.contextPath }/regist1.action" method="post">
姓名:<input type="text" name="username" /><br/>
密码:<input type="password" name="password" /><br/>
年龄:<input type="text" name="age" /><br/>
<input type="submit" value="注册" />
</form>

输出结果

1
renkai 12 25

属性驱动:把数据封装到 JavaBean 对象中

在页面上,使用 OGNL 表达式进行数据封装。

  • 在页面中使用 OGNL 表达式进行数据的封装,就可以直接把属性封装到某一个 JavaBean 的对象中。

  • 在页面中定义一个 JavaBean,并且提供 set 方法:例如:private User user;

  • 页面中的编写发生了变化,需要使用 OGNL 的方式,表单中的写法:<input type="text" name="user.username">

  • 注意:只提供一个 set 方法还不够,必须还需要提供 user 属性的 getset 方法!!!

原理: 先调用 get 方法,判断一下是否有 user 对象的实例对象,如果没有,调用 set 方法把拦截器创建的对象注入进来。

Action:

1
2
3
4
5
6
7
8
// 需要提供 get 和 set 方法
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}

struts.xml

1
2
3
4
<package name="demo2" extends="struts-default" namespace="/">
<!--属性驱动的方式,把数据封装到 JavaBean 的对象中-->
<action name="regist2" class="com.renkaigis.demo2.Regist2Action"/>
</package>

jsp页面

1
2
3
4
5
6
7
8
<h3>属性驱动的方式(把数据封装到 JavaBean 的对象中)</h3>
<%--页面的编写发生了变化,使用的是 OGNL 表达式的写法--%>
<form action="${ pageContext.request.contextPath }/regist2.action" method="post">
姓名:<input type="text" name="user.username" /><br/>
密码:<input type="password" name="user.password" /><br/>
年龄:<input type="text" name="user.age" /><br/>
<input type="submit" value="注册" />
</form>

输出结果

1
User{username='renkai', password='12', age=25}

第二种方式:模型驱动

使用模型驱动的方式,也可以把表单中的数据直接封装到一个 JavaBean 的对象中,并且表单的写法和之前的写法没有区别!

编写的页面不需要任何变化,正常编写 name 属性的值

模型驱动的编写步骤:

  • 手动实例化 JavaBean,即:private User user = new User();

  • 必须实现 ModelDriven<T> 接口,实现 getModel() 的方法,在 getModel() 方法中返回 user 即可!!

Action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 模型驱动的方式
* 需要实现 ModelDriven 接口
* 必须要手动实例化对象(需要自己new好)
*/
public class Regist3Action extends ActionSupport implements ModelDriven<User> {
// 必须要手动实例化
private User user = new User();
// 获取模型对象
@Override
public User getModel() {
return user;
}

@Override
public String execute() throws Exception {
System.out.println(user);
return NONE;
}
}

struts.xml

1
2
3
4
<package name="demo2" extends="struts-default" namespace="/">
<!--模型驱动的方式-->
<action name="regist3" class="com.renkaigis.demo2.Regist3Action"/>
</package>

jsp页面

1
2
3
4
5
6
7
<h3>模型驱动的方式</h3>
<form action="${ pageContext.request.contextPath }/regist3.action" method="post">
姓名:<input type="text" name="username" /><br/>
密码:<input type="password" name="password" /><br/>
年龄:<input type="text" name="age" /><br/>
<input type="submit" value="注册" />
</form>

输出结果

1
User{username='renkai', password='12', age=25}

Struts2 把数据封装到集合中

(默认采用的是属性驱动的方式)

把数据封装到 List 集合中

  • 因为 Collection 接口都会有下标值,所有页面的写法会有一些区别,注意:
1
<input type="text" name="products[0].name" />
  • Action 中的写法,需要提供 user 的集合,并且提供 getset 方法。

Action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 属性驱动的方式,把数据封装到List集合中
*/
public class Regist4Action extends ActionSupport {
private List<User> list;
public List<User> getList() {
return list;
}
public void setList(List<User> list) {
this.list = list;
}
@Override
public String execute() throws Exception {
for (User user : list) {
System.out.println(user);
}
return NONE;
}
}

struts.xml

1
2
3
4
<package name="demo2" extends="struts-default" namespace="/">
<!--把数据封装到List集合中-->
<action name="regist4" class="com.renkaigis.demo2.Regist4Action"/>
</package>

jsp页面

1
2
3
4
5
6
7
8
9
10
11
<h3>向List集合封装数据(默认情况下,采用的是属性驱动的方式)</h3>
<form action="${ pageContext.request.contextPath }/regist4.action" method="post">
姓名:<input type="text" name="list[0].username" /><br/>
密码:<input type="password" name="list[0].password" /><br/>
年龄:<input type="text" name="list[0].age" /><br/>

姓名:<input type="text" name="list[1].username" /><br/>
密码:<input type="password" name="list[1].password" /><br/>
年龄:<input type="text" name="list[1].age" /><br/>
<input type="submit" value="注册" />
</form>

输出结果

1
2
User{username='renkai', password='12', age=25}
User{username='xiaomei', password='34', age=22}

把数据封装到 Ma 中

  • Map 集合是键值对的形式,页面的写法
1
<input type="text" name="map['one'].name" />

注意:里面的 key 值可以自定义。

  • Action中提供 map 集合,并且提供 getset 方法

Action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 属性驱动的方式,把数据封装到map集合中
*/
public class Regist5Action extends ActionSupport {
private Map<String, User> map;
public Map<String, User> getMap() {
return map;
}
public void setMap(Map<String, User> map) {
this.map = map;
}
@Override
public String execute() throws Exception {
System.out.println(map);
return NONE;
}
}

struts.xml

1
2
3
4
<package name="demo2" extends="struts-default" namespace="/">
<!--把数据封装到map集合中-->
<action name="regist5" class="com.renkaigis.demo2.Regist5Action"/>
</package>

jsp页面:这里的 key 值自定义。

1
2
3
4
5
6
7
8
9
10
11
<h3>向map集合封装数据(默认情况下,采用的是属性驱动的方式)</h3>
<form action="${ pageContext.request.contextPath }/regist5.action" method="post">
姓名:<input type="text" name="map['one'].username" /><br/>
密码:<input type="password" name="map['one'].password" /><br/>
年龄:<input type="text" name="map['one'].age" /><br/>

姓名:<input type="text" name="map['two'].username" /><br/>
密码:<input type="password" name="map['two'].password" /><br/>
年龄:<input type="text" name="map['two'].age" /><br/>
<input type="submit" value="注册" />
</form>

输出结果

1
{one=User{username='renkai', password='12', age=25}, two=User{username='xiaomei', password='34', age=22}}

Struts2 的拦截器技术


拦截器

拦截器概述

  • 拦截器就是 AOP(Aspect-Oriented Programming,面向切面编程)的一种实现。(AOP是指用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。)

  • 过滤器:过滤从客服端发送到服务器端请求的。

  • 拦截器:对目标 Action 中的某些方法进行拦截。

拦截器不能拦截JSP

拦截 Action 中某些方法

拦截器和过滤器的区别

1)拦截器是基于 JAVA反射机制 的,而过滤器是基于 函数回调
2)过滤器依赖于Servlet容器,而拦截器不依赖于Servlet容器
3)拦截器 只能对Action请求 起作用(Action中的方法),而过滤器可以对 几乎所有的请求 起作用(CSS JSP JS)。

  • 拦截器 采用 责任链 模式

在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链

责任链每一个节点,都可以继续调用下一个节点,也可以阻止流程继续执行

  • 在 struts2 中可以定义很多个拦截器,将多个拦截器按照特定顺序组成拦截器栈(顺序调用栈中的每一个拦截器 )

Struts2 框架的核心是拦截器

自定义拦截器和配置

编写拦截器

需要实现 Interceptor 接口,实现接口中的三个方法

1
2
3
4
5
6
7
8
9
protected String doIntercept(ActionInvocation invocation) throws Exception {
// 获取session对象
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("existUser");
if(user == null){
// 说明,没有登录,后面就不会执行了
return "login";
}
return invocation.invoke();
}

配置拦截器

注意: 只要引用了自己的拦截器,Struts2 框架默认栈的拦截器就不执行了,必须要手动引入默认栈。

需要在 struts.xml 中进行拦截器的配置,配置一共有两种方式

第一种方式:直接引入

  • <package> 包中定义拦截器,出现在 <package> 包的上方
1
2
3
<interceptors>
<interceptor name="DemoInterceptor" class="com.renkaigis.interceptor.DemoInterceptor"/>
</interceptors>
  • 在某个 action 中引入拦截器:
1
2
3
4
5
<action name="userAction" class="com.renkaigis.demo3.UserAction">
<!-- 只要是引用自己的拦截器,默认栈的拦截器就不执行了,必须要手动引入默认栈 -->
<interceptor-ref name="DemoInterceptor"/>
<interceptor-ref name="defaultStack"/>
</action>

第二种方式:定义拦截器栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<interceptors>
<interceptor name="DemoInterceptor" class="com.renkaigis.interceptor.DemoInterceptor"/>
<!-- 定义拦截器栈 -->
<interceptor-stack name="myStack">
<interceptor-ref name="DemoInterceptor"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>

<action name="userAction" class="com.renkaigis.demo3.UserAction">
<!-- 只要是引用自己的拦截器,默认栈的拦截器就不执行了,必须要手动引入默认栈 -->
<interceptor-ref name="DemoInterceptor"/>
<interceptor-ref name="defaultStack"/>
</action>