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 =