实现原理
先放一张很俗的框架图:
是不是看着很复杂?其实简单的讲这个古老的框架将我们平时使用的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失效。
待续。。。。。