day07-SpringMVC底层机制简单实现-03
SpringMVC底层机制简单实现-03
7.任务6-完成控制器方法获取参数-@RequestParam
功能说明:自定义 @RequestParam 注解和方法参数名获取参数。
当浏览器访问 Handler 方法时,如果 url 带有参数,可以通过自定义的 @RequestParam 注解来获取该参数,将其值赋给 Handler 方法中该注解修饰的形参。如:
url=http://ip:port/web工程路径/monster/find?name=孙悟空
@RequestMapping(value = "/monster/find") public void findMonstersByName(HttpServletRequest request,HttpServletResponse response, @RequestParam(value = "name") String username) { //注解的 value 值要和 url 的参数名一致 //代码.... }
7.1分析
之前是通过自定义的前端控制器 MyDispatcherServlet 来完成分发请求:所有的请求都通过 doGet 和 doPost 来调用 executeDispatch() 方法,在 executeDispatch() 方法中,通过反射调用控制器的方法。
原先的 executeDispatch() 方法:
//编写方法,完成分发请求 private void executeDispatch(HttpServletRequest request, HttpServletResponse response) { MyHandler myHandler = getMyHandler(request); try { //如果 myHandler为 null,说明请求 url没有匹配的方法,即用户请求的资源不存在 if (myHandler == null) { response.getWriter().print("<h1>404 NOT FOUND</h1>"); } else {//匹配成功,就反射调用控制器的方法 myHandler.getMethod().invoke(myHandler.getController(), request, response); } } catch (Exception e) { e.printStackTrace(); } }
但是由于 Handler 业务方法的形参个数、种类的不同,因此在反射的时候要考虑目标方法形参多种形式的问题。
Method 类的 invoke() 方法如下,它支持可变参数。

因此解决办法是:将需要传递给目标方法的实参,封装到一个参数数组,然后以反射调用的方式传递给目标方法。
控制器方法用来接收前端数据的参数,除了request 和 response,其他参数一般都是使用 String 类型来接收的,因此目标方法形参可能有两种情况:
- HttpServletRequest 和 HttpServletResponse 参数
- 接收的是String类型的参数
- 指定 @RequestParam 的 String 参数
- 没有指定 @RequestParam 的 String 参数
因此需要将上述两种形参对应的实参分别封装到实参数组,进行反射调用:
怎么将需要传递给目标方法的实参,封装到一个参数数组?答:获取当前目标方法的所有形参信息,遍历这个形参数组,根据形参数组的下标索引,将实参填充到实参数组对应的下标索引中。
(1)将方法的 HttpServletRequest 和 HttpServletResponse 参数封装到参数数组
(2)将方法指定 @RequestParam 的 String 参数封装到参数数组
(3)将方法中没有指定 @RequestParam 的String 参数按照默认参数名封装到参数数组
7.2代码实现
(1)@RequestParam注解
package com.li.myspringmvc.annotation; import java.lang.annotation.*; /** * @author 李 * @version 1.0 * RequestParam 注解标注在目标方法的参数上,表示映射http请求的参数 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { String value() default ""; }
(2)MyDispatcherServlet 中修改 executeDispatch() 方法,并增加两个方法 getIndexOfRequestParameterIndex() 和 getParameterNames()。
部分代码:
//编写方法,完成分发请求 private void executeDispatch(HttpServletRequest request, HttpServletResponse response) { MyHandler myHandler = getMyHandler(request); try { //如果 myHandler为 null,说明请求 url没有匹配的方法,即用户请求的资源不存在 if (myHandler == null) { response.getWriter().print("<h1>404 NOT FOUND</h1>"); } else {//匹配成功,就反射调用控制器的方法 /** * 1.原先的写法为 myHandler.getMethod() * .invoke(myHandler.getController(), request, response); * 它的局限性是目标方法只能有两个形参: HttPServletRequest 和 HttPServletResponse * 2.改进:将需要request的实参,封装到一个参数数组,然后以反射调用的方式传递给目标方法 * 3.public Object invoke(Object obj, Object... args) */ //1.先获取目标方法的所有形参的参数信息 Class<?>[] parameterTypes = myHandler.getMethod().getParameterTypes(); //2.创建一个参数数组(对应实参数组),在后面反射调动目标方法时会用到 Object[] params = new Object[parameterTypes.length]; //遍历形参数组 parameterTypes,根据形参数组的信息,将实参填充到实参数组中 //步骤一:将方法的Request和Response参数封装到实参数组,进行反射调用 for (int i = 0; i < parameterTypes.length; i++) { //取出当前的形参的类型 Class<?> parameterType = parameterTypes[i]; //如果这个形参是 HttpServletRequest,将request填充到实参数组params //在原生的SpringMVC中,是按照类型来匹配的,这里为了简化就按照名称来匹配 if ("HttpServletRequest".equals(parameterType.getSimpleName())) { params[i] = request; } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) { params[i] = response; } } //步骤二:将 http请求的参数封装到 params数组中[要注意填充实参数组的顺序问题] // 获取http请求的参数集合 Map<String, String[]> // 第一个参数 String 表示 http请求的参数名, // 第二个参数 String[]数组,之所以为数组,是因为前端有可能传入像checkbox这种多选的参数 Map<String, String[]> parameterMap = request.getParameterMap(); // 遍历 parameterMap,将请求参数按照顺序填充到实参数组 params for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { //取出请求参数的名 String name = entry.getKey(); //取出请求参数的值(为了简化,只考虑参数是单值的情况,不考虑类似checkbox提交的数据) String value = entry.getValue()[0]; //找到请求的参数对应目标方法的形参的索引,然后将其填充到实参数组 //1.[请求参数名和 @RequestParam 注解的 value值 匹配] int indexOfRequestParameterIndex = getIndexOfRequestParameterIndex(myHandler.getMethod(), name); if (indexOfRequestParameterIndex != -1) {//找到了对应位置 //将请求参数的值放入实参数组中 params[indexOfRequestParameterIndex] = value; } else { //没有在目标方法的形参数组中找到对应的下标位置 //2.使用默认机制进行匹配 [即请求参数名和形参名匹配] // (1)拿到目标方法的所有形参名 List<String> parameterNames = getParameterNames(myHandler.getMethod()); // (2)对形参名进行遍历,如果匹配,把当前请求的参数值填充到实参数组的相同索引位置 for (int i = 0; i < parameterNames.size(); i++) { //如果形参名和请求的参数名相同 if (name.equals(parameterNames.get(i))) { //将请求的参数的value值放入实参数组中 params[i] = value; break; } } } } myHandler.getMethod().invoke(myHandler.getController(), params); } } catch (Exception e) { e.printStackTrace(); } } /** * 编写方法,返回请求参数是目标方法的第几个形参 * [请求参数名和 @RequestParam 注解的 value值 匹配] * * @param method 目标方法 * @param name 请求的参数名 * @return 返回请求的参数匹配目标方法形参的索引位置 */ public int getIndexOfRequestParameterIndex(Method method, String name) { //得到 method的所有形参参数 Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { //取出当前的形参 Parameter parameter = parameters[i]; //先处理前面有 @RequestParam 注解修饰的形参 if (parameter.isAnnotationPresent(RequestParam.class)) { //取出当前形参parameter的注解 @RequestParam的 value值 String value = parameter.getAnnotation(RequestParam.class).value(); //将请求的参数和注解指定的value匹配,如果相同就说明找到了目标方法的形参位置 if (name.equals(value)) { return i;//返回的是匹配的形参的位置 } } } return -1;//如果没有匹配成功,就返回-1 } /** * 编写方法,得到目标方法的所有形参的名称,并放入到集合中返回 * * @param method * @return */ public List<String> getParameterNames(Method method) { ArrayList<String> paramNamesList = new ArrayList<>(); //获取到所有的参数名--->这里有一个细节 //默认情况下 parameter.getName() 返回的的名称不是真正的形参名 request,response,name... //而是 [arg0, arg1, arg2...] //这里我们使用java8的特性,并且在pom.xml文件中配置maven编译插件,才能得到真正的名称 Parameter[] parameters = method.getParameters(); //遍历parameters,取出名称,放入 paramNamesList for (Parameter parameter : parameters) { paramNamesList.add(parameter.getName()); } System.out.println("目标方法的形参参数列表名称=" + paramNamesList); return paramNamesList; }
(3)在pom.xml文件中引入插件
点击 maven 管理,clean 项目,再重启一下 tomcat,防止引入出现问题
<build> <pluginManagement> <plugins> <plugin>...</plugin> <plugin>...</plugin> <!--引入插件--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>1.8</source> <target>1.8</target> <compilerArgs> <arg>-parameters</arg> </compilerArgs> <encoding>utf-8</encoding> </configuration> </plugin> </plugins> </pluginManagement> </build>
(4)编写方法测试
MonsterService 接口:
package com.li.service; import com.li.entity.Monster; import java.util.List; /** * @author 李 * @version 1.0 */ public interface MonsterService { //增加方法,通过传入的名字返回 monster列表 public List<Monster> findMonsterByName(String name); }
MonsterServiceImpl 实现类:
package com.li.service.impl; import com.li.entity.Monster; import com.li.myspringmvc.annotation.Service; import com.li.service.MonsterService; import java.util.ArrayList; import java.util.List; /** * @author 李 * @version 1.0 * MonsterServiceImpl 作为一个Service对象注入容器 */ @Service public class MonsterServiceImpl implements MonsterService { @Override public List<Monster> findMonsterByName(String name) { //这里模拟到 DB获取数据 List<Monster> monsters = new ArrayList<>(); monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400)); monsters.add(new Monster(200, "猫妖", "撕咬", 800)); monsters.add(new Monster(300, "鼠精", "偷灯油", 200)); monsters.add(new Monster(400, "大象精", "运木头", 300)); monsters.add(new Monster(500, "白骨精", "吐烟雾", 500)); //创建集合返回查询到的monster集合 List<Monster> findMonsters = new ArrayList<>(); //遍历monster集合,将符合条件的放到findMonster集合中 for (Monster monster : monsters) { if (monster.getName().contains(name)) { findMonsters.add(monster); } } return findMonsters; } }
MonsterController 控制器类:
package com.li.controller; import com.li.entity.Monster; import com.li.myspringmvc.annotation.AutoWired; import com.li.myspringmvc.annotation.Controller; import com.li.myspringmvc.annotation.RequestMapping; import com.li.myspringmvc.annotation.RequestParam; import com.li.service.MonsterService; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.List; /** * @author 李 * @version 1.0 * 用于测试的 Controller */ @Controller public class MonsterController { //属性 @AutoWired private MonsterService monsterService; //增加方法,通过name返回对应的monster集合 @RequestMapping(value = "/monster/find") public void findMonsterByName(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "name") String monsterName) { //设置编码 response.setContentType("text/html;charset=utf-8"); System.out.println("----接收到的name=" + monsterName); StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>"); content.append("<table border='1px' width='400px' style='border-collapse:collapse'>"); //调用 monsterService的方法 List<Monster> monsters = monsterService.findMonsterByName(monsterName); for (Monster monster : monsters) { content.append("<tr>" + "<td>" + monster.getId() + "</td>" + "<td>" + monster.getName() + "</td>" + "<td>" + monster.getSkill() + "</td>" + "<td>" + monster.getAge() + "</td></tr>"); } content.append("</table>"); //获取writer,返回提示信息 try { PrintWriter printWriter = response.getWriter(); printWriter.print(content.toString()); } catch (IOException e) { e.printStackTrace(); } } }
(5)重启 tomcat,浏览器访问url= http://localhost:8080/li_springmvc/monster/find?name=牛魔王
,显示如下:

后端输出:
----接收到的name=牛魔王
情况二:如果目标方法没有使用 @RequestParam 注解修饰:

redeployTomcat,访问相同的url,仍然可以接收到参数,并显示页面。测试成功。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!