七的博客

Struts2核心知识点

Java

Struts2核心知识点

1. Servlet 以及 JSP 编写 Web 应用的不足

JSP 中可以切入 Java 代码,可以生成动态的 HTML 页面。 但是业务逻辑跟 HTML 代码混一起,在 Eclipse 跟 IDEA 中调试都很困难。 同时对 HTML 跟 JAVA 代码的自动补全做的也不是特别好。

Servlet 适合处理复杂的业务逻辑,也方便调试。 但是有些地方还是比较繁琐,我总结出来比较典型的几个点:

  • 每次都需要手动从 request 中获取请求参数,然后封装成对象。
  • 请求参数验证需要自己写重复的逻辑去验证。
  • 扩展的标签 JSTL 也比较复杂,语法比较多。
  • 处理应答结果的时候,每次都需要去操作 HttpServletResponse 对象,设置应答头,应答体等。

这些重复的逻辑其实是可以进行封装的,不应该每次都重复的写这些逻辑。Struts 就是这么一个框架,可以解决上面说的这些问题。

2. Struts 的作用

Struts 2 是一个用于开发 Java Web 应用的开源框架,它前面的一个版本是 Struts 1。

看名字就知道了,2 肯定是 1的升级版本。 Struts2 是 Struts 1 和 WebWork 的结合。截止到 2015年,Struts 2 已经是一个成熟且广泛使用的 MVC 框架。

MVC 就是 Model、View、Controller 三个单词的缩写。

  • Model ( 模型 ) 代表的是程序的业务逻辑。
  • View ( 视图 ) 负责将数据渲染到展示界面上。
  • Controller ( 控制器 ) 负责处理请求,作为 Model 跟 View 之间的一个中间人,调用 Model 处理业务,指定 View 进行展示。

这种架构分离了各自的职责,不会出现在页面中写 Java 业务逻辑,Java 业务逻辑中也不会去写页面的代码。

使用 Struts 后,由 JSP 负责页面的展示, Struts 的 Action 作为控制器去接受以及处理请求,业务逻辑还是交由普通的 Java 类去处理。

3. Strut2 上手实践

先实际上手体验下 Struts2 ,案例还是用之前的 Servlet + JSP 的登录案例。

Strut2 依赖了好几个 JAR 包,我们需要从 Struts 官网进行下载。所以准备下面的环境:

  • 下载好 Apache Tomcat , 用来运行 JSP 应用。
  • 在 Eclipse 或者 IDEA 中创建好一个标准的 Java Web 应用,注意别选错项目类型。
  • 下载 Struts2 依赖包,这里我下载的是最新的 2.3.24.1 版本。

将下载好的的包解压,我们只需要用到一部分 JAR 包,所以只需要将下面的 JAR 包抽取出来复制到项目中的 WEB-INF\lib 目录中。

  • commons-fileupload-1.3.1.jar
  • commons-io-2.2.jar
  • commons-lang3-3.2.jar
  • freemarker-2.3.22.jar
  • javassist-3.11.0.GA.jar
  • ognl-3.0.6.jar
  • struts2-core-2.3.24.1.jar
  • xwork-core-2.3.24.1.jar

现在 web.xml 中配置下 fiter ,将请求都交给 Struts 。

    <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>

新建一个 com.suny.struts2demo.action 包,然后包下新建一个类 LoginAction ,写入下面的内容:

package com.suny.struts2demo.action;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;

import java.util.Map;

public class LoginAction extends ActionSupport implements SessionAware {
    private String username;
    private String password;
    private Map<String, Object> session;

    public String execute() {
        if ("admin".equals(username) && "password".equals(password)) {
            session.put("username", username);
            return SUCCESS;
        }
        addActionError("用户名或者密码验证失败.");
        return INPUT;
    }


    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;
    }

    @Override
    public void setSession(Map<String, Object> session) {
        this.session = session;
    }
}

再新建这个 /WEB-INF/jsp/login.jsp 文件,写入内容:

<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登录界面</title>
</head>
<body>
    <h2>填写登录信息</h2>
    <s:actionerror />
    <s:form action="login">
        <s:textfield name="username" label="Username" />
        <s:password name="password" label="Password" />
        <s:submit value="Login" />
    </s:form>
</body>
</html>

新建 /WEB-INF/jsp/success.jsp:

<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登录成功</title>
</head>
<body>
    <h2>欢迎登录, <s:property value="#session.username"/>!</h2>
    <p>登录成功.</p>
</body>
</html>

新建一个 Struts 的配置文件 struts.xml , 放置在 src/ 目录下,然后写上内容:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "/blog//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
        "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
    <constant name="struts.devMode" value="true"/>

    <package name="default" namespace="/" extends="struts-default">
        <action name="login" class="com.suny.struts2demo.action.LoginAction">
            <result name="input">/WEB-INF/jsp/login.jsp</result>
            <result name="success">/WEB-INF/jsp/success.jsp</result>
        </action>
    </package>
</struts>

上面的配置就是指定了一个 login 的 action, 其实就是暴露了一个接口 /login 。

action 的请求处理类是 com.suny.struts2demo.action.LoginAction

请求处理完如果是 input,会跳转到 /WEB-INF/jsp/login.jsp 这个页面。

请求处理完如果是 success ,会跳转到 /WEB-INF/jsp/success.jsp 这个页面。

通过这个登录改造的例子,可以得到很明显的区别:

  • Servlet 通过实现 HttpServlet 类,实现 doGet 和 doPost 方法处理请求。Struts 2 使用 Action 类通过 execute 方法来处理请求。
  • Servlet 是通过 request.getParameter() 获取参数。 Struts 2 是在 Action 类中定义属性,然后Struts 2 会自动填充,不用手动去取值赋值。
  • Servlet 在 web.xml 中配置 Servlet 映射或使用注解。Struts 2 在 struts.xml 中配置 action 映射。
  • Servlet + JSP 会使用原生 HTML 表单,但是 Struts 2 + JSP 可以使用 Struts 2 标签库,如 来简化一些逻辑。

从上面来看,Struts 简化了开发流程,但是不是这一个小例子就可以完全展示出威力的。

3. Struts 核心概念

3.1 Action

Action 就是 Struts 2 的核心概念,它处理来自用户的请求并返回一个结果。

Action 里可以有属性,用于接受请求参数,同时这个属性也可以是返回给页面的参数。比如下面这个例子:

public class UserAction extends ActionSupport {
    private String username;

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public String execute() {
        // 可以使用 提交上来的用户名 属性, 这时候的 username 是请求参数
        if (username == null || username.isEmpty()) {
            username = "Guest";  // 设置成游客,这时候页面读取到的 username 会变成 Guest
        }
        return SUCCESS;
    }
}

Action 必须实现 execute()方法 ,这个方法用于处理业务逻辑等。

public class XXAction extends ActionSupport {
    
    public String execute() {
        // 这里实现业务逻辑
        return INPUT;
    }
}    

3.2 Interceptor 拦截器

拦截器就跟它的名字一样,是用于拦截 Action 请求的调用,会在 Action 的逻辑执行之前或者之后进行触发。跟 Servlet 的过滤器 Filter 基本套路一样,都是用于一些同时的逻辑校验、日志记录、请求验证等等。

比如写一个认证拦截器:

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import org.apache.struts2.ServletActionContext;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class AuthInterceptor extends AbstractInterceptor {

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        HttpSession session = request.getSession(false);

        boolean isLoggedIn = (session != null && session.getAttribute("user") != null);

        if (isLoggedIn) {
            // 用户已经登录,继续执行Action
            return invocation.invoke();
        } else {
            // 用户没有登录,重定向到登录页面
            response.sendRedirect(request.getContextPath() + "/login.jsp");
            // 返回null以阻止进一步的处理
            return null; 
        }
    }
}

然后在 struts.xml 注册下拦截器:

<struts>
    <package name="default" extends="struts-default">
        <interceptors>
            <interceptor name="auth" class="com.suny.struts2demo.interceptor.AuthInterceptor"/>
            <interceptor-stack name="authStack">
                <interceptor-ref name="defaultStack"/>
                <interceptor-ref name="auth"/>
            </interceptor-stack>
        </interceptors>
        
        <default-interceptor-ref name="authStack"/>
        
        .... action 配置
    </package>
</struts>

3.3 Result 结果

上面的 Action 实现的方法 execute() 固定返回一个 String , 这个String 一般比较常用的有下面几种,都是定义在接口里面的:

public interface Action {
    String SUCCESS = "success";
    String NONE = "none";
    String ERROR = "error";
    String INPUT = "input";
    String LOGIN = "login";

    String execute() throws Exception;
}

  • success 是默认值,表示 Action 执行成功。

  • error 执行过程中遇到错误,通常需要跳转到错误页面。

  • input 是表单验证失败,需要重新回到表单页面。

  • login 是用户需要登录的情况,跳转到登录页面去。

  • none 表示不需要处理。

  • 也可以返回任意的字符串。

3.4 ValueStack 值栈

我的理解是 ValueStack 就像一个全局的保存数据的容器,你可以往里面扔数据,在整个请求处理过程中都是可以直接进行获取里面的数据。

比如可以在 Action 中添加数据到 ValueStack 中,然后 JSP 中直接获取:

public String execute() {
   ActionContext.getContext().getValueStack().push(dataObject);
   return SUCCESS;
 }

JSP 中直接获取:

<s:property value="dataObject"/>

当然 valueStack 的功能也远远不至于这些,它是整个框架中的核心概念,需要花时间去消化。

3.5 OGNL

OGNL 就是 Object-Graph Navigation Language 的缩写,翻译过来就是表达式语言。 在写 JSP 页面的时候已经大量的使用这种语法。比如:

  • %{username} 就是访问 ValueStack 中的 username 属性
  • %{#session.user.name} 就是访问session中user对象的name属性

3.6 标签库

Struts 2 提供了强大的标签库,可以简化在 JSP 页面中与后端数据的交互流程。

常用的标签:

  • <s:property> 用来显示ValueStack中的值。
  • <s:form> 用来创建HTML表单。
  • <s:iterator> 用来遍历集合。