欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

struts2学习

程序员文章站 2022-07-04 19:35:23
...

struts安装、配置

安装

下载地址
https://struts.apache.org

struts-2.3.37-all.zip 资源文件
struts2学习

  • apps :包含基于struts2的实例应用
  • docs :struts2的官方文档,教程,API等文档
  • lib:struts2框架的核心类库,以及第三方插件类库
  • src::struts2框架的全部源代码

其中lib中包含的struts2运行的必需基础包

  • struts2-core-2.3.37.jar:struts2的核心类库
  • freemarker-2.3.28.jar:struts2的UI标签模板
  • commons-logging-1.1.3.jar:日志包
  • ognl-3.0.21.jar:ognl表达式包
  • xwork-core-2.3.37.jar:xwork类库,struts2在其基础上构建
  • commons-fileupload-1.4.jar:文件上传组件

配置

  • 在web.xml中配置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>

struts2过滤器的配置是固定的

  • 在src目录下创建struts2核心配置文件struts.xml
<?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="helloDemo" extends="struts-default" namespace="/">
        <action name="Hello" class="cn.edu.stu.action.Hello">
            <result name="login">/login.jsp</result>
        </action>
    </package>
</struts>

struts.xml的名称和位置都是固定的,其中的标签含义如下

  1. package:对应包配置
    包含的属性:
    name:标识该package
    extends :继承其他包,这样可以继承其他包下的action定义。通常设置为struts-default
    namespace:命名空间,默认为“/”

  2. action:对应一个action的URL
    对应属性:
    name:指定action访问时的名称:与package中的namespace组成访问连接
    class:对应action的路径
    method:指定请求action时调用的方法

  3. result:根据action方法返回结果,配置到不同的路径
    属性:
    name:对应action调用方法的返回值
    type:配置到路径的方式
    type路径有两种方式:
    到页面
    dispatch:转发(默认)
    redirect:重定向
    到action
    redirectAction:重定向
    chain:转发(因为缓存问题,一般不用)
    注意:result标签分为2中:全局和局部
    struts2学习
    global-results 标签内的result为全局
    优先级:局部>全局

  • 加载子类配置文件
    在struts.xml文件中根元素:struts中添加
<include file="配置文件路径名"></include>
  • Struts2中常量配置
    在struts中大部分常量在配置文件中已经配好了
  1. 默认常量定义文件位置
    struts2学习

  2. 修改
    可以在struts.xml中的

<struts>
	<constant name="struts.i18n.encoding" value="UTF-8"></constant>
</struts>

name:指定常量名
value:指定常量值

Struts2工作流程

工作流程图

struts2学习

源码流程

  1. 首先struts过滤器继承Filter
    struts2学习
  2. 在web.xml中,配置了拦截所有请求,在拦截请求后会加载配置文件进行过滤
    struts2学习

struts2中action实现的三种方式

  • 普通类
    不需要继承或实现其他类。但是需要添加一个execute方法
    public类型
    String 返回类型
public class Hello{
    public String execute(){
        return "login";
    }
}
  • 实现Action接口
public class Hello implements Action {
    @Override
    public String execute() throws Exception {
        return null;
    }

}

Action接口提供了5个常量
SUCCESS:success 代表成功
NONE:none该表页面不跳转
ERROR:error跳转到错误页面
INPUT:input 数据校验的时候跳转的路径
LOGIN:login跳转到登陆页面

  • 继承ActionSupport类(最常用)
    ActionSupport是实现了Action接口的子类,功能更强大

  • action和servlet比较
    servlet是单例的,第一次访问会创建,后面不会创建
    action是多例的,每次访问都会创建

action中方法的访问

通过action标签中的method方法

通过通配符方法

在action标签中的name中使用* ,同时在method中使用占位符来设置来任意匹配字符串

<action name="hello_*" class="cn.edu.stu.action.Hello" method="{1}">
    <result name="success">/login.jsp</result>
</action>

struts2学习
login对应了action的方法名

动态访问实现

  1. 在struts.xml文件中配置:开启动态访问
<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant>
  1. action配置
<action name="Hello" class="cn.edu.stu.action.Hello">
            <result name="success">/login.jsp</result>
 </action>
  1. 前端页面
<a href="${pageContext.request.contextPath}/Hello!login">登陆</a>

在路径中在action的name后面需要添加一个!在加上方法名

方法中方法返回值

有返回值,返回值类型必须是String
没有返回值
可以写成void(不推荐)
写成String类型,返回为none

action获取请求数据

首先是前端

<form action="${pageContext.request.contextPath}/login_login" method="post">
  用户名:<input type="text" name="username"/><br/>
  密  码:<input type="password" name="password"/>
  <input type="submit" value="提交"/>
</form>

ActionContext

public String login(){

    //得到参数
    ActionContext context = ActionContext.getContext();
    Map<String, Object> parameters = context.getParameters();
    User user=new User();

    String username=(String) ((Object[])parameters.get("username"))[0];
    String password=(String) ((Object[])parameters.get("password"))[0];

    user.setUsername(username);
    user.setPassword(password);
    System.out.println(user);
    return LOGIN;
}

注意:ActionContext.getContext().gatParameters()得到的是一个map数组。
其中map值是一个object对象(更准确的说是一个object数组)

ServletActionContext(使用最多)

public String login(){

    //得到参数
    HttpServletRequest request = ServletActionContext.getRequest();
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    User user=new User();

    user.setUsername(username);
    user.setPassword(password);
    System.out.println(user);
    return LOGIN;
}

实现ServletRequestAware

通过实现ServletRequestAware接口,从写setServletRequest()方法,得到request对象,来得到参数

public class LoginAction extends ActionSupport implements ServletRequestAware {

    private HttpServletRequest request;

    @Override
    public void setServletRequest(HttpServletRequest request) {
        this.request=request;
    }

    public String login(){
        //得到参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        User user=new User();

        user.setUsername(username);
        user.setPassword(password);
        System.out.println(user);
        return LOGIN;
    }
}

封装表单数据到实体对象中

原始方式

先得到参数值,再赋值给一个实体对象

属性封装(表达式封装)

  1. 在对应的action中,声明一个实体类(User user;)
  2. 添加该实体类的set,get方法
  3. 表单中的name中使用设置表达式:user.属性名
public class LoginAction extends ActionSupport {
    private User user;
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public String login(){
        System.out.println(user);
        return LOGIN;
    }
}

前端jsp页面

<form action="${pageContext.request.contextPath}/login_login" method="post">
  用户名:<input type="text" name="user.username"/><br/>
  密  码:<input type="password" name="user.password"/>
  <input type="submit" value="提交"/>
</form>

在action中定义的user,在前端表单中的name属性中使用

模型驱动封装(推荐使用)

  1. 首先在对应的action中实现ModelDriven接口
  2. 在action中创建一个是实体类对象
  3. 在ModelDriven的一个方法 getModel中返回该实体类对象即可
public class LoginAction extends ActionSupport implements ModelDriven<User> {
    private User user=new User();
    @Override
    public User getModel() {
        return user;
    }
    public String login(){
        System.out.println(user);
        return LOGIN;
    }
}

注意:属性封装和模型驱动封装不能在一个action中对一个表单进行使用,如果同时使用,只会执行驱动封装

表达式与模型驱动的比较

模型驱动封装只能把数据封装到一个实体类对象里面
表达式封装可以把数据封装到不同的实体类中

封装表单数据到List、Map

List:

  1. 在对应的action中声明一个list,并添加对应的set和get方法
    private Listlist
  2. 在前端form中的input中的name中设置表达式:list[0].username
public class LoginAction extends ActionSupport {
    private List<User> userList=new ArrayList<>();
    public List<User> getUserList() {
        return userList;
    }
    public void setUserList(List<User> userList) {
        this.userList = userList;
    }
    public String login(){
        System.out.println(userList);
        return LOGIN;
    }
}
<form action="${pageContext.request.contextPath}/login_login" method="post">
    用户1<br/>
    用户名:<input type="text" name="userList[0].username"/><br/>
    密  码:<input type="password" name="userList[0].password"/><br/>
    用户2<br/>
    用户名:<input type="text" name="userList[1].username"/><br/>
    密  码:<input type="password" name="userList[1].password"/><br/>
    用户3<br/>
    用户名:<input type="text" name="userList[2].username"/><br/>
    密  码:<input type="password" name="userList[2].password"/><br/>
    <input type="submit" value="提交"/>
  </form>

map

  1. 在对应的action中声明一个map,并添加对应的set和get方法
    private Map<String,User>map
  2. 在前端form中的input中的name中设置表达式:map[“one”].username
public class LoginAction extends ActionSupport {
    private Map<String,User> userMap=new HashMap<>();
    public Map<String, User> getUserMap() {
        return userMap;
    }
    public void setUserMap(Map<String, User> userMap) {
        this.userMap = userMap;
    }
    public String login(){
        System.out.println(userMap);
        return LOGIN;
    }
}
<form action="${pageContext.request.contextPath}/login_login" method="post">
    用户1<br/>
    用户名:<input type="text" name="userMap['one'].username"/><br/>
    密  码:<input type="password" name="userMap['one'].password"/><br/>
    用户2<br/>
    用户名:<input type="text" name="userMap['two'].username"/><br/>
    密  码:<input type="password" name="userMap['two'].password"/><br/>
    用户3<br/>
    用户名:<input type="text" name="userMap['three'].username"/><br/>
    密  码:<input type="password" name="userMap['three'].password"/><br/>
    <input type="submit" value="提交"/>
  </form>

在struts2中使用ognl

什么是ognl

OGNL是Object-Graph Navigation Language的缩写,对象图导航语言,它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。

ognl的作用

Struts2默认的表达式语言就是OGNL,它具有以下特点:

  • 支持对象方法调用。例如: objName methodName()。
  • 支持类静态方法调用和值访问,表达式的格式为@[类全名(包括包路径)]@[方法名|值名]。
    例如: @java lang aaa@qq.com(foo %s’, ‘bar’)。
  • 支持赋值 操作和表达式串联。
    例如: price=100, discount=0.8, calculatePrice(), 在方法中进行乘法计算会返回80。
  • 访问OGNL. 上下文(OGNL context)和ActionContext。
  • 操作集合对象。

ognl三要素

OGNL的操作实际上就是围绕着OGNL结构的三个要素而进行的,分别是表达式(Expression)、根对象(Root Object)、上下文环境(Context) ,下面分别讲解这三个要素,具体如下。

  1. 表达式
    表达式是整个OGNL的核心,OGNL会根据表达式去对象中取值。所有OGNL操作都是针对表达式解析后进行的。它表明了此次OGNL操作要“做什么”。表达式就是一一个带有语法含义的字符串,这个字符串规定了操作的类型和操作的内容。OGNL支持大量的表达式语法,不仅支持这种“链式”对象访问路径,还支持在表达式中进行简单的计算。
  2. 根对象(Root)
    Root对象可以理解为OGNL的操作对象,表达式规定了“做什么”,而Root对象则规定了“对谁操作”。OGNL称为对象图导航语言,所谓对象图,即以任意一个对象为根,通过OGNL可以访问与这个对象关联的其它对象。
  3. Context 对象
    实际上OGNL的取值还需要一个上下文环境。设置了Root对象,OGNL可以对Root对象进行取值或写值等操作,Root 对象所在环境就是OGNL的上下文环境(Context) 。上下文环境规定了OGNL的操作“在哪里进行”。上下文环境Context是一一个Map类型的对象,在表达式中访问Context中的对象,需要使用“#”号加上对象名称,即“#对象名称”的形式。

实例1:字符串长度

@Test
public void func() throws OgnlException {
    OgnlContext context=new OgnlContext();
    Object value = Ognl.getValue("'12345'.length()", context, context.getRoot());
    System.out.println(value);
}

调用字符串对象的length()方法

实例2:

ognl还可以访问对象的静态方法,语法格式
@类的全路径名@静态方法
@类的全路径名@属性名

@Test
public void func() throws OgnlException {
    OgnlContext context=new OgnlContext();
    Object value = Ognl.getValue("@aaa@qq.com()", context, context.getRoot());
    System.out.println(value);
}

实例3:获取值

@Test
public void func() throws OgnlException {
    OgnlContext context=new OgnlContext();

    User user=new User();
    user.setUsername("张三");
    user.setPassword("123");
    context.setRoot(user);
    String username = (String) Ognl.getValue("username", context, context.getRoot());
    System.out.println(username);
}

在struts2标签中使用ognl

  1. 首先需要在前端jsp页面引入标签
    <%@ taglib prefix=“s” uri="/struts-tags" %>

  2. 使用ognl
    <s:property value=""/>
    ognl需要配合struts2标签一起使用,其中value中存放ognl标签

OGNL中#和%的作用

#:用于从context中取数据
%:在struts2中的表单标签中,无法直接使用ognl表达式,需要将其阔在%{ }中

值栈

什么是值栈

ValueStack是Struts 的一一个接口,字面意义为值栈,OgnlValueStack 是ValueStack 的实现类,客户端发起一个请求struts2 架构会创建一个action 实例同时创建一- 个OgnlValueStack 值栈实例,OgnlValueStack贯穿整个Action 的生命周期,struts2 中使用OGNL将请求Action的参数封装为对象存储到值栈中,并通过OGNL表达式读取值栈中的对象属性值。

值栈的内部结构

struts2学习
root继承了ArryList,实现压栈和出栈功能。存储action实例和请求参数。
context即ognl上下文对象,其中存储了一些引用:request、session、application等,其中最重要的是其Root为CompoundRoot.
context示例图:
struts2学习

ActionContext与ValueStack的关系

  • 在創建ActionContext的吋候創建Valuestack的対象,將Valuestack対象給ActionContext.
  • ActionContext 中有一-个Valuestack的引用. Valuestack 中也有- -个ActionContext的引用.

使用struts2标签<s:debug>标签可以查看值栈结构
其中栈顶元素是对应的action对象的引用

获取值栈对象

  1. 通过ActionContext
ValueStack stack= ActionContext.getContext().getValueStack();
  1. 通过request
ValueStack stack= (ValueStack) ServletActionContext.getRequest()
               .getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);

向值栈中存放数据

  1. 使用值栈对象的set(String,value)方法
  2. 使用值栈对象的push()方法
  3. 在对应的action内创建一个变量,并添加get方法
public class LoginAction extends ActionSupport {
    //方法三:通过在action中添加一个变量
    private String username="wangwu";

    public String getUsername() {
        return username;
    }
    public String login(){
        ValueStack valueStack = ActionContext.getContext().getValueStack();
        //方法一:通过set()
        valueStack.set("username","zhangsan");
        //方法二:通过push()
        valueStack.push("lisi");
        return LOGIN;
    }
}

前端测试代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--添加struts标签头--%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
    <title>测试</title>
</head>
<body>
<%--s:debug 标签可以测试值栈--%>
    <s:debug/>
</body>
</html>

测试图:
struts2学习

从值栈中获取值

  • jsp中struts2标签+ognl表达式
    <s:property value=""/>
  • 取值如果是字符串
    <s:property value=“字符串变量名”/>
  • 实体类
    <s:property value=“实体类变量.属性”/>

list

  1. 通过数组下标获取对象
    <s:property value=“list[0].属性”/>
    ……

  2. 通过struts2的标签遍历
    <s:iterator value=”list”>
    <s:property value=“属性”/>
    </s:iterator >
    在循环体内,已经确定是对应数组值了,所以在取得属性值时,可以省略list[下标]

  3. 使用struts2标签和#

struts2学习

值栈中通过set和push存放值,取出方式

  • set(key,value)
    <s:property value=”key”>
  • push(“”);
    push()存放数据,没有名称,会存放在top数组
    取出方式
    <s:property value=”[0].top”>

使用EL表达式也可以取出值栈的值

使用EL取出域对象中的方式:
getAttribute();
在struts2中对getAttribute方法进行了增强:先从域对象中取出,如果域对象不存在,再从值栈中取。

拦截器

拦截器是什么

拦截器,在action对象创建之后,action的方法执行之前。struts2里面封装了很多的功能,有很多拦截器,不是每次这些拦截器都执行,每次执行默认的拦截器

默认拦截器文件位置

struts2学习

拦截器原理

  • AOP思想(面向切面编程)
    在已有的基础功能基础之上,扩展功能,不通过修改源代码的方式来扩展功能(通过接口编程:修改配置文件等)

  • 责任链模式
    要执行多个操作,例如:增加、修改、删除三个操作
    首先执行增加操作,完成后,需要一个类似放行的操作;然后依次进行。和Java web中过滤器中的过滤链放行类似的思想

AOP和责任链在拦截器中应用

  • 拦截器在action对象创建之后,action的方法执行之前执行

  • 在action方法执行之前执行默认拦截器,执行过程使用aop思想,在action没有直接调用拦截器的方法,使用配置文件方式进行操作

  • 在执行拦截器时候,执行很多的拦截器,这个过程使用责任链模式
    假如执行三个拦截器,执行拦截器1,执行拦截器1之后做放行操作,执行拦截器2,执行拦截器2之后做放行,执行拦截器3,执行拦截器3之后放行,执行action的方法

查看源码

  1. 执行action
    struts2学习
  2. 创建action对象,使用动态代理方式
    struts2学习
  3. 执行action的方法
    struts2学习
  4. 执行很多的拦截器,遍历执行
    struts2学习
    类似于放行的操作的方法
    struts2学习

过滤器与拦截器的区别

过滤器理论上可以拦截任意资源
拦截器只能拦截action

自定义拦截器

拦截器接口类:Interceptor

该接口有三个方法

  • void destroy();//销毁操作
  • void init();//初始化操作
  • String intercept(ActionInvocation var1) throws Exception;//拦截逻辑操作

在开发中一般继承MethodFilterInterceptor,只需要考虑拦截操作,并且该类可以在配置文件中设置不拦截某些方法

实际步骤

  1. 写一个类继承MethodFilterInterceptor
  2. 实现其中的doInterceptor()方法,完成拦截逻辑
  3. 在struts.xml文件中进行配置
    a) 在package中声明该拦截器
    b) 在action中使用该拦截器
    c) 需要再使用模式拦截器:否则不适用默认拦截器

继承MethodFilterInterceptor的拦截器可以指定不拦截一些方法
在使用拦截器时

<param name=”excludeMethods”>方法名</>
  • 代码实例
    action: LoginAction.java
    interceptor: CustomInterceptor.jvar
    jsp:login.jsp、welcome.jsp

  • 逻辑:
    首先从login.jsp登陆-》LoginAction调用login方法判断成功跳转到welcome页面,否则跳转到login页面
    这里需要演示拦截器:拦截login方法,和配置不拦截login方法

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--添加struts标签头--%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
    <title>登陆界面</title>
</head>
<body>
<h1>登陆</h1>
<form action="${pageContext.request.contextPath}/LoginAction?login" method="post">
    用户名:<input type="text" name="username"/><br/>
    密  码:<input type="password" name="password"/><br/>
    <input type="submit" value="提交"/>
</form>
</body>
</html>

welcome.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title>欢迎界面</title>
  </head>
  <body>
  <h1>欢迎登陆系统</h1>
  </form>
  </body>
</html>

LoginAction.java

public class LoginAction extends ActionSupport {


    public String login(){
        //得到request对象
        HttpServletRequest request = ServletActionContext.getRequest();
        String username = (String) request.getParameter("username");
        if (username.equals("user")) {
            //如果用户名为user,保存到session中
            request.getSession().setAttribute("username",username);
            //到列表页面
            return "welcome";
        }
        //返回登陆页面
        return LOGIN;
    }
}

CustomInterceptor.jvar

public class CustomInterceptor extends MethodFilterInterceptor {

    //拦截操作
    protected String doIntercept(ActionInvocation actionInvocation)
            throws Exception {
        System.out.println("CustomInterceptor……");
        HttpServletRequest request = ServletActionContext.getRequest();
        if (request.getSession().getAttribute("username")!=null) {
            //放行
            actionInvocation.invoke();
        }
        //用户名错误,到登陆界面去
        return "login";
    }
}

struts.xml

<?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="login" extends="struts-default" namespace="/">
        <!--声明自定义拦截器-->
        <interceptors>
            <interceptor name="CustomInterceptor" class="cn.edu.stu.interceptor.CustomInterceptor"></interceptor>
        </interceptors>
        <action name="LoginAction" class="cn.edu.stu.action.LoginAction" method="login">
            <!--使用自定义的拦截器-->

            <interceptor-ref name="CustomInterceptor">
                <!--
                    excludeMethods:设置不拦截的方法
                    includeMethods:设置拦截的方法
                -->
                <!--<param name="excludeMethods">login</param>-->
            </interceptor-ref>
            <!--使用默认拦截器-->
            <interceptor-ref name="defaultStack"></interceptor-ref>
            <result name="login">/login.jsp</result>
            <result name="welcome">/welcome.jsp</result>
        </action>
    </package>
</struts>