SSH框架之旅-struts2(4)

2018-08-30 09:41:10 浏览数 (1)

struts.jpg

1.Struts2 拦截器


1.1 AOP 思想

AOP 是 Aspect Objected Prograing(面向切面编程)的缩写。struts2 中的拦截器就是这种编程策略的一种实现,AOP 思想是在基本功能上,不通过修改源代码就可以扩展功能,提高代码的重用性。

1.2 拦截器概述

struts2 框架的许多功能都是基于拦截器的,struts2 中有很多拦截器,默认的拦截器每次都执行。说到拦截器,还有一个种重要的概念——拦截器链(在 struts2 中称为是拦截器栈)。拦截器链就是将一堆拦截器按照一定顺序联结成一条链,在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其定义的顺序调用。

1.3 struts2 拦截器原理

struts2 的拦截是通过 xml文件的配置实现的,默认的拦截器在 struts2-core-*.jar 包中的 struts-default.xml 文件,可以查看源代码。当收到一个请求时,struts2 会先查找xml配置文件,并根据配置来实例化拦截器对象,然后串成一条链,请求要通过每一个拦截器,才能执行 Action 中的方法,最终才能得到想要的结果。这么多拦截器,使用一个代理对象对它们进行动态调用。当 action 的请求到来时,创建 Action 的代理对象,这个代理对象在 Action 方法执行之前执行默认的拦截器和其他拦截器(用户自定义的拦截器),最后才是 Action 对应方法的调用,这里面是数据结构栈的思想。代理对象调用栈的最底层才是 Action 方法的调用,然后在返回给上一个拦截器,层层退出。

struts2 拦截器结构的设计是一个典型的责任链模式的应用,首先将整个执行的过程划分为若干相同类型的元素,每个元素具备不同的逻辑责任,并将这些元素放到一个栈式的数据结构中,每个元素又有责任负责下一个元素的执行调用。将一个复杂的系统,分而治之,从而将每个部分的逻辑能够高度重用并具备高度扩展性,拦截器在 struts2 中的设计实乃精彩。

实现原理

查看源代码

找到 web.xml 文件中配置拦截器类的位置 org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter,找到这个类中的 execute.executeAction(request, response, mapping) 方法,进入这个方法查看,dispatcher.serviceAction(request, response, mapping),在进入这个方法找到 ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false) ,这个方法就会创建 action 的代理对象,不是直接 new 出来的,继续往下找到 proxy.execute() 方法,进入方法查看,会发现这是一个接口,所以找到这个方法的实现类 StrutsActionProxy,在这个类中找到 execute() 方法,该方法的返回值是 invocation.invoke(),表示执行 Action 中的方法,进入方法查看也是一个接口,找到这个方法的实现类 DefaultActionInvocation,在这个类的 invoke() 方法中有一个遍历拦截器的操作 if (interceptors.hasNext()),一个拦截器执行完后执行下一个拦截器,最后返回invocation.invoke(),执行 Action 中的方法。

1.4 拦截器和过滤器的区别

  • 过滤器理论上可以过滤任意内容,比如 jsp页面,html页面,servlet,图片路径等等
  • 拦截器只能拦截 Action,每次访问时就会创建一个 Action,多个 Action 的对象。

1.4 自定义拦截器

在 struts2 中有很多默认的拦截器,打开 struts2-core-*.jar 包中的 struts-default.xml 文件,在 <interceptor-stack name="paramsPrepareParamsStack"> 标签中的拦截器就是 struts2 的默认拦截器。在实际的开发中,如果想使用是 struts2 中没有的拦截器功能,这时就要自己写自定义的拦截器。

查看源代码查看拦截器类的结构

在 struts-default.xml 文件中,有很多拦截器,在 <interceptors> 标签中有拦截器的包名,可以找一个进入查看,这些默认的拦截器类都继承自抽象的拦截器类 AbstractInterceptor,抽象拦截器类中有 init(),destroy(),intercept() 三个方法。init() 方法执行拦截器的初始化操作,destroy() 方法执行拦截器的销毁操作,intercept() 方法执行拦截器具体的逻辑操作。但在开发中建议继承 MethodFilterInterceptor 类来实现自定义的拦截器,这样可以对 Action 中的某些方法不进行拦截。

拦截器和 Action 建立关系不是直接在 Action 中调用拦截器的方法,而是通过配置文件的方式让两者建立关系的。

拦截器实现的步骤:

    1. 创建拦截器类,继承 MethodFilterInterceptor 类
    1. 重写 MethodFilterInterceptor 类中的 doIntercept() 方法,在这个方法写拦截器的逻辑

拦截器配置的步骤:

    1. 在要拦截的 action 标签所在的 package 标签中声明拦截器
    1. 在具体的 action 标签中使用声明的自定义拦截器
    1. 手动配置 struts2 的默认拦截器

下面就通过一个用户登陆的案例来说明自定义拦截器的使用。

在 Web 应用中,用户需要在登录之后才能使用主页面的功能,如果用户没有登录,则在使用主页面的功能之前先让其登录,用户登录成功,在 session 中保存用户名。

示例代码如下:

拦截器类

代码语言:javascript复制
package cc.wenshixin.interceptor;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;

public class LoginInteceptor extends MethodFilterInterceptor{

    @Override
    //在这个方法中写拦截器的逻辑
    protected String doIntercept(ActionInvocation invocation) throws Exception {
        //判断 session 中是否有用户名,也即是判断用户是否登录
        HttpServletRequest request = ServletActionContext.getRequest();
        Object obj = request.getSession().getAttribute("username");
        if(obj != null)
        {
            //已经登录,则执行 Action 中的方法
            return invocation.invoke();
        }else
        {
            //未登录,则返回值使其跳转到登录页面
            return "login";
        }
    }
    
}

Action 类

代码语言:javascript复制
package cc.wenshixin.action;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

public class ActionDemo extends ActionSupport{
    public String login()
    {
        //获取表单数据
        HttpServletRequest request = ServletActionContext.getRequest();
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //这里为了简单就直接写死判断了,实际开发中要在数据库查询
        if("admin".equals(username) && "123".equals(password))
        {
            //登录成功,设置session值,并挑转到主页面
            request.getSession().setAttribute("username", username);
            return "success";
        }else
        {
            //用户名密码错误,则跳转回登录页面
            return "login";
        }
    }
    
    public String add()
    {
        //跳转到添加页面
        return "add";
    }
    
    public String delete()
    {
        //跳转到删除页面
        return "delete";
    }
}

struts2 配置文件

注意:如果在配置文件中配置自定义的拦截器,默认的 struts2 拦截器就不会执行了,所以要把默认的拦截器手动使用一下。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
  "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
  "http://struts.apache.org/dtds/struts-2.3.dtd">
  
<struts>
  <package name="demo" extends="struts-default" namespace="/">
    <!-- 声明拦截器 -->
    <interceptors>
      <interceptor name="loginInteceptor" class="cc.wenshixin.interceptor.LoginInteceptor"></interceptor>
    </interceptors>
    <action name="user-*" class="cc.wenshixin.action.ActionDemo" method="{1}">
      <!-- 使用自定义的拦截器 -->
      <interceptor-ref name="loginInteceptor">
        <!-- 配置不拦截的方法,多个方法用逗号隔开 -->
        <param name="excludeMethods">login</param>
      </interceptor-ref>
      <!-- 手动使用默认的拦截器 -->
      <interceptor-ref name="defaultStack"></interceptor-ref>
      <!-- 设置页面跳转为重定向方式 -->
      <result name="success" type="redirect">/index.jsp</result>
      <result name="login" type="redirect">/login.jsp</result>
      <result name="add" type="redirect">/add.jsp</result>
      <result name="delete" type="redirect">/delete.jsp</result>
    </action>
  </package>
</struts>

jsp 页面

    1. 登录页面
代码语言:javascript复制
<%@ 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>
  <h1>登录页面</h1>
  <form action="{pageContext.request.contextPath}/user-login.action" method="post">
        用户名:<input type="text" name="username"><br>
        密码:    <input type="password" name="password"><br>
      <input type="submit" value="登录"> 
  </form>
</body>
</html>
    1. 主页页面
代码语言:javascript复制
<%@ 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>
  <h1>欢迎 ${sessionScope.username} 主页面</h1>
  <a href="{pageContext.request.contextPath}/user-add.action" target="_parent">跳转到添加页面</a>
  <a href="{pageContext.request.contextPath}/user-delete.action" target="_parent">跳转到删除页面</a>
</body>
</html>
    1. 添加页面
代码语言:javascript复制
<%@ 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>
  <h1>这是添加页面</h1>
</body>
</html>
    1. 删除页面
代码语言:javascript复制
<%@ 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>
  <h1>这是删除页面</h1>
</body>
</html>

2.struts2 标签库


2.1 普通标签

  • <s:property>:和 OGNL 表达式一起使用在 jsp 页面中获取值栈中的数据
  • <s:iterator>:获取值栈 list 集合的数据,遍历 list 集合中的值
  • <s:debug>:查看值栈结构和数据

2.2 表单标签

2.2.1 HTML 表单相关标签回顾

form 表单

<form action="" method="" enctype="">

输入项

普通输入项: <input type="text"> 密码输入项: <input type="password"> 单选输入项: <input type="radio"> 复选输入项: <input type="checkbox"> 文件上传项: <input type="file"> 隐藏输入项: <input type="hidden"> 普通按钮: <input type="button"> 提交按钮: <input type="submit"> 图片按钮: <input type="image"> 重置按钮: <input type="reset"> 下拉输入项: <select><option></select> 文本域: <textarea rows="" cols=""></textarea>

2.2.2 struts2 中对应的表单标签(知道即可)

注意要在 jsp 中引入 struts2 的标签库。

代码语言:javascript复制
  <s:form>
    <!-- 普通输入项 -->
    <s:textfield name="username" label="用户名"></s:textfield>
    <!-- 密码输入项 -->
    <s:password name="password" label="密码"></s:password>
    <!-- 单选输入项 -->
      <!-- value的值和显示的值一样的写法 -->
    <s:radio list="{'男','女','保密'}" name="sex" label="性别"></s:radio>
      <!-- value的值和显示的值不一样的写法 -->
    <s:radio list="#{'nan':'男','nv':'女','secret':'保密'}" name="sex" label="性别"></s:radio>
    <!-- 复选框输入项 -->
    <s:checkboxlist list="{'看书','写字','画画'}" name="love" label="爱好"></s:checkboxlist>
    <!-- 下拉输入项 -->
    <s:select list="{'北京','上海','广州'}" name="address" label="地址"></s:select>
    <!-- 文件输入项 -->
    <s:file name="file" label="上传文件"></s:file>
    <!-- 隐藏输入项 -->
    <s:hidden name="hidden" value="123"></s:hidden>
    <!-- 文本域 -->
    <s:textarea rows="10" cols="10"></s:textarea>
    <!-- 提交按钮 -->
    <s:submit value="提交"></s:submit>
    <!-- 重置按钮 -->
    <s:reset value="重置"></s:reset>
  </s:form>

页面效果如下图所示

效果图

页面源码如下图所示

查看网页源代码,可以发现,struts2 的 form 标签里面是通过 table 来布局的,这并不符合现在的网页布局方式(Div 布局),也不利于页面的美化,所以 struts2 的 form 标签基本不用。

页面源码

0 人点赞