struts2框架学习

实现原理

先放一张很俗的框架图:

是不是看着很复杂?其实简单的讲这个古老的框架将我们平时使用的springMVC中的controller使用过滤器filter来实现了。这样讲可能比较空洞,那我们不用框架如何实现对httpRequest的接收处理和控制呢?
下面我们在传统的web项目中,使用filter和JAVA反射机制来模拟一下struts2对用户请求的处理流程:

这是简单实例的结构
这是简单实例的项目结构:
在这里插入图片描述

这是我们要做到的效果:
可见这样的实现方式,难怪我们在项目中找不到controller,控制层逻辑写在过滤器中了。
过滤器代码:

 /**
     *  拦截并处理action请求
     * @param req httpRequest
     * @param resp httpResponse
     * @param chain  FilterChain
     * @throws ServletException ServletException
     * @throws IOException IOException
     */
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //获取http请求地址
        HttpServletRequest request= (HttpServletRequest) req;
        String servletPath=request.getServletPath();
       if(servletPath.equals("/save.action")){
           //获取参数
           Map<String, String[]> paramMap=request.getParameterMap();
           //由反射实例化PO并返回结果VO
           Class peopleClass = null;
           Object obj=null;
           try {
               peopleClass = Class.forName("po.People");
               obj= peopleClass.newInstance();
               //获得POJO所有属性
               Field[] fields=peopleClass.getDeclaredFields();
               for (Field field:fields){
                   String filedName=field.getName();
                   String[] targetVal=paramMap.get(filedName);
                   //请求参数长度为1则 是key为sting value为string形式
                   if(targetVal.length==1){
                       //直接为VO属性赋值
                       field.setAccessible(true);
                       String filedType= field.getType().getSimpleName();
                       //匹配属性类型
                       if(filedType.equals("String")){
                           field.set(obj,targetVal[0]);
                       }
                       //其他类型。。也是这么判断赋值的
                   }
               }
           } catch (ClassNotFoundException e) {
               e.printStackTrace();
           } catch (IllegalAccessException e) {
               e.printStackTrace();
           } catch (InstantiationException e) {
               e.printStackTrace();
           }
           //将VO加入request域
           request.setAttribute("people",obj);
           //最后将返回结果到前端
           request.getRequestDispatcher("/WEB-INF/view/input.jsp").forward(req,resp);
       }else if(servletPath.equals("/input.action")) {
           System.out.println("拦截 input.action");
           request.getRequestDispatcher("/WEB-INF/view/input.jsp").forward(req,resp);
       }
        chain.doFilter(req, resp);
    }

POJO代码:在structs中每一个POJO都可以作为一个action类,这个下篇文章再接着说

package po;

public class People {
    private String name;
    private String  sex;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "people{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}

VIEW显示层 就是前端页面啦

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>input</title>
</head>
<body>
<form action="save.action">
    name:<input name="name" type="text" value="${people.name}">
    sex :<input name="sex" type="text" value="${people.sex}">
    <input type="submit">
</form>
<%=request%>:::<%=request.getAttribute("people")%>
</body>
</html>

那我们这么实现的方式对不对呢?下面搭建一个基于的struts2框架的项目看一看:

struts2项目搭建

:struts项目与传统的web项目相比,项目的结构并没有很多改变,就像加入了依赖一样。
如图:
在这里插入图片描述
和我们自己实现的相比:多了一堆jar包,和一个structs配置文件,少了我们自己用过滤器实现的控制器。
效果是相同的:
在这里插入图片描述
在这里插入图片描述
是不是很神奇?难道struts能无中生有么?都知道建国后不准成精,所以答案肯定是否定的!!!
那么,问题来了:How did it do that?
谜底当然从多出来的struts.xml文件中揭开啦!
下面贴上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>

    <constant name="struts.enable.DynamicMethodInvocation" value="false" />
    <constant name="struts.devMode" value="true" />
<!--
 package:包,struts-default 这个包在struts-default.xml文件中定义的,里面定义了一堆通用的过滤器
 ,和返回结果。
 name:必填属性,用于其他包引用当前包
extends:当前包继承哪个包,可以继承其中所有的配置,通常都继承struts-default
namespace 属性是可选的 类似于我们在springmvc中在controller类的类名上加了@requestMapping注解
 ,该属性默认值为‘/’,所以我们在jsp的a标签中虽然写的是action="save.action" 但我们浏览器访问时依然要写为:/save.action -->
    <package name="default" namespace="/at" extends="struts-default">
  <!--
     action:
      配置一个action: 一个struts2请求就是一个action
      name:对应一个struts2请求的名字(或者对应一个servletPath,但去除‘/’和拓展名),不包含拓展名
          :就例如在jsp的a标签中虽然写的是action="save.action" 那么我们在<action>标签中的name属性就写为save
     class:应写为我们自己定义的action类 默认值为:com.opensymphony.xwork2.ActionSupport
     method:我们定义的action类中在在本次请求中要执行的方法 默认值为execute
 -->
  <!--
  result:
         结果,表示action方法执行后可能返回的一个结果,所以一个action可能对应多个result节点
          即可以根据返回值返回到不同的页面。返回到不同页面的依据是result标签的name属性name:标识result节点
   		type:表示结果的类型,默认值为dispatcher(将请求转发到当前result对应的页面)
   -->
    <action name="input" class="com.opensymphony.xwork2.ActionSupport">
        <result>/WEB-INF/view/input.jsp</result>
    </action>
    <action name="save" class="po.People" method="save">
        <result name="people">/WEB-INF/view/input.jsp</result>
    </action>
    </package>
</struts>

这么直接看是不是有点难懂?那来拿这个配置文件和我们之前写的控制器做一个对比:

在这里插入图片描述
这样比较一下是不是很直观的明白了他们在代码中的作用了呢?哈哈没错就是配置框架写好的过滤器也就是控制器。告诉框架什么action请求对应什么action类,执行什么方法,返回结果在哪展示。
此时还有一个问题没有回答,也就是即使是我们自己写的控制器将vo返回到jsp时我们获取属性值还需要people.name这样,而struts2缺可以直接${name} 这是怎么回事呢?
这个问题我们先来玩个游戏,名字叫找不同:请看图:
在这里插入图片描述
请问这两张图有什么不一样的?
对对对!很聪明,获取到的request对象不一样!这就是这个问题答案的秘密所在,struts2的StrutsRequestWrapper类继承并重写了httpRequest的getAttribute(String key)方法:
我们看一下源码:

package org.apache.struts2.dispatcher;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.ValueStack;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.BooleanUtils;

public class StrutsRequestWrapper extends HttpServletRequestWrapper {
    private static final String REQUEST_WRAPPER_GET_ATTRIBUTE = "__requestWrapper.getAttribute";
    private final boolean disableRequestAttributeValueStackLookup;

    public StrutsRequestWrapper(HttpServletRequest req) {
        this(req, false);
    }

    public StrutsRequestWrapper(HttpServletRequest req, boolean disableRequestAttributeValueStackLookup) {
        super(req);
        this.disableRequestAttributeValueStackLookup = disableRequestAttributeValueStackLookup;
    }

    public Object getAttribute(String key) {
        if (key == null) {
            throw new NullPointerException("You must specify a key value");
        } else if (!this.disableRequestAttributeValueStackLookup && !key.startsWith("javax.servlet")) {
            ActionContext ctx = ActionContext.getContext();
            Object attribute = super.getAttribute(key);
            if (ctx != null && attribute == null) {
                boolean alreadyIn = BooleanUtils.isTrue((Boolean)ctx.get("__requestWrapper.getAttribute"));
                if (!alreadyIn && !key.contains("#")) {
                    try {
                        ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);
                        ValueStack stack = ctx.getValueStack();
                        if (stack != null) {
                        
                            attribute = stack.findValue(key);
                        }
                    } finally {
                        ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);
                    }
                }
            }

            return attribute;
        } else {
            return super.getAttribute(key);
        }
    }
}

重点在这里:
在这里插入图片描述
我们要获取的参数值是由struts2的值栈(valueStack)提供的关于栈值之后会介绍到

action概述

1.action:
action表示一个action请求

2.action类:
能够处理action请求的类:就比如我们上面写的people这个类,
》action类的属性名称必须遵守与javabean属性名相同的命名规则。
属性的类型必须可以是任意类型,从字符串到非字符串(基本数据类型)之间的数据转换可以自动发生。
》必须有一个不带参的构造器:struts是通过反射创建实例

》至少提供一个struts在执行这个action时调用的方法。

》同一个action类可以包含多个action方法。
struts2会为每一个http请求创建一个新的action实例,线程安全的。
(类比spring mvc也会为每一个http请求创建一个新的controller实例)

在action中访问web资源

1.什么是web资源?
HttpServletRequest ,HttpSession,ServletContext 等原生的Servlet API
2.为什么访问web资源?
B/S的应用的Controller中必然要访问web资源:向域中写属性,读写cookie,获取realPath…
3.如何访问?
1.Servlet API解耦的方式:只访问有限的Servlet API对象,且只能访问有限的方法(读取请求参数,读写域只对象的属性,使用session)

1.使用ActionContext
2.实现XxxAware接口

**

Servlet解耦的方式是怎样的呢?

**原来为了避免与ServletAPI耦合在一起,方便Action做单元测试,Struts2对HttpServletRequest , HttpSession和ServletContext进行了封装,构造了3个Map对象来代替这三个对象,以便在Action中可以直接使用HttpServletRequest,HttpServletSession,ServletContex对应的Map对象来保存和读取数据。而不是直接使用原生的Servlet对象。
我们用一个实例来说明:这三个map的使用:
1.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>

    <constant name="struts.enable.DynamicMethodInvocation" value="false" />
    <constant name="struts.devMode" value="true" />
<!--
 package:包,struts-default 这个包在struts-default.xml文件中定义的,里面定义了一堆通用的过滤器
 ,和返回结果。
    name:必填属性,用于其他包引用当前包
    extends:当前包继承哪个包,可以继承其中所有的配置,通常都继承struts-default
    namespace 属性是可选的 类似于我们在springmvc中在controller类的类名上加了@requestMapping注解
    ,该属性默认值为‘/’,所以我们在jsp的a标签中虽然写的是action="save.action" 但我们浏览器访问时依然要写
    为:/save.action
 -->
    <package name="default"  extends="struts-default">
        <!--
        action:
            配置一个action: 一个struts2请求就是一个action
            name:对应一个struts2请求的名字(或者对应一个servletPath,但去除‘/’和拓展名),不包含拓展名
            :就例如在jsp的a标签中虽然写的是action="save.action" 那么我们在<action>标签中的name属性就写为save
            class:应写为我们自己定义的action类 默认值为:com.opensymphony.xwork2.ActionSupport
            method:我们定义的action类中在在本次请求中要执行的方法 默认值为execute
        -->
        <!--
            result:结果,表示action方法执行后可能返回的一个结果,所以一个action可能对应多个result节点
            即可以根据返回值返回到不同的页面。返回到不同页面的依据是result标签的name属性
            name:标识result节点
            type:表示结果的类型,默认值为dispatcher(将请求转发到当前result对应的页面)
            -->
    <action name="input" class="com.opensymphony.xwork2.ActionSupport">
        <result>/WEB-INF/view/action-context.jsp</result>
    </action>
    <action name="actionContext" class="test.action.context.TestActionContext" method="execute">
        <result name="success">/WEB-INF/view/action-context.jsp</result>
    </action>

    </package>



</struts>

jsp内容:
index.jsp

<%@ page import="java.util.Date" %><%--
  Created by IntelliJ IDEA.
  User: zhangyx-v
  Date: 2020-9-30
  Time: 16:42
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>

  <body>
  <a href="actionContext.action?name=123">actionContext</a>
  <%
    if (application.getAttribute("date")==null)
       application.setAttribute("date",new Date());

  %>
  </body>
</html>

action.jsp

  Created by IntelliJ IDEA.
  User: zhangyx-v
  Date: 2020-9-30
  Time: 16:50
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>action-context</title>

</head>
<body>
applicationKey:${applicationScope.applicationKey}
<br>
<span>session:${sessionScope.sessionKey}</span>
<br>
<span>request:${requestScope.requestKey}</span>

</body>



</html>


action类:

package test.action.context;

import com.opensymphony.xwork2.ActionContext;

import java.util.Map;

/**
 * @author zhangyx-v
 */
public class TestActionContext {

    public String execute(){

        //0.获取actionContext对象
        ActionContext actionContext=ActionContext.getContext();
        //1.ActionContext是Action的上下文,类似于springMVC中的ApplicationContext,可以从中获取Action需要的一切信息
        //获取application(域对象) 对应的map
        Map<String,Object>applicationMap=actionContext.getApplication();
        //向map中存入一属性 ?这个过程就相当于request.setAttribute(key,val)
        applicationMap.put("applicationKey","applicationValqqq");
        //获取属性request.getAttribute(key,val)
        Object date=applicationMap.get("date");
        System.out.println("date:"+date);

        //2.session
        Map<String,Object> sessionMap=actionContext.getSession();
        sessionMap.put("sessionKey","sessionValue");

        //3.request
        //ActionContext中没有提供getRequest方法来获取request对应的Map
        //需要手工调用get()方法,传入request字符串来获取
        Map<String,Object> requestMap= (Map<String, Object>) actionContext.get("request");
        requestMap.put("requestKey","requestVal");
        //4.获取请求参数对应的Map,并获取指定的参数值
        Map<String,Object> parameters=actionContext.getParameters();
        //参数map的键为参数名称,值都为字符串数组
        //注意:1.getParameters的返回值为Map<String,Object>,而不是Map<String,String[]>,所以下面需要强转
        //2.parameter这个map只能读不能写,如果写入不会报错,只是无法获取到值
        System.out.println("request:"+ ((String[])parameters.get("name"))[0]);

        return "success";
    }
}

数据前后端传递流程:
在这里插入图片描述
总结:
其实这部分只是要求记住structs对ServletContext,HttpRequest,HttpSession以及请求参数封装的(Map<String, Object>)集合的获取和使用方式:
这四个map都是从上下文中获得的,所以我们必须先使用*ActionContext.getContext() *;获取上下文,然后由上下文获取相应的map集合。
1.ServletContext对应的map获取:
actionContext.getApplication();
2.HttpSession对应的map获取
actionContext.getSession();
3.HttpRequest对应的map获取:
(Map<String, Object>)actionContext.get(“request”);
这三个也是我们常用来从后端向前端传递数据的方式。在struts2里面我们就像上面例子一样先把map获取到,然后使用map的put方法将数据存入map,前端便可获取。
前端获取值的方式:
1.ServletContext对应的map:
${applicationScope.存入map中的key}
2.HttpSession对应的map:
${sessionScope.存入map中的key}
3.HttpRequest对应的map:
${requestScope.存入map中的key}

请求参数对应的map使用:
这个map是用来封装前端请求的参数的所以是不能在后端存入值的:
1.获取请求参数对应的(Map<String, Object>)集合:
actionContext.getParameters();
2.因为map是<String,Object>类型的,但请求参数都是一个字符串数组类型的,所以通过参数名字获取指定参数后需要强转为string[]类型然后再参数的值:
在这里插入图片描述

通过xxxAware接口获取web资源

*通过xxxAware接口就是通过实现ApplicationAware, SessionAware, RequestAware, ParameterAware接口然后通过实现接口的setApplication,setParameters,setRequest,setSession方法来获取ServletContext,HttpSession和HttpRequest以及请求参数对应的map集合,然后传参:
具体过程如下action类:

package test.action.context;

import org.apache.struts2.interceptor.ApplicationAware;
import org.apache.struts2.interceptor.ParameterAware;
import org.apache.struts2.interceptor.RequestAware;
import org.apache.struts2.interceptor.SessionAware;

import java.util.Date;
import java.util.Map;

/**
 * 以实现applicationAware皆苦为例子来说明在struts2框架中
 * 通过实现实现XxxAware接口的方式来获取web资源的过程
 * @author MOON
 */
public class TestAwareAction implements ApplicationAware, SessionAware, RequestAware, ParameterAware {


    /**
     * @see #applicationMap 通过实现ApplicationAware接口的setApplication方法注入
     *是ServletContext对应的map集合
     */
    private Map<String,Object> applicationMap;


    /**
     * @see #sessionMap 通过实现SessionAware接口的setSession方法注入
     *是HttpSession对应的map集合
     */
    private Map<String,Object> sessionMap;
    /**
     * @see #requestMap 通过实现RequestAware接口的setRequest方法注入
     *是HttpRequst对应的map集合
     */
    private Map<String,Object> requestMap;
    /**
     * @see #parameterMap 通过实现ParameterAware接口的setParameters方法注入
     *是请求参数对应的map集合
     */
    private Map<String,String[]> parameterMap;

    /**
     *
     *是testAware action中药执行的方法
     *
     */
    public String execute(){

        //向application中存入一个属性key为applicationKey2的属性

        applicationMap.put("applicationKey2","applicationKey2Val");

        //从application中读取一个key为date的属性

        Date applicationDate= (Date) applicationMap.get("date");
        System.out.println("application中属性date" + applicationDate);

        //向sessionMap中存入一个key为sessionKey2的属性
        sessionMap.put("sessionKey2","sessionKey2Val");
        //向requestMap中存入一个key为requestKey2的属性
        requestMap.put("requestKey2","requestKey2Val");

        //从parameterMap中获取一个key为getName的参数
        String[] name=parameterMap.get("getName");
        for (int i = 0; i < name.length; i++) {
            System.out.println("name参数"+i+"\t"+name[i]);
        }
        return "success";
    }

    /**
     * applicationMap的注入方法
     * @param map applicationMap
     */
    @Override
    public void setApplication(Map<String, Object> map) {

        this.applicationMap=map;

    }

    /**
     * parameter map的注入方法
     * @param map parameter map
     */
    @Override
    public void setParameters(Map<String, String[]> map) {
        this.parameterMap=map;
    }

    /**
     * requestMap的注入方法
     * @param map requestMap
     */
    @Override
    public void setRequest(Map<String, Object> map) {
            this.requestMap=map;
    }

    /**
     * sessionMap的注入法昂发
     * @param map sessionMap
     */
    @Override
    public void setSession(Map<String, Object> map) {
            this.sessionMap=map;
    }
}

具体执行过程如图:
在这里插入图片描述
是不是很简单,通过实现xxxAware就可以自动的注入获取相应的map集合到我们定义的Map类型的成员变量上然后供方法调用,能实现和通过ActionContext获取一样的功能。
建议:若定义的action类中有多个action方法并且都需要使用ServletContext或HttpSession或HttpRequest以及请求参数对应的map集合,建议使用这种方式获取相应的map.
另外提一点就是,HttpSession对应的map集合其实是SessionMap类型的,我们获取到sessionMap之后强转为SessionMap类型可以调用其中的invalidate()方法使session失效。

待续。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值