Struts框架核心原理与实战:从MVC模式到动态方法调用

发布时间:2026/8/1 3:22:30
Struts框架核心原理与实战:从MVC模式到动态方法调用 1. 从Servlet到Struts为什么我们需要一个“控制器”如果你是从Servlet和JSP时代一路走过来的Java Web开发者听到“Struts”这个名字大概率会勾起一些复杂的回忆。它可能代表着你的第一个MVC框架也可能代表着一段与复杂XML配置“搏斗”的时光。今天我们不谈情怀只谈技术。我想从一个最根本的问题开始在Servlet/JSP已经能完成所有工作的年代我们为什么还需要Struts让我们回到那个典型的Servlet开发场景。一个用户提交登录表单请求发送到LoginServlet。在这个Servlet的doPost方法里你需要做以下几件事调用request.getParameter()获取用户名和密码。连接数据库进行校验。根据校验结果决定下一步成功将用户对象存入Session然后request.getRequestDispatcher(“/welcome.jsp”).forward(request, response);失败在Request中设置一个错误信息属性然后request.getRequestDispatcher(“/login.jsp”).forward(request, response);看起来清晰明了对吧但问题会随着项目规模膨胀而指数级增长。想象一下一个中等规模的系统有50个功能模块对应50个Servlet。你会发现URL映射混乱每个Servlet都需要在web.xml里配置一个servlet和servlet-mapping。维护这个日益庞大的XML文件本身就是一场噩梦。代码重复严重每个Servlet里都有获取参数、类型转换、校验逻辑。这些样板代码Boilerplate Code占据了大量的行数。视图控制耦合Servlet直接决定了跳转到哪个JSP页面。如果有一天需要把welcome.jsp改名为home.jsp你必须找到所有跳转到它的Servlet进行修改。团队协作困难没有统一的流程规范每个程序员写Servlet的风格都可能不同导致代码可读性和可维护性急剧下降。Struts 1.x 的出现就是为了解决这些痛点。它的核心思想是“关注点分离”和“约定优于配置”虽然它的配置也不少。它将上述Servlet中的流程标准化、模块化了用一个统一的“前端控制器”ActionServlet接收所有请求替代了分散的Servlet。web.xml里只需要配置这一个Servlet。用“动作”Action类来处理具体业务逻辑它只负责调用Service、准备数据而不关心下一步跳转到哪里。用“配置文件”struts-config.xml来管理请求路径、Action类、跳转页面之间的映射关系。修改页面路径只需改配置文件无需动Java代码。用“表单Bean”ActionForm来封装请求参数自动完成类型转换和数据校验将开发者从繁琐的getParameter中解放出来。所以Struts的本质是一个基于MVC设计模式的请求驱动型Web框架。它通过引入一个中心化的控制层为混乱的Web开发流程建立起了秩序。尽管它如今已被更现代的框架如Spring MVC所取代但理解Struts的运作机制尤其是其访问流程和配置文件是理解Java Web MVC框架演进史的绝佳起点。接下来我们就亲手搭建一个环境看看这套机制是如何运转起来的。2. 快速入门亲手搭建你的第一个Struts应用理论说再多不如动手跑一遍。我们用一个最经典的例子——“用户登录”来快速感受Struts的完整流程。我会假设你使用Eclipse或IDEA并已配置好Tomcat服务器。2.1 环境准备与项目创建首先你需要准备Struts的核心库文件。访问Apache官网注意Struts 1已停止维护但学习资料和库文件仍可找到下载Struts 1.x例如1.3.10的发行包。解压后我们主要关注lib文件夹下的JAR包。对于一个基础应用你至少需要以下JAR文件struts-core-1.3.10.jarStruts框架的核心类库。commons-beanutils-1.8.3.jarBean工具库用于操作JavaBean。commons-digester-2.1.jarXML解析工具用于读取struts-config.xml。commons-logging-1.1.3.jar通用日志接口。commons-validator-1.3.1.jar校验框架。 将这些JAR包复制到你的Web项目的WEB-INF/lib目录下。2.2 核心四步从请求到响应的完整链条Struts应用的骨架由四个核心部分组成我们一步步来创建。第一步配置前端控制器web.xml所有的请求都需要先经过Struts的“总闸”——ActionServlet。在WEB-INF/web.xml中配置它。?xml version1.0 encodingUTF-8? web-app xmlnshttp://java.sun.com/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd version2.5 !-- 定义Struts的核心控制器Servlet -- servlet servlet-nameaction/servlet-name servlet-classorg.apache.struts.action.ActionServlet/servlet-class !-- 指定Struts配置文件的路径默认为/WEB-INF/struts-config.xml -- init-param param-nameconfig/param-name param-value/WEB-INF/struts-config.xml/param-value /init-param load-on-startup1/load-on-startup /servlet !-- 将所有以.do结尾的请求映射到ActionServlet -- servlet-mapping servlet-nameaction/servlet-name url-pattern*.do/url-pattern /servlet-mapping !-- 设置欢迎页面 -- welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list /web-app这里的关键点是url-pattern*.do/url-pattern。它意味着所有以.do结尾的URL都会被ActionServlet拦截处理。这是一种约定你可以用其他模式如/action/*。第二步创建视图JSP页面创建login.jsp这是一个简单的登录表单。% page contentTypetext/html;charsetUTF-8 languagejava % html head titleStruts快速入门 - 登录/title /head body h2用户登录/h2 %-- 注意表单的action路径以 .do 结尾对应web.xml中的映射 --% form actionlogin.do methodpost 用户名input typetext nameusernamebr 密码input typepassword namepasswordbr input typesubmit value登录 /form %-- 显示Action传递过来的错误信息 --% font colorred${error}/font /body /html同时创建一个登录成功后的页面welcome.jsp% page contentTypetext/html;charsetUTF-8 languagejava % html head title欢迎页/title /head body h1欢迎您${username}/h1 /body /html第三步定义表单Bean与动作类ActionForm Action这是Struts的“模型”和“控制器”部分。创建ActionFormLoginForm.java用于封装表单数据自动进行类型转换和基础校验。package com.example.form; import org.apache.struts.action.ActionForm; public class LoginForm extends ActionForm { private String username; private String password; // 必须提供getter和setter方法Struts通过反射调用它们 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; } // 可选重置方法在复用ActionForm实例时被调用以清空旧数据 public void reset() { this.username null; this.password null; } // 可选表单验证方法在Action.execute()之前被调用 public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors new ActionErrors(); if (username null || username.trim().isEmpty()) { errors.add(username, new ActionMessage(error.username.required)); } if (password null || password.trim().isEmpty()) { errors.add(password, new ActionMessage(error.password.required)); } return errors; } }创建ActionLoginAction.java处理核心业务逻辑。package com.example.action; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import com.example.form.LoginForm; public class LoginAction extends Action { Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // 1. 将通用的ActionForm向下转型为具体的LoginForm LoginForm loginForm (LoginForm) form; String username loginForm.getUsername(); String password loginForm.getPassword(); // 2. 模拟业务逻辑处理这里简化成硬编码校验 ActionForward forward null; if (admin.equals(username) 123456.equals(password)) { // 登录成功 // 将数据存入request范围供JSP页面显示 request.setAttribute(username, username); // 通过mapping.findForward找到配置文件中名为“success”的跳转定义 forward mapping.findForward(success); } else { // 登录失败 request.setAttribute(error, 用户名或密码错误); // 跳转回登录页面 forward mapping.findForward(failure); } // 3. 返回ActionForward对象告诉Struts下一步去哪 return forward; } }第四步编写中枢配置文件struts-config.xml这是Struts的“路由表”将所有部分连接起来。在WEB-INF/下创建struts-config.xml。?xml version1.0 encodingUTF-8 ? !DOCTYPE struts-config PUBLIC -//Apache Software Foundation//DTD Struts Configuration 1.3//EN http://struts.apache.org/dtds/struts-config_1_3.dtd struts-config !-- 表单Bean定义 -- form-beans !-- 定义一个表单Bean名为“loginForm”对应我们写的LoginForm类 -- form-bean nameloginForm typecom.example.form.LoginForm/ /form-beans !-- 全局异常和全局转向定义本例暂不涉及 -- !-- 动作映射定义核心中的核心 -- action-mappings !-- 定义一个动作映射 -- action path/login !-- 请求路径对应login.do -- typecom.example.action.LoginAction !-- 处理动作类 -- nameloginForm !-- 使用的表单Bean名称 -- scoperequest !-- 表单Bean的作用域request或session -- validatetrue !-- 是否调用ActionForm的validate方法 -- input/login.jsp !-- 如果校验失败跳转回哪个页面 -- !-- 定义局部转发Forward -- forward namesuccess path/welcome.jsp/ forward namefailure path/login.jsp/ /action /action-mappings !-- 消息资源文件定义本例暂不涉及 -- !-- message-resources parametercom.example.ApplicationResources/ -- /struts-config现在启动你的Tomcat服务器访问http://localhost:8080/你的项目名/。你会看到登录页面输入admin和123456点击登录就能看到欢迎页面。一个最基础的Struts应用就跑通了。这个过程看似步骤繁多但每一步都职责清晰构成了Struts MVC的基石。3. 庖丁解牛Struts请求访问流程深度解析在点击“登录”按钮后到看到“欢迎页”之前Struts框架内部到底发生了什么理解这个流程是掌握Struts乃至任何MVC框架的关键。我们可以将整个过程拆解为一条清晰的“流水线”。3.1 核心流程图与阶段拆解整个流程可以概括为以下阶段由ActionServlet这个总调度员严格控制用户请求 (login.do) ↓ ActionServlet (前端控制器) ↓ 1. 根据请求路径 /login 查找对应的 action 配置 ↓ 2. 根据 name”loginForm” 创建/获取 LoginForm 实例并用请求参数填充它 (populate) ↓ 3. 如果 validate”true”调用 LoginForm.validate() 进行表单校验 ↓ ┌─ 校验失败 ──→ 跳转到 action 中 input”/login.jsp” 指定的页面 ↓ (校验成功) 4. 将 LoginForm 和请求对象等传递给 LoginAction.execute() 方法 ↓ 5. LoginAction 执行业务逻辑并返回一个 ActionForward 对象 (如 mapping.findForward(“success”)) ↓ 6. ActionServlet 根据 ActionForward 对象找到配置中对应的 forward name”success” path”/welcome.jsp”/ ↓ 7. 使用 RequestDispatcher 转发 (forward) 到 /welcome.jsp ↓ JSP 页面渲染响应返回给用户3.2 关键环节的技术内幕1. ActionServlet的初始化与请求分发在Web容器如Tomcat启动时由于web.xml中配置了load-on-startup1/load-on-startupActionServlet的init()方法会被立即调用。在这个方法中它会解析struts-config.xml文件将所有的配置信息form-bean,action-mapping等加载到内存中形成一系列ModuleConfig对象。当请求到达时如login.doActionServlet的doGet()或doPost()方法会被调用。它首先从请求路径中提取出动作路径例如从/app/login.do中提取出/login然后在其内存配置中查找匹配的action映射。2. ActionForm的生命周期与数据填充这是Struts的一个精巧设计也是早期容易产生困惑的地方。ActionForm本质上是一个值对象Value Object或数据传输对象DTO。框架通过Java的反射机制根据action中name属性指定的名称找到对应的form-bean定义进而实例化LoginForm类。接着它会遍历LoginForm中所有的setter方法如setUsername根据setter方法名推导出请求参数名username然后调用request.getParameter(“username”)获取值并调用setUsername(String)将其注入。这个过程称为“填充”Population。scope属性决定了这个ActionForm实例是放在HttpServletRequest还是HttpSession中。request范围意味着每次请求都会创建新的实例session范围则会在会话中复用此时reset()方法就非常重要用于清除前一次请求的残留数据。3. validate()方法的执行与校验流程如果action配置中validate”true”在填充数据之后、执行Action之前框架会自动调用ActionForm的validate()方法。开发者可以在这个方法里编写业务规则校验如非空、格式、长度等。validate()方法返回一个ActionErrors对象。如果这个对象为null或者不包含任何错误信息流程继续否则流程将中断直接跳转到input属性指定的页面通常是表单页并且ActionErrors中的信息可以通过html:errors标签在JSP上显示。这是一种声明式校验将校验逻辑从Action中剥离使得Action更专注于核心业务。4. Action的execute()与请求转发经过以上步骤一个“干净”的、已校验的ActionForm对象连同ActionMapping封装了当前action的配置信息、HttpServletRequest和HttpServletResponse一起被传递给Action.execute()方法。这里是开发者编写业务逻辑的核心地带。业务逻辑执行完毕后Action需要返回一个ActionForward对象告诉框架“接下来去哪”。通常我们通过mapping.findForward(String name)来获取在配置文件中预定义的转发。ActionServlet拿到这个ActionForward后会从中解析出真正的资源路径如/welcome.jsp然后使用RequestDispatcher.forward()进行服务器端跳转。至此控制权从Struts框架交还给了Web容器由容器来渲染最终的JSP页面。注意这里有一个非常重要的细节Struts默认使用的是服务器端转发Forward而非重定向Redirect。转发是在一次请求内部完成的因此request范围内的属性如我们设置的username可以传递到JSP页面。而重定向是两次独立的请求request范围内的数据会丢失。在Struts配置中可以通过在forward标签中设置redirect”true”来指定使用重定向但这需要根据数据传递的需求谨慎使用。理解了这个流程你就掌握了Struts框架的“任督二脉”。无论配置看起来多复杂都是在这个标准流程上的定制和扩展。接下来我们就深入这个流程的“指挥中心”——struts-config.xml配置文件。4. struts-config.xml配置文件框架的“指挥中心”详解如果说ActionServlet是Struts框架的“大脑”那么struts-config.xml就是存储其全部“知识与指令”的“记忆库”。这个XML文件定义了整个应用的骨架和行为规则。它的结构清晰但选项繁多我们将其拆解为几个核心模块来逐一攻克。4.1 全局配置数据源、插件与异常处理在文件的顶部通常是一些影响整个应用的全局设置。struts-config !-- 数据源配置 (Struts 1.2.7)用于定义全局数据库连接池 -- data-sources data-source typeorg.apache.commons.dbcp.BasicDataSource set-property propertydriverClassName valuecom.mysql.jdbc.Driver/ set-property propertyurl valuejdbc:mysql://localhost:3306/mydb/ set-property propertyusername valueroot/ set-property propertypassword valuepassword/ set-property propertymaxActive value10/ /data-source /data-sources !-- 插件配置用于在Struts启动和关闭时执行自定义逻辑 -- plug-in classNamecom.example.plugin.CustomPlugin set-property propertysomeConfig valuesomeValue/ /plug-in !-- 全局异常处理定义某种异常类型发生时统一跳转到哪个页面 -- global-exceptions exception keyglobal.error.invalidlogin !-- 在资源文件中定义的错误消息key -- typecom.example.exception.AuthenticationException path/error.jsp !-- 发生此异常时跳转的路径 -- scoperequest/ /global-exceptions !-- 全局转发定义所有Action都可以使用的公共跳转路径 -- global-forwards forward namehome path/index.jsp/ forward nameerror path/common/error.jsp/ forward namelogout path/logout.do redirecttrue/ /global-forwardsdata-sources在早期Java Web应用中经常在这里配置数据库连接池。但在现代开发中更推荐使用专门的连接池工具如C3P0、Druid或在Web容器中配置JNDI数据源。plug-in这是Struts一个强大的扩展机制。你可以编写实现PlugIn接口的类在应用启动时init方法加载一些全局资源如缓存、配置在应用关闭时destroy方法安全释放。Spring框架早期就提供了ContextLoaderPlugin用于在Struts中集成Spring容器。global-exceptions和global-forwards它们的作用范围是整个模块。global-exceptions实现了声明式的异常处理将异常类型与处理页面解耦。global-forwards定义了公共的跳转目标避免在多个action中重复定义相同的forward。4.2 表单Bean定义数据的第一道关卡form-beans区域是所有ActionForm的注册中心。这里定义了表单数据的结构。form-beans !-- 动态表单Bean无需编写Java类通过配置文件定义字段和类型 -- form-bean namedynamicForm typeorg.apache.struts.action.DynaActionForm form-property nameuserId typejava.lang.Integer/ form-property nameuserName typejava.lang.String/ form-property namesubscription typejava.lang.Boolean initialfalse/ /form-bean !-- 静态表单Bean对应我们编写的LoginForm类 -- form-bean nameloginForm typecom.example.form.LoginForm/ /form-beans动态ActionFormDynaActionForm这是Struts提供的一个非常实用的特性。对于字段简单、无需复杂校验的表单你不需要编写具体的ActionForm类。只需在配置中声明字段名和类型框架会在运行时动态生成表单对象。这极大地减少了编码量是“约定优于配置”思想的体现。访问动态表单中的属性需要使用form.get(“propertyName”)方法。静态ActionForm就是继承ActionForm的自定义类提供了最强的类型安全和灵活性可以编写复杂的validate()逻辑。4.3 动作映射请求路由的核心规则action-mappings是配置文件的心脏它定义了URL到处理逻辑的映射规则。每个action元素都是一条路由指令。action-mappings action path/user/login !-- 【必填】请求路径标识符 -- typecom.example.action.LoginAction !-- 【必填】处理类 -- nameloginForm !-- 【可选】关联的表单Bean名称 -- scoperequest !-- 【可选】表单Bean作用域默认request -- validatetrue !-- 【可选】是否校验默认true -- input/login.jsp !-- 【可选】校验失败或input转发目标 -- parametermethod !-- 【关键】用于动态方法调用 -- unknownfalse !-- 【可选】是否作为默认action -- cancellablefalse !-- 【可选】是否启用“取消”功能 -- !-- 局部转发定义优先级高于全局转发 -- forward namesuccess path/welcome.jsp/ forward namefailure path/login.jsp redirectfalse/ !-- 可以定义多个forward -- forward nameneedActivation path/activate.jsp/ /action !-- 使用动态表单的Action示例 -- action path/user/register typecom.example.action.RegisterAction namedynamicForm !-- 使用上面定义的动态表单 -- scoperequest validatetrue input/register.jsp forward namesuccess path/regSuccess.jsp/ /action !-- 转发Action不处理逻辑仅用于页面跳转 -- action path/goToAbout forward/aboutus.jsp/ /action-mappings核心属性解析path这是Action的“名字”对应URL中的一部分如/user/login.do。它是查找Action映射的关键。type负责处理请求的Action类。name与scope这对属性共同决定了表单数据的生命周期。name指定使用的form-beanscope决定这个Bean实例是存在于一次请求中request还是会话中session。对于多页表单如向导式注册使用session范围可以跨请求保存数据但务必注意在最后清理它。validate与input这是一对组合。当validate”true”时如果ActionForm.validate()返回错误请求会自动跳转到input指定的路径。这是实现“表单提交-校验失败-回显表单”模式的标准化流程。parameter这是实现动态方法调用DMI的关键属性我们将在下一章详细展开。forward属性如果action标签只有path和forward属性没有type那么这个Action就是一个“转发Action”。它不执行任何业务逻辑仅仅是将请求转发到指定的资源如JSP。这常用于简化对纯展示页面的访问。4.4 消息资源与控制器配置文件的最后部分通常用于国际化等高级功能。!-- 消息资源文件用于国际化(I18N)和集中管理提示信息 -- message-resources parametercom.example.ApplicationResources/ !-- 控制器配置用于定制ActionServlet的行为 -- controller processorClassorg.apache.struts.action.RequestProcessor maxFileSize10M tempDir/tmp/strutsuploads/ /struts-configmessage-resources这是Struts国际化的基石。它指向一个.properties文件如ApplicationResources.properties或ApplicationResources_zh_CN.properties。在JSP中你可以使用bean:message key”welcome.title”/来获取本地化的文本在ActionForm.validate()中可以使用ActionMessage来引用资源文件中的错误信息。这实现了界面文本与代码的分离。controller用于替换默认的RequestProcessor或调整其行为。例如你可以自定义一个Processor来处理文件上传的大小和临时目录如示例所示或者实现一些全局的预处理/后处理逻辑。通过这样逐层拆解struts-config.xml你会发现它就像一个精密的仪表盘每一个开关和旋钮都控制着框架行为的某个方面。熟练掌握这个文件的配置是成为Struts开发高手的必经之路。而其中最具技巧性的功能之一就是接下来要讲的动态方法调用。5. 动态方法调用一个Action如何化身“多面手”在基础的Struts模式中一个URL路径/login.do对应一个action配置进而对应一个Action类的execute()方法。如果一个模块有多个相似操作例如用户的增、删、改、查我们就需要配置多个action编写多个Action类即使它们可能共享大部分逻辑。这显然不够优雅会产生大量重复配置和类文件。动态方法调用Dynamic Method Invocation, DMI就是为了解决这个问题而生的。5.1 DMI的原理与配置启用DMI的核心思想是让一个Action类不再只有一个固定的execute()入口而是可以根据请求参数动态地调用其内部的任意一个公有方法。它的实现依赖于action标签中的一个关键属性parameter。同时请求URL中需要携带一个特定的参数来指明要调用的方法名。默认情况下Struts 1.3为了安全是禁用DMI的我们需要在struts-config.xml中显式启用它通常通过配置控制器controller !-- 关键配置启用动态方法调用 -- set-property propertyconfig/${org.apache.struts.action.DISPATCHER_FROM_PARAMETER?} valuetrue/ /controller更常见的做法是在struts-config.xml的action映射中直接利用parameter属性。其工作原理是在action中定义parameter”method”。在请求URL中附加参数如/user.do?methodadd。Struts框架会截取method参数的值这里是”add”。框架会尝试调用当前Action类中名为add、且签名与execute方法相同即返回ActionForward参数相同的公有方法。5.2 实战使用DMI重构用户管理模块假设我们有一个UserAction需要处理用户的增加、删除、修改、查询。不使用DMI时我们需要配置四个action/userAdd.do,/userDelete.do,/userUpdate.do,/userQuery.do并可能对应四个Action类。使用DMI后一切变得简洁。第一步编写多功能Action类UserAction.javapackage com.example.action; import org.apache.struts.action.*; public class UserAction extends Action { // 默认入口如果未指定method参数则调用此方法 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { return mapping.findForward(“menu”); // 比如跳转到用户管理菜单页 } // 新增用户 public ActionForward add(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UserForm userForm (UserForm) form; // 调用Service层执行新增逻辑 userService.addUser(userForm); request.setAttribute(“message”, “用户添加成功”); return mapping.findForward(“success”); } // 删除用户 public ActionForward delete(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String userId request.getParameter(“id”); userService.deleteUser(userId); request.setAttribute(“message”, “用户删除成功”); return mapping.findForward(“success”); } // 更新用户 public ActionForward update(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UserForm userForm (UserForm) form; userService.updateUser(userForm); request.setAttribute(“message”, “用户信息已更新”); return mapping.findForward(“success”); } // 查询用户 public ActionForward query(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String name request.getParameter(“name”); ListUser userList userService.queryUsers(name); request.setAttribute(“userList”, userList); return mapping.findForward(“list”); } }第二步简化struts-config.xml配置action-mappings !-- 一个Action配置覆盖所有用户相关操作 -- action path”/user” type”com.example.action.UserAction” name”userForm” scope”request” parameter”method” !-- 关键指定方法参数名为 method -- forward name”menu” path”/userMenu.jsp”/ forward name”success” path”/common/success.jsp”/ forward name”list” path”/userList.jsp”/ !-- input转发可用于所有方法的校验失败 -- forward name”input” path”/userEdit.jsp”/ /action /action-mappings第三步在JSP页面或链接中发起请求!-- 添加用户的链接 -- a href”user.do?methodadd”添加用户/a !-- 删除用户的表单通过隐藏域传递method和方法参数 -- form action”user.do” method”post” input type”hidden” name”method” value”delete”/ input type”hidden” name”id” value”${user.id}”/ input type”submit” value”删除”/ /form !-- 查询表单 -- form action”user.do” method”get” input type”hidden” name”method” value”query”/ 姓名input type”text” name”name”/ input type”submit” value”查询”/ /form5.3 DMI的优缺点与安全考量优点配置极大简化一个action配置替代了多个struts-config.xml文件变得非常清爽。代码更内聚相关功能集中在一个Action类中提高了代码的可维护性。减少类爆炸无需为每个简单操作创建单独的Action类。缺点与风险安全隐患最重要DMI本质上是根据用户输入的参数值来反射调用方法。如果攻击者构造一个请求如user.do?methodexecutesomeMaliciousParam...或者尝试调用类的其他公有方法如getServlet可能导致严重的安全漏洞。因此在Struts 1.3及以后版本中默认是禁用DMI的。方法签名限制被调用的方法必须具有与execute完全相同的签名这限制了灵活性。调试复杂度增加堆栈跟踪中的方法名是动态的可能给调试带来一些不便。安全实践建议谨慎评估在安全性要求不高的内部系统或经过严格输入过滤的情况下可以考虑使用。使用替代方案更安全的做法是使用“DispatchAction”或其子类如LookupDispatchAction,MappingDispatchAction。DispatchAction是Struts官方提供的、专门用于多方法处理的基类。它要求请求参数的值必须严格映射到Action类中预定义的方法名并且框架内部有更严格的检查机制比原始的DMI更安全。public class UserDispatchAction extends DispatchAction { public ActionForward add(...) { ... } public ActionForward delete(...) { ... } // 框架会确保只调用这些明确声明的方法 }输入过滤如果必须使用DMI务必在Action基类或过滤器中对method参数进行白名单校验只允许调用预定的方法名。动态方法调用体现了Struts框架在追求配置简洁和代码复用上所做的努力。它是一把双刃剑用好了能大幅提升开发效率用不好则会引入安全风险。理解其原理和边界能帮助你在合适的场景做出恰当的选择。