Struts2初步使用

struts是一个Java中基于MVC设计模式的WEB应用程序框架。在吸收struts1与webwork的基础上发展而来的新框架。其工作原理如下图所示:

  1. HTTP请求到达后首先经过ActionContextCleanUp过滤器以消除属性,以延长Action中属性的生命周期。之后再经过如SiteMesh等其他过滤器后,请求传递给StrutsPrepareAndExecuteFilter核心控制过滤器
  2. StrutsPrepareAndExecuteFilter通过ActionMapper映射器确定调用哪个Action,再将控制权转移给ActionProxy代理
  3. ActionProxy调用配置管理器ConfigurationManager从配置文件struts.xml中读取配置信息,创建ActionInvocation对象
  4. ActionInvocation依次通过拦截器链后再调用Action,根据Action返回的结果字符串查找对应的Result
  5. Result调用视图模板,再以相反的顺序执行拦截器链,返回HttpServlet响应
  6. HTTP响应以相反的顺序穿过Struts以及用户自定义的过滤器,将结果返回给客户端。

创建一个struts项目

新建一个Java web项目后首先引入所需的jar包,从struts官网https://struts.apache.org/download.cgi下载所需要的依赖包,选择最新版本2.5.22的All dependencies包,解压得到多个jar包,从其中选择如下几个必要的拷贝到项目lib文件夹下,并添加到依赖。项目的目录结构如下

接着在web.xml文件中配置struts2过滤器StrutsPrepareAndExecuteFilter,对所有的路径进行过滤

<?xml version="1.0" encoding="UTF-8"?>
<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>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

接着定义Action类,在src目录下新建action.FirstAction,继承自ActionSupport类,

package action;

import com.opensymphony.xwork2.ActionSupport;

public class FirstAction extends ActionSupport {
    @Override
    public String execute() throws Exception {
        System.out.println("Action执行");
        return SUCCESS;                        //返回字符串SUCCESS
    }
}

在src目录下创建struts.xml文件,该文件是struts2的核心配置文件,完成对action的配置,以及对应的result定义等功能。

<package>为包,一个package可以包含多个action,其name属性为包名,extends属性为继承的包名,namespace为命名空间,通过url层级访问action时需要加上命名空间,例如namespace为/download,访问时url为http://localhost:8080:projectname/download/xxx,若为"/"代表根命名空间,可以省略。

<action>的name属性为访问的url路径,例如这里为/firstaction,class为对应的Java类。

<result>定义返回的结果集页面,根据不同的name属性返回不同的界面,name属性默认为SUCCESS,因此这里默认返回界面success.jsp

<constant>标签可以键值对的方式定义常量,name为key,value为值

此外可以使用<include>标签引入其他struts的xml配置文件

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
    <package name="default" namespace="/" extends="struts-default">
        <action name="firstaction" class="action.FirstAction">
            <result>/success.jsp</result>
        </action>
    </package>
    <constant name="struts.url.http.port" value="8080"></constant>
</struts>

启动项目访问firstaction,在控制台输出“Action执行”并且跳转到success.jsp页面,这样一个基本的struts项目便运行了起来。

Action

访问Servlet API

Http请求来到通过过滤器来到action,执行操作后在返回结果,那么如何获取Request与Response对象呢?Struts提供了三种方法

第一种是通过实现相应的Aware接口,在接口的setXxx()方法中获取request、response和context对象。进而通过各自对应的方法设置和获取属性。这种方法与Servlet API严重耦合,不推荐使用

public class FirstAction extends ActionSupport
        implements ServletRequestAware, ServletResponseAware, ServletContextAware {        //实现相应接口
    HttpServletRequest request;
    HttpServletResponse response;
    ServletContext application;

    @Override
    public void setServletRequest(HttpServletRequest httpServletRequest) {
        this.request=httpServletRequest;        //获取request对象
    }

    @Override
    public void setServletResponse(HttpServletResponse httpServletResponse) {
        this.response=httpServletResponse;      //获取response对象
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.application=servletContext;        //获取application对象
    }

    @Override
    public String execute() throws Exception {
        request.setAttribute("key1","value1");                //设置属性
        System.out.println(request.getAttribute("key1"));     //获取属性
        return SUCCESS;
    }
}

第二种方法是通过ServletActionContext类中的静态方法getXxx(),得到真正的Servlet相关Api。之后再以request、session、application对象的setAttribute、getAttribute方法设置和获取属性

HttpServletRequest request= ServletActionContext.getRequest();  //获取request对象
HttpSession session=request.getSession();                       //获取session对象
ServletContext context=ServletActionContext.getServletContext();//获取全局application对象
session.setAttribute("key3","value3");                          //设置属性值
System.out.println(session.getAttribute("key3"));               //获取属性值

第三种是通过ActionContext类访问Servlet API,Struts2将Servlet对象重新使用Map集合进行了封装,可以直接通过Map集合操作application、session、request对象中的共享数据,存取值比较方便,是推荐使用的一种方式。通过ActionContext对象可以直接调用get/put方法对request中保存的对象进行获取和设置。通过getApplication()、getSession()、getParameters()可获取application对象、session对象与request请求中的parameter参数。由于这种方法获取的不是真正的Servlet API对象,其返回的数据是Map类型,因此可以通过Map的put/get方法对数据进行设置和获取。

        ActionContext actionContext=ActionContext.getContext();     //创建Context对象

        actionContext.put("key1","value1");                 //向request中存储对象
        System.out.println(actionContext.get("key1"));      //获取request中存储的信息

        Map application=actionContext.getApplication();     //获取application
        application.put("key2","value2");                   //向application对象存对象
        System.out.println(application.get("key2"));        //从application中取出对象

访问action的url顺序

由于struts的package可以嵌套,所以访问的url路径也可以有多层,例如一个package1下有一个子包subpackage,其中包含firstaction,则其访问路径为http://localhost:8080/Struts2Demo/package1/subpackage/firstaction。如果在subpackage中没有找到firstaction,则会退回到上一级package1中查找,如果仍没有再返回上一级根目录下查找,如果仍没有找到才会报错404。

动态调用action的方法

如果希望针对不同的url调用相同的action中的不同方法,则需要动态调用。第一种可以在<action>标签通过method属性指定调用action类中的哪个方法。如下所示,当访问url为loginaction时,struts会执行action.FirstAction中的login()方法,并且在返回SUCCESS后跳转到login.jsp页面

        <action name="loginaction" class="action.FirstAction" method="login">
            <result>/login.jsp</result>
        </action>

但是如果每一个url都要定义一个action,太过重复和麻烦,可以使用通配符的形式根据不同的url定义不同的调用方法。例如定义action的name为"hello_*"用*代表通配符并用_和其他的url隔开,在method中用{1}代表通配符第一个位置。当我们访问的url为hello_login时,{1}代表login,则对应执行FirstAction中的login方法,login()返回字符串为login,在<result>中找到name为login的标签,返回/login.jsp页面。当访问hello_logout时,就会执行logout(),并且返回/logout.jsp。可见根据传入的不同url实现了动态调用action与动态返回页面的功能。

    <package name="default" namespace="/" extends="struts-default">
        <global-allowed-methods>regex:.*</global-allowed-methods>
        <action name="hello_*" method="{1}" class="action.FirstAction">
            <result name="{1}">/{1}.jsp</result>
        </action>
    </package>
public class FirstAction extends ActionSupport{
    public String login(){
        return "login";
    }

    public String logout(){
        return "logout";
    }
}

默认Action

通过default-action-ref标签可以指定package中默认访问的路径,即除了已定义的正常路径之外的其他请求都会跳转到该action。例如定义默认action为error,在action为error中定义跳转到error.jsp页面

<package name="default" namespace="/" extends="struts-default">
        <default-action-ref name="error"></default-action-ref>
        <action name="error">
            <result>/error.jsp</result>
        </action>
......
</packge>

添加url后缀

action的默认后缀为.action,在访问url时这个后缀可写可不写。在struts配置文件中,通过常量struts.action.extension为访问页面添加后缀,例如下面设置后缀为html,则所有的url结尾必须加上.html才能正常访问。

    <constant name="struts.action.extension" value="html"></constant>

接收参数

除了使用request接收请求中的参数外,还可以通过如下三种方法接收前端页面中的参数

第一种是直接通过Action属性来接收。在Action类中定义属性变量并实现其get/set方法,然后即可在action方法中直接使用属性变量。例如前端表单提交有name为username、password两个参数,在LoginAction中定义相应的变量以及get/set方法,然后在login()方法中就可以直接得到两个参数值了。

  <form action="login.action" method="post">
    姓名:<input type="text" name="username">
    密码:<input type="password" name="password">
    <input type="submit" value="提交">
  </form>
public class LoginAction extends ActionSupport {
    private String username;
    private String password;

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

    public String login(){
        System.out.println(username+":"+password);      //直接通过Action属性获取参数
        return SUCCESS;
    }
}

直接将属性放在action类中不利于对象的封装和管理,第二种方法DomainModel就是采用对象来接收参数。新建一个JavaBean类User用于储存username、password属性,然后在action中实例化一个user对象并定义set/get方法。在前端表单中设置属性name时添加对象名user,即为user.username、user.password。这样属性传到action文件中会自动完成对象赋值,可以直接使用user对象。

//JavaBean类User
public class User {
    private String username;
    private String password;

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

//Action中使用user对象接收参数
public class LoginAction extends ActionSupport {
    private User user;        //新建user对象

    public User getUser() {    //设置get方法
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String login(){            //通过User对象获取参数
        System.out.println(user.getUsername()+":"+user.getPassword());      
        return SUCCESS;
    }
}

  <form action="login.action" method="post">
    姓名:<input type="text" name="user.username">    <!--通过user对象传递属性-->
    密码:<input type="password" name="user.password">
    <input type="submit" value="提交">
  </form>

第三种方法是使用ModelDriven接口,Action类实现ModelDriven接口并实现其getModel()方法获取user对象。这样就不用定义user对象的get/set方法,而且前端页面表单属性不必添加user对象前缀,降低了耦合性,推荐使用这种方法。

public class LoginAction extends ActionSupport implements ModelDriven<User> {    //实现接口
    private User user=new User();            //需要实例化user对象

    @Override
    public User getModel() {                //实现接口方法
        return user;
    }

    public String login(){                    //可以使用user对象获取参数
        System.out.println(user.getUsername()+":"+user.getPassword());      
        return SUCCESS;
    }
}
  <form action="login.action" method="post">
    姓名:<input type="text" name="username">    <!--不必添加user对象前缀-->
    密码:<input type="password" name="password">
    <input type="submit" value="提交">
  </form>

Result

在<result>标签会根据action返回的不同String字符串去匹配不同的name属性,从而执行不同的结果集,这样做更有利于框架模块之间的分离。之前返回SUCCESS代表字符串"success",struts还有其他默认字符串:

当前端传递的参数与action中接收的变量不能匹配,action会自动返回INPUT,也可以手动返回INPUT。例如对接收到username进行判断,若为空则添加FiledError并返回INPUT。可以在前端页面通过struts-tags显示fielderro标签,当action抛出FieldError并返回INPUT再次跳转回login.jsp时,fielderror标签就会显示FieldError中的错误信息。

public class LoginAction extends ActionSupport implements ModelDriven<User> {
    private User user=new User();

    @Override
    public User getModel() {
        return user;
    }

    public String login(){
        if (user.getUsername()==null||user.getUsername().isEmpty()) {
            this.addFieldError("username", "用户名不能为空");      //添加错误信息
            return INPUT;
        }
        System.out.println(user.getUsername()+":"+user.getPassword());  
        return SUCCESS;
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>                       <!--引入struts-tags标签-->
<html>
  <head>
    <title>登录界面</title>
  </head>
  <body>
  <form action="login.action" method="post">
    姓名:<input type="text" name="username"><s:fielderror name="username"/>    <!--错误提示-->
    密码:<input type="password" name="password">
    <input type="submit" value="提交">
  </form>
  </body>
</html>

如下所示,struts-tags显示提示信息:

根据<result>标签的位置可以将result视图分为局部结果和全局结果。如果<result>直接定义在<global-result>标签内,则其为全局结果。如果<result>定义在<action>内,则其为局部结果。

<package>    
    <global-results>
        <result name="404error">/404.jsp</result>
    </global-results>
    ......
</package>

<result>的属性type可以指定返回结果的类型,默认为JSP,还可以使用其他模板引擎如Valocity、FreeMarker,还支持重定向redirect、纯文本plaintext等。

拦截器

在struts原理图中可以看到拦截器(Interceptor)作用于请求到达Action之前,并且在返回Result结果后再反向经过拦截器。多个拦截器构成了拦截器栈,拦截器栈的执行是有顺序的,就像栈一样先进后出。所以我们可以使用拦截器对请求到达action之前进行预处理操作,并且在返回结果前执行进一步操作。

 定义拦截器

第一种方法是实现Interceptor接口。该接口有三个方法:

  • init()初始化拦截器所需要的资源
  • destroy()释放分配的资源
  • String intercept(ActionInvocation ai),通过ActionInvocation获取action状态,完成拦截器主要操作,并且返回Result字符串

第二种方法是继承AbstractInterceptor类,该类提供了init()与destroy()方法的空实现,所以我们只需要实现intercept()方法即可,因此这种方法较为常用。如下所示定义一个拦截器对用户登录进行验证,如果session中username不为空,则拦截器放行到下一层,否则返回字符串“login”,跳转到登录界面。

public class AuthLog extends AbstractInterceptor {
    @Override
    public String intercept(ActionInvocation actionInvocation) throws Exception {
        System.out.println("Action执行之前");
        ActionContext actionContext=ActionContext.getContext();
        Map session =actionContext.getSession();
        if (session.get("username")!=null){
            //执行下一个拦截器,若为最后一个则执行目标Action,然后返回结果
            String result=actionInvocation.invoke();
            System.out.println("执行Action之后");
            //将结果返回上一层
            return result;
        }else {
            return "login";
        }
    }
}

注册拦截器

首先在struts.xml文件中注册拦截器,然后再要使用的action中引用拦截器。也可以将多个拦截器组合为一个拦截器栈。

如下所示首先定义一个拦截器authlog,指向AuthLog类。之后和默认拦截器组合为myStack拦截器栈。之后定义action为personalPage并为其添加拦截器myStack。当用户访问该action时,首先会经过myStack拦截器栈中的defaultStack,然后再经过authlog拦截器,在其中完成登陆验证,如果验证通过则返回success.jsp,否则返回login.jsp。

        <interceptors>
            <interceptor name="authlog" class="interceptor.AuthLog"></interceptor>
            <!--定义拦截器栈-->
            <interceptor-stack name="myStack">
            <interceptor-ref name="defaultStack"></interceptor-ref>     <!--引用默认拦截器-->
                <interceptor-ref name="authlog"></interceptor-ref>
            </interceptor-stack>
        </interceptors>

        <action name="personalPage">
            <result>/success.jsp</result>
            <result name="login">/login.jsp</result>
            <interceptor-ref name="myStack"></interceptor-ref>          <!--引用拦截器-->
        </action>

内置拦截器

struts中内置了许多拦截器来实现其功能:

  • params拦截器将请求的params参数传给action属性,之前使用action直接接收params属性就是通过这个拦截器实现的
  • servletConfig拦截器将servlet的接口注入action,从而可以在action中使用request、session等servlet对象
  • fileUpload拦截器对文件上传提供了支持,将文件和元数据设置到action属性
  • exception拦截器捕获异常并映射到用户自定义页面
  • validation拦截器通过验证框架进行数据验证

这些拦截器都在struts-core.jar包中struts-default.xml文件中进行了注册,并且集中打包为默认拦截器栈<interceptor-stack name="defaultStack">。并且设置为默认引用<default-interceptor-ref name="defaultStack"/>,即如果用户没有自已引用拦截器的话就会默认引用拦截器栈defaultStack。但是如果用户自定义了拦截器,就不会再引用defaultStack,因此需要手动引用。

发布了124 篇原创文章 · 获赞 65 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/theVicTory/article/details/104816050