当前位置: 首页 > article >正文

Struts源码阅读——三个常用的辅助类DispatchAction

Struts源码阅读——三个常用的辅助类

紧接前文,我们来阅读org.apache.struts.actions包中三个常用类的源码。

DispatchActionLookupDispatchActionMappingDispatchAction 是 Struts 1 框架中的三个常用的辅助类,用来简化 Action 类中的请求分发。

这三个文件可以从框架的核心库 struts-corejar 包中找到。

全类名是org.apache.struts.actions.xxxAction


DispatchAction.java

类图

image-20241108155357333

源码

这是一个抽象类,派生类(后端控制器)继承该类,但是不会覆盖其方法,该类并无抽象方法,仅仅是增加多个后端控制方法罢了。

例如,增删改查的业务逻辑,通常写上 4 个后端控制器类:CreateActionRetrieveActionUpdateActionDeleteAction
这些后端控制器类都继承 Action 类,实现执行函数,也就是覆盖 Action 类的 execute 函数。

public ActionForward execute(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response) throws Exception

这样的设计导致后端控制器类数量太多,并且没有体现出聚合性、封装性。可以把这几个类的 execute 方法,封装在同一个类中,当然,函数声明重复了,所以要根据功能把这些函数的函数名称修改一下,例如:create 函数、retrieve 函数、update 函数、delete 函数。

前端控制器 org.apache.struts.action.ActionServlet 的控制流程当然没有改变,它会调用后端控制器的 execute 函数。

DispatchAction 的设计思路

那么,DispatchAction 这个抽象类的 execute 方法的实现,就是根据浏览器端(客户端)发送过来的 URL 或者 queryString 来完成分发(dispatch)!
DispatchAction 这个类的 execute 方法的具体实现,就是根据查询字符串 (queryString) 中的某个参数数据完成分发,例如,execute 方法可以分发到 createretrieveupdatedelete 方法。

既然我们已经理解了 DispatchAction 类的 execute 函数的设计需求,那么,同学们,你们会写出来 DispatchAction 类的 execute 函数吗?

两个关键问题

有两个问题需要知道:

  1. 后端控制器类继承 DispatchAction 类,增加多个控制函数,函数名称未知(输入输出参数都是不变的)。
  2. 根据查询字符串中的什么参数的值来完成转发(dispatch)?
上面的回答

在 Struts 配置文件中指定使用什么参数,并自定义参数的逻辑名称。然后,由参数的值决定调用哪个控制函数,调用过程使用 Java 反射机制。

例如,Struts 配置文件:

<action path="/calc" type="action.CalcAction" name="calcForm" input="/calc.jsp" parameter="method"/>

calc 表单中的四个提交按钮,value代表了method所拥有的四个值:

<input type="submit" name="method" value="add"/>
<input type="submit" name="method" value="subtract"/>
<input type="submit" name="method" value="multiply"/>
<input type="submit" name="method" value="divide"/>

优化后的控制器设计

通过这种方法,AddActionSubtractActionMultiplyActionDivideAction 的后端控制器数量就被减少到只写一个后端控制器类:CalcAction,并继承自 DispatchAction,在 CalcAction 中定义四个控制函数即可:

public ActionForward add(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception
public ActionForward subtract(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception
public ActionForward multiply(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception
public ActionForward divide(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception
DispatchAciton源码
package org.apache.struts.actions;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.HashMap;

public abstract class DispatchAction extends BaseAction {
    protected static Log log = LogFactory.getLog(DispatchAction.class);
    protected Class clazz = this.getClass();
	
	// methods这个map集合是缓存的目的,因为反射机制有点慢。
	// key: String method name, value: Method object
	// 声明为HashMap<String,Method> methods更好,类型安全,更准确,否者HashMap,鬼知道这个集合里面都有啥。
    protected HashMap methods = new HashMap();  

    // execute函数的输入参数的类型,反射机制使用。同时,也是扩展的控制函数的参数类型。
    protected Class[] types =
        {
            ActionMapping.class, ActionForm.class, HttpServletRequest.class,
            HttpServletResponse.class
        };
	
	// 最重要的算法实现,完成dispatch
    public ActionForward execute(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {

        // 从配置文件中获取parameter的名,例如,method
        String parameter = getParameter(mapping, form, request, response);

        // 从请求对象中,获取由parameter的值指定的控制函数的名称
		// String name = request.getParameter(parameter)
        String name = getMethodName(mapping, form, request, response, parameter);	// 考试内容
		
		// name的值就是控制函数的名称,例如add,subtract,multipy,divide之类的。

        // Prevent recursive calls, 避免递归,扩展的控制函数的名称不能是execute
        if ("execute".equals(name) || "perform".equals(name)) {
            String message =
                messages.getMessage("dispatch.recursive", mapping.getPath());
            log.error(message);
            throw new ServletException(message);
        }

		// 动态调用控制函数,完成dispatch! 
		// 控制流程是前端控制器调用CalcAction类的execute函数(继承自DispatchAction)
		// 然后execute函数将调用转发到add函数,subtract函数等等。
		
        // Invoke the named method, and return the result
        return dispatchMethod(mapping, form, request, response, name);
    }

    
    protected ActionForward dispatchMethod(ActionMapping mapping,
        ActionForm form, HttpServletRequest request,
        HttpServletResponse response, String name)
        throws Exception {
      
        // Identify the method object to be dispatched to
        Method method = null;

        try {
            method = getMethod(name);	// 根据函数名称,找到函数对象。
        } catch (NoSuchMethodException e) {
            String message =
                messages.getMessage("dispatch.method", mapping.getPath(), name);
            log.error(message, e);
            String userMsg =
                messages.getMessage("dispatch.method.user", mapping.getPath());
            throw new NoSuchMethodException(userMsg);
        }

        ActionForward forward = null;

        try {
			// Java反射机制,执行某个对象的函数,动态调用。考试重点。
            Object[] args = { mapping, form, request, response };
            forward = (ActionForward) method.invoke(this, args);

        }
		
        // Return the returned ActionForward instance
        return (forward);
    }

    protected String getParameter(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {

        // 从struts-config.xml配置文件中读取action元素的parameter属性的值。
		// 当然是ActionMapping类封装的功能了。ActionMapping类不考。
        String parameter = mapping.getParameter();	// 算法重点

        return parameter;
    }

    // 根据函数名称获取函数对象,需要调用Class类的的getMethod函数,Java的反射机制!
    protected Method getMethod(String name)
        throws NoSuchMethodException {
		// 同步,加锁,这个性能不好,如何解决?不使用同步代码可以吗?
        synchronized (methods) {	// Action是单实例对象,注意methods线程安全。
            Method method = (Method) methods.get(name);

            if (method == null) {
                method = clazz.getMethod(name, types);	// 重点,Java反射机制
				// 保存到Map集合中,下次再调用getMethod函数直接从Map集合中读取,不再调用反射。
                methods.put(name, method);	
            }

            return (method);
        }
    }

    
    protected String getMethodName(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response,
        String parameter) throws Exception {
        //从请求对象中获取参数数据,HttpServletRequest中的获取参数的方法一定要掌握,考试重点。
        return request.getParameter(parameter);
    }
}

功能:

DispatchAction 是 Struts 1 提供的一个 Action 类,用于将多个业务逻辑分派到同一个 Action 中处理。它允许你在一个类中定义多个方法,并根据请求参数调用不同的方法。

注意事项:

在 JSP 页面中指定 method 参数(或其他指定参数)来区分调用的方法。例如:<html:link action="/example.do?method=add">Add</html:link>

优点和限制

  • 优点:减少了创建多个 Action 类的需求,可以将同一模块的操作集中在一个类中,提升了代码组织性。

  • 限制:传递的 method 参数必须与方法名严格一致,且方法必须是 public 且没有参数的。


LookupDispatchAction.java

类图

image-20241108155704151

源码

LookupDispatchAction 设计分析

DispatchAction 的问题

DispatchAction 类的转发逻辑相对简单,但也存在一些问题。
例如,以下 HTML 代码中,提交按钮的 value 属性值就是函数名称:

<input type="submit" name="method" value="add"/>

这里的按钮文本直接对应了后端控制器的方法名称,这样做存在以下问题:

  1. 安全性差:函数名称暴露在客户端。
  2. 代码泄漏:方法名暴露可能会被恶意用户篡改或猜测。
  3. 特殊字符问题:按钮的文本可能是中文或包含空格等特殊字符,可能会导致问题。

因此,必须对这种实现方式进行改进。

LookupDispatchAction 解决方案

LookupDispatchAction 类继承自 DispatchAction,并继承了 DispatchAction 类的 execute 函数,但它对 execute 函数进行了增强和改进。

LookupDispatchAction 中,主要做了以下改进:

  1. 获取控制函数的名称LookupDispatchAction 通过 getMethodName() 函数获取控制函数名称。
  2. 反射机制完成 dispatch:利用反射机制来进行函数的调用。

虽然 LookupDispatchAction 没有改变 dispatch 逻辑(即根据函数名称进行分发),但是它改写了 getMethodName 函数,从而提供了一个更安全、灵活的方式来获取函数名称。

getMethodName 的实现

DispatchAction 类的 getMethodName 函数非常简单,通过配置文件指定参数名称:

<action path="/calc" type="action.CalcAction" name="calcForm" input="/calc.jsp" parameter="method"/>

客户端提交指定参数的值,也就是函数名称:

<input type="submit" name="method" value="add"/>

这个方法存在安全性和灵活性的问题,因此需要改进。

引入资源文件

为了提高灵活性和支持国际化,Struts 支持使用资源文件。在 struts-config.xml 中,可以声明资源文件:

<message-resources parameter="ApplicationResources" />

然后在类加载路径中创建 ApplicationResources.properties 文件,文件内容如下:

button.add=Add
button.subtract=Subtract
button.multiply=Multiply
button.divide=Divide

在资源文件中,name(即键)通常是程序内部使用的标识符,而 value(即值)通常是显示给用户的文本(例如按钮的文本)。资源文件中的值可以是中文字符,name 是唯一的,value 通常也是唯一的。

提交按钮的国际化实现

在 HTML 表单中使用资源文件的内容,代码如下:

<html:form action="/calc">
    <html:submit property="method">
        <bean:message key="button.add"/>
    </html:submit>
    <html:submit property="method">
        <bean:message key="button.subtract"/>
    </html:submit>
</html:form>
CalcAction 类的改进

在这种设计下,CalcAction 类不再继承 DispatchAction,而是继承 LookupDispatchAction
LookupDispatchAction 继承自 DispatchAction,并改进了 getMethodName 的实现,使得控制函数的名称不再直接暴露在客户端。

体现 “lookup” 的机制

LookupDispatchAction 类的核心思想是通过字典映射来查找函数名称。它定义了一个抽象方法 getKeyMethodMap,返回一个映射(Map),将资源文件中的 key 映射到相应的控制函数名称。

例如,getKeyMethodMap 可以如下实现:

@Override
public Map getKeyMethodMap() {
    Map<String, String> map = new HashMap<>();
    map.put("button.add", "add");
    map.put("button.subtract", "subtract");
    return map;
}

这个映射返回一个 Map,其中 key 是资源文件中的 key(例如 button.add),而 value 是相应的方法名称(例如 add)。

getMethodName 方法的改写

DispatchAction 类中,getMethodName 是一个重要的函数,我们需要对其进行改写(override)。
改写后的 getMethodName 根据参数名称(例如 method)来获取客户端提交的参数值,然后通过反向查找获取函数名称。

protected String getMethodName(
    ActionMapping mapping, 
    ActionForm form, 
    HttpServletRequest request, 
    HttpServletResponse response, 
    String parameter) throws Exception

具体实现过程如下:

  1. 根据 parameter 指定的浏览器端提交的参数名称(例如 parameter="method"),从请求对象中获取该参数的值。
  2. 根据该值,在资源文件中的 key-value 映射中查找对应的 keyName
  3. 根据 keyName 查找到函数名称,并最终调用相应的控制函数。

通过 LookupDispatchAction 的设计,我们不仅提高了安全性,避免了暴露函数名称,还通过资源文件实现了国际化和灵活的参数配置。此设计利用字典(映射集合)来查找函数名称,并通过反射机制实现动态方法调用,从而提高了代码的封装性、可维护性和安全性。

LookupDispatchAction源码
package org.apache.struts.actions;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.MessageResourcesConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.util.MessageResources;

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

import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

public abstract class LookupDispatchAction extends DispatchAction {

    private static final Log LOG = LogFactory.getLog(LookupDispatchAction.class);
    protected Map localeMap = new HashMap(); 	// Map<Locale,Map<String,String>>
    protected Map keyMethodMap = null;			// Map<String,String>

    private Map initLookupMap(HttpServletRequest request, Locale userLocale) {
		
        Map lookupMap = new HashMap();
        this.keyMethodMap = this.getKeyMethodMap();

        // 获取模块配置,struts 支持模块开发(一个项目通常会分解为多个模块)
        // 可以理解有多个类似struts-config.xml的配置文件。
        ModuleConfig moduleConfig =
            (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);  // 通常根据uri获取模块配置

        // 读取该模块的配置文件中的资源文件的配置。
        // 在struts-config.xml文件中可以有多个这样的资源配置,例如在struts-config.xml中的声明
        //  <message-resources parameter="ApplicationResources" />
        //  <message-resources key="aaa" parameter="res.aaa.AResources" />
        //  <message-resources key="bbb" parameter="res.bbb.BResources" />

        // 获取所在模块的资源配置信息。一个模块可以有多个资源文件,见上面的配置信息。
        // MessageResourcesConfig对象有parameter和key属性。
        MessageResourcesConfig[] mrc = 
            moduleConfig.findMessageResourcesConfigs();

        // 这个注释不准确。Look through all module's MessageResources
        // 遍历每个message-resources
        for (int i = 0; i < mrc.length; i++) {
            MessageResources resources =
                this.getResources(request, mrc[i].getKey());

            // 开始处理资源文件中的一部分name:value pair数据。主要是根据函数字典进行数据遍历。
            // Look for key in MessageResources
            Iterator iter = this.keyMethodMap.keySet().iterator();

            while (iter.hasNext()) {
                String key = (String) iter.next();
                String text = resources.getMessage(userLocale, key);	// 支持国际化

                // Found key and haven't added to Map yet, so add the text
                if ((text != null) && !lookupMap.containsKey(text)) {
                    lookupMap.put(text, key);	// 考试内容,注意是text:key映射,不是key:text映射!
            }
        }

        return lookupMap;
    }

    // 派生类必须覆盖这个抽象函数
    protected abstract Map getKeyMethodMap();


    // 参数keyName就是浏览器端提交的参数信息,例如:method=Add
    // keyName是Add,根据这个value,找到button.add这个key
    protected String getLookupMapName(HttpServletRequest request,
        String keyName, ActionMapping mapping)
        throws ServletException {
        // Based on this request's Locale get the lookupMap
        Map lookupMap = null;

        synchronized (localeMap) {
            Locale userLocale = this.getLocale(request);	// 获取浏览器的区域语言,从Action类继承过来的。
            lookupMap = (Map) this.localeMap.get(userLocale);
            if (lookupMap == null) {
                lookupMap = this.initLookupMap(request, userLocale);
                this.localeMap.put(userLocale, lookupMap);
            }
        }

        // 从value:key映射集合中,获取key
        String key = (String) lookupMap.get(keyName);	// 资源文件中的value:key构成的映射集合

        // Find the method name
        String methodName = (String) keyMethodMap.get(key);	// 在函数字典中lookup up到函数名称

        return methodName;	// 返回函数名称
    }

    // 考试重点
    // LookupDispatchAction类的核心实现,根据浏览器提交的参数数据,以及函数字典,获取派发的函数名称!
    @Override
    protected String getMethodName(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response,
        String parameter) throws Exception {
			
        // 从浏览器端发送过来的参数获取标签文本、按钮文本之类的参数值
        String keyName = request.getParameter(parameter);

        if ((keyName == null) || (keyName.length() == 0)) {
            return null;
        }

        String methodName = getLookupMapName(request, keyName, mapping);

        return methodName;
    }
}

功能:

LookupDispatchActionDispatchAction 的扩展,主要用于国际化场景。它允许基于请求参数自动调用不同的方法,并且支持从资源文件中读取键值对,实现国际化。

注意事项:

需要重写 getKeyMethodMap() 方法,定义请求参数和方法名的映射关系。此映射关系可以用来支持不同语言的按钮文本映射到相应的操作方法。

优点和限制

  • 优点:适合需要根据不同语言自动映射的操作方法,使代码更加清晰。

  • 限制:比 DispatchAction 多了键值对配置的工作量,但更适合多语言需求的场景。


MappingDispatchAction.java

类图

image-20241108155723633

源码

同一个后端控制器对应多个 <action> 配置

在 Struts 框架中,一个后端控制器类可以对应多个 <action> 配置。也就是说,同一个后端控制器类可以有多个控制函数。例如,CalcAction 类可以有 addsubtractmultiplydivide 函数,而 DispatchAction 类的 execute 函数负责完成函数的分发(dispatch)。

如何实现函数的分发?

那么,如何实现这些控制函数的调用和转发呢?execute 函数是如何将请求转发到 addsubtractmultiplydivide 等不同的函数呢?

MappingDispatchAction 的改进

MappingDispatchAction 类改写了(覆盖)getMethodName 函数。这个函数返回的控制函数名称来自 <action> 元素的 parameter 属性的值。通过这个方法,MappingDispatchAction 能够根据请求的参数值动态决定调用哪个函数。

通过这种设计,可以将具有相关性和聚合性的多个函数封装在同一个后端控制器类中,并为该控制器类定义多个 <action> 路径。

示例:为不同的路径定义多个 <action>

举个例子,如果我们有一个 CalcAction 类,它包含了 addsubtractmultiplydivide 函数,我们可以通过以下方式在 struts-config.xml 中为每个函数定义一个路径:

<action path="/add" type="action.CalcAction" parameter="add"/>
<action path="/subtract" type="action.CalcAction" parameter="subtract"/>
<action path="/multiply" type="action.CalcAction" parameter="multiply"/>
<action path="/divide" type="action.CalcAction" parameter="divide"/>

在这个配置中,每个 <action> 元素对应了 CalcAction 中的一个函数,parameter 属性指定了要调用的控制函数的名称。

控制器类的改进

为了使得这个配置生效,CalcAction 类只需要继承 MappingDispatchAction 类,并且通过 parameter 属性来确定要调用的控制函数。由于 MappingDispatchAction 已经实现了动态分发的机制,parameter 属性直接决定了要调用的函数名称。

通过将多个函数封装在同一个后端控制器类中,并为该控制器类定义多个 <action> 路径,MappingDispatchAction 实现了一个灵活的解决方案,使得我们可以根据请求的不同路径动态调用不同的函数。这样,后端控制器类就不需要为每个操作定义单独的控制器类,减少了代码冗余,并提高了可维护性和灵活性。

MappingDispatchAction源码
package org.apache.struts.actions;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

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

public class MappingDispatchAction extends DispatchAction {
  
    // 这个函数没有存在的必要,直接继承DispatchAction类就可以了。
    // 毫无意义的覆盖。
    protected String getParameter(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {

        return mapping.getParameter();

    }

    // MappingDispatchAction唯一有意义的函数,覆盖了DispatchAction类的getMethodName函数。
    protected String getMethodName(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response,
        String parameter) throws Exception {

        // 派发的控制函数的函数名称就是在<action>元素中parameter属性的值。
        return parameter;
    }
}

功能:

MappingDispatchAction 类似于 DispatchAction,但它使用了 ActionMapping 的属性 parameter 来确定调用的方法。它通过 ActionMapping 中的 parameter 值来找到相应的方法,避免在请求中直接使用 method 参数。

注意事项:

struts-config.xml 文件中配置 Action 时,设置 parameter 属性来指定映射参数。

优点和限制:

  • 优点:避免了在请求 URL 中暴露方法名,提升了安全性,适合不想公开请求参数的应用场景。

ected String getMethodName(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response,
String parameter) throws Exception {

    // 派发的控制函数的函数名称就是在<action>元素中parameter属性的值。
    return parameter;
}

}


### 功能: 
`MappingDispatchAction` 类似于 `DispatchAction`,但它使用了 ActionMapping 的属性 `parameter` 来确定调用的方法。它通过 `ActionMapping` 中的 `parameter` 值来找到相应的方法,避免在请求中直接使用 `method` 参数。

### 注意事项:
在 `struts-config.xml` 文件中配置 Action 时,设置 `parameter` 属性来指定映射参数。

### 优点和限制:
- **优点**:避免了在请求 URL 中暴露方法名,提升了安全性,适合不想公开请求参数的应用场景。

- **限制**:需要在 `struts-config.xml` 中额外配置 `parameter` 属性。

http://www.kler.cn/a/394921.html

相关文章:

  • 【视觉SLAM】1-概述
  • 【教程】Ubuntu设置alacritty为默认终端
  • react-redux useSelector钩子 学习样例 + 详细解析
  • 项目风险管理的3大要素
  • 第三十一天|贪心算法| 56. 合并区间,738.单调递增的数字 , 968.监控二叉树
  • 【操作系统】守护进程
  • STM32学习笔记-----UART的概念
  • 2025年前端发展趋势
  • Spring Boot 中的全局异常处理器
  • Ubuntu20.04 解决一段时间后键盘卡死的问题 ubuntu
  • 前端开发中常用的包管理器(npm、yarn、pnpm、bower、parcel)
  • SSL证书以及实现HTTP反向代理
  • autodl+modelscope推理stable-diffusion-3.5-large
  • Sql server查询数据库表的数量
  • Linux入门攻坚——37、Linux防火墙-iptables-3
  • 陈列oracle的错误信息列表
  • 微服务架构面试内容整理-安全性-Spring Security
  • Koa进阶:掌握中间件和参数校验的艺术
  • RestFul URL
  • gitlab-development-kit部署gitlab《二》
  • 期权懂|请问如何用期权进行风险管理?
  • RabbitMQ 全面解析:语法与其他消息中间件的对比分析
  • Python 编程入门指南(一)
  • GitHub Org
  • 图形 2.7 LDR与HDR
  • css文字间距撑满横向距离