- 手写 MVC 框架
- 写MVC框架之注解开发
- 1、创建项目
- 2、删除build里面的内容,换为tomcat
- 3、添加文件夹并设置
- ">
- 4、因为要写web项目,需要用到原生的api
- 5、创建框架类
- 6、创建了SlDispatcherServlet类,那么在web.xml配置文件中就可以对它进行调用
- 7、创建注解(需要写四个注解)
- 8、SlDispatcherServlet 要重新Servlet方法,并且初始化很多方法
- 9、根据第八步的1,需要创建配置文件
- 10、在web.xml中指定SlDispatcherServlet类的初始化参数,为了将配置文件的值传入
- 11、先将SlDispatcherServlet初始化需要的方法都创建出来
- 12、创建对应的包以及类,方便后续的解析
- 13、初始化各个步骤的方法的实现如下
- 14、需要进行两项配置
- 15、访问测试
- 16、定义一个类来存储更多的东西
- 17、在initHandlerMapping()中存的不仅仅是映射关系,还需整个handler方法
- 18、完善post请求
- 19、修改编译的插件
- 20、还需要重新编译一次,&测试
手写 MVC 框架
回顾SpringMVC执行的大致原理,后续根据这个模仿收写自己的mvc框架
流程图
写MVC框架之注解开发
1、创建项目
2、删除build里面的内容,换为tomcat
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source><!--jdk是11版本-->
<maven.compiler.target>11</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
3、添加文件夹并设置
4、因为要写web项目,需要用到原生的api
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
5、创建框架类
package com.shanglin.edu.mvcframework.servlet;
import javax.servlet.http.HttpServlet;
/**
* @Description:
* @Author: shanglin
* @Date: 2020-08-13 19:58
*/
public class SlDispatcherServlet extends HttpServlet {
}
6、创建了SlDispatcherServlet类,那么在web.xml配置文件中就可以对它进行调用
<!--刚刚已经写了servlet,然后这里就可以进行调用了
SlDispatcherServlet会去加载配置文件,配置文件会去扫描注解-->
<servlet>
<servlet-name>SlMVC</servlet-name>
<servlet-class>com.shanglin.edu.mvcframework.SlDispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SlMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
7、创建注解(需要写四个注解)
// 注解的使用范围
@Target:注解的作用目标
@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包
a、创建controller注解
package com.shanglin.edu.mvcframework.annotation;
import java.lang.annotation.*;
@Documented // 可以被java文档的编辑工具所读取
@Target(ElementType.TYPE) // 因为Controller是添加到类上的,所以是ElementType.TYPE
@Retention(RetentionPolicy.RUNTIME) // 指定它的生存周期为运行时(jvm加载到内存中也是有效的)
public @interface SlController {
String value() default "";
}
b、创建service注解
package com.shanglin.edu.mvcframework.annotations;
import java.lang.annotation.*;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SlService {
String value() default "";
}
c、创建RequestMapping注解
package com.shanglin.edu.mvcframework.annotations;
import java.lang.annotation.*;
@Documented
@Target({ElementType.TYPE,ElementType.METHOD}) // 不仅可以添加到类上,还能添加到方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface SlRequestMapping {
String value() default "";
}
d、创建autowired注解
**
package com.shanglin.edu.mvcframework.annotations;
import java.lang.annotation.*;
@Documented
@Target(ElementType.FIELD) // 添加到字段上
@Retention(RetentionPolicy.RUNTIME)
public @interface SlAutowired {
String value() default "";
}
8、SlDispatcherServlet 要重新Servlet方法,并且初始化很多方法
package com.shanglin.edu.mvcframework.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description: 重写一下Servlet里面的方法,因为创建了注解,还需要对注解进行扫描
* 所以还是有很多方法要进行初始化的,所以要init方法
* @Author: shanglin
* @Date: 2020-08-13 19:58
*/
public class SlDispatcherServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
// 加载配置文件
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求
}
}
初始化里面具体的步骤
@Override
public void init(ServletConfig config) throws ServletException {
// 1、加载配置文件 springmvc.properties
// 2、扫描相关的类、扫描注解
// 3、初始化bean对象(实现ioc容器,基于注解)
// 4、实现依赖注入
// 5、构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系
System.out.println("mvc 初始化完成");
// 等待请求进入,处理请求
}
9、根据第八步的1,需要创建配置文件
scanPackage=com.shanglin.demo
10、在web.xml中指定SlDispatcherServlet类的初始化参数,为了将配置文件的值传入
11、先将SlDispatcherServlet初始化需要的方法都创建出来
疑问一:scanPackage如何来。
@Override
public void init(ServletConfig config) throws ServletException {
// 1、加载配置文件 springmvc.properties
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
doLoadConfig(contextConfigLocation);
// 2、扫描相关的类、扫描注解
doScan(properties.getProperty("scanPackage"));
// 3、初始化bean对象(实现ioc容器,基于注解)
doInstance();
// 4、实现依赖注入
doAutoWired();
// 5、构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系
initHandlerMapping();
System.out.println("mvc 初始化完成");
// 等待请求进入,处理请求
}
// 构造一个HandlerMapping处理器映射器
private void initHandlerMapping() {
}
// 实现依赖注入
private void doAutoWired() {
}
// ioc容器
private void doInstance() {
}
// 扫描类
private void doScan(String scanPackage) {
}
// 加载配置文件
private void doLoadConfig(String contextConfigLocation) {
}
12、创建对应的包以及类,方便后续的解析
创建Controller类
@SlController
public class DemoController {
}
创建Service接口
public interface IDemoService {
String get(String name);
}
创建Service接口的实现类
@SlService
public class DemoServiceImpl implements IDemoService {
@Override
public String get(String name) {
System.out.println("service 实现类中的name参数"+name);
return name;
}
}
Controller需要使用service层,需要添加接口,通过注解注入到Controller层
@SlController
@SlRequestMapping("/demo")
public class DemoController {
// 在controller层完成service层的调用,通过注解SlAutowired将对象注入进去
@SlAutowired
private IDemoService demoService;
// 添加一个查询的方法,name等都传进来
@SlRequestMapping("/query")
public String query(HttpServletRequest request, HttpServletResponse response,String name){
return demoService.get(name);
}
}
13、初始化各个步骤的方法的实现如下
加载方法
// 加载配置文件
private void doLoadConfig(String contextConfigLocation) {
// 先将读取进来配置文件转为字节输入流
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
// 将字节输入流加载到内存中,并且存储到一个properties容器中
try {
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
扫描方法(将磁盘文件扫描出来)需要深入!!!
根据扫描的包名,通过当前线程的路径,得到扫描包的物理路径
根据物理路径,读取出物理路径下的文件到file中
如果是空则继续递归,如果非空,有类,则包名+文件名去后缀,就是全限定类名
// 将扫描到的类的全限定类名,缓存起来
private List<String> classNames = new ArrayList<>();
// 扫描类 传入的参数 package——> 在磁盘上以文件夹展示(File) com/shanglin/demo
// 将.转为/才可以去访问文件夹里面的文件 scanPackage=com.shanglin.demo
// 包下面可能有子包,子包下面还可能有子包,所以肯定有用到递归的
private void doScan(String scanPackage) {
// 因为包的开头是以classpath开头,所以要从classpath去拿到磁盘路径。
// getResource是空的时候拿到的就是classpath的路径
// 磁盘路径找到了,还需要找到文件,文件是com/shanglin/demo形式的,所以要将.替换成/。
String scanPackagePath= Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
// 借助File,读取路径,转为文件
File pack = new File(scanPackagePath);
// 读出的是一个数组,将文件读取出来
File[] files = pack.listFiles();
// 遍历数组
for (File file : files) {
// 如果存在子包
if(file.isDirectory()){
// 则继续递归
doScan(scanPackage+"."+file.getName()); // 如: com.shanglin.demo.controller
}else if(file.getName().endsWith(".class")){
// 如果是.class结尾就是java类了,那么下面就是获取它的权限定类名 包名+类名 类名的后缀去掉
String className = scanPackage + "." + file.getName().replaceAll(".class", "");
classNames.add(className);
}
}
}
实例化对象,存入id和对象的映射关系
全限定类名,通过反射创建对象(返回一个类) ——》类的注解从类上判断
判断对应的注解,取出对应的value做id,如果没有则用类名小写做id
然后将id,反射回来的对象,一起存入容器中(什么样的beanName对应什么样的Object)
如果该类还存在接口,将其所有接口也存入容器中( 该类内注入的接口)
// 需要一个容器,将他们保存下来
private Map<String,Object> iocMap= new HashMap<String,Object>();
// ioc容器
// 基于classNames缓存的类的全限定类名,以及反射技术,完成对象创建和管理
private void doInstance() {
try {
if(classNames.size() == 0) return;
for (int i = 0; i < classNames.size(); i++) {
// className好比com.shanglin.demo.controller.DemoController
String className = classNames.get(i);
// 将获取到的全限定类名,通过反射技术,创建对象
// JVM查找并加载指定的类,并且返回的是一个类
Class<?> aClass = Class.forName(className);
// 区分Controller,区分service
if(aClass.isAnnotationPresent(SlController.class)){
// controller的id此处不做过多处理,不取value,只拿类的首字母小写作为id,保存到ioc中
//获取类的简写名称,也就是类名,如DemoController
String simpleName = aClass.getSimpleName();
// 将类名转为首字母小写
String lowerFirstSimpleName = lowerFirst(simpleName);
// 通过反射创建新的类,默认调用无参的构造函数,而且被调用的构造函数是可见的
// 使用类加载机制,通过newInstance调用无参构造函数初始化一个类,生成一个实例
Object o = aClass.newInstance();
// 把对象和简写后的名字放入ioc容器进行保存(beanName,Object)
iocMap.put(lowerFirstSimpleName,o);
// 到此完成了Controller对象的实例化和管理
}else if(aClass.isAnnotationPresent(SlService.class)){ // 判断service层
// 先获取注解
SlService annotation = aClass.getAnnotation(SlService.class);
// 获取注解里面的value值,他的值就是id,也就是beanName
String beanName = annotation.value();
// 如果不为空就是指定了id,就以指定的id为主
if(!"".equals(beanName.trim())){
iocMap.put(beanName,aClass.newInstance()); // 实体化一个对象
}else{
// 如果没有指定,就以类名首字母小写
beanName = lowerFirst(aClass.getSimpleName());
iocMap.put(beanName,aClass.newInstance());
}
// service层往往是有接口的,面向接口开发,如果有接口,此时再以接口名为id,
// 放入一份对象到iocMap中,便于后期根据接口类型注入
Class<?>[] interfaces = aClass.getInterfaces();
// 遍历接口
for (int j = 0; j < interfaces.length; j++) {
Class<?> anInterface = interfaces[j];
// 以接口的全限定类名作为id放入iocMap,实例化一个对象
iocMap.put(anInterface.getName(),aClass.newInstance());
}
}else{
// 如果还有其他的继续即可
continue;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
依赖注入(需要深入!!!)
遍历映射集合,通过对象获取其类型,通过类型获取其字段信息 ——》字段上的注解,从字段上判断
遍历其字段信息,看是否有对应的注解
如果有对应注解则取此value,如果无value,就取字段名
给相关对象,绑定实例
// 实现(基于注解的)依赖注入
// 先查看有没有SlAutowired这个注解,如果有指向的是哪个进行注入
private void doAutoWired() {
// 如果为空的则直接返回
if(iocMap.isEmpty()){return;}
// 如果不为空,则为有对象,则要进行依赖注入处理。不知道有没有注入,先要遍历容器
// 遍历iocMap中所有对象,查看对象中的字段,是否有@SlAutowired注解,
//如果有需要维护依赖注入关系
for (Map.Entry<String,Object> entry: iocMap.entrySet()){
// 获取bean对象中的字段信息
// .getValue()拿到对象Object,.getClass()拿到类型,.getDeclaredFields()拿到字段信息
Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
// 遍历判断处理
for (int i = 0; i < declaredFields.length; i++) {
// 遍历这个字段
Field declaredField = declaredFields[i]; //@SlAutowired private IDemoService demoService;
// 判断有没有SlAutowired这个注解,如果不存在注解
if(!declaredField.isAnnotationPresent(SlAutowired.class)){
continue;
}
// 如果有SlAutowired这个注解 // 就获取到这个注解
SlAutowired annotation = declaredField.getAnnotation(SlAutowired.class);
// 再获取注解的value ,这个value就是所依赖bean的id
String beanName = annotation.value(); // 需要注入bean的id
if("".equals(beanName.trim())){
// 没有配置具体的bean id,那就需要根据当前字段类型注入(接口注入) IDemoService
// declaredField.getType() 就是IDemoService
beanName = declaredField.getType().getName();
}
// 开启赋值
declaredField.setAccessible(true);
try {
// 给那个对象entry.getValue(),传递iocMap.get(beanName)
declaredField.set(entry.getValue(),iocMap.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
// 到此,依赖注入维护完毕
}
映射关系
从ioc实例化容器中取出全限定类名,通过全限定类名扫描所有注解或方法
判断有要处理的参数注解,取出其value值,
同样的方法,取出所有方法的上面对应注解的值
将类上面的值+方法的值就是=》要访问的方法路径
// 添加一个handlerMapping来保存方法
private Map<String,Object> handlerMapping=new HashMap<String ,Object>();
// 构造一个HandlerMapping处理器映射器
// 因为全限定类名有了, 通过全限定类名获取Class类型,通过Class类型获取其所有方法,
//有了方法就可以扫描方法上面的注解。Controller类上面的注解也可以扫描到
// 然后获取注解里面的参数,将两个参数拼接起来就OK了。
// 目的:就是将Url和method建立关联
private void initHandlerMapping() {
// 先判断容器是否为空
if(iocMap.isEmpty()){return;}
// 如果不为空,扫描ioc容器当中对象的所有的方法,看看扫描到的方法上是否有注解RequestMapping,
// 如果有再找一下它对应url,然后建立他们的映射
for (Map.Entry<String,Object> entry: iocMap.entrySet()){
// 获取iocMap中当前遍历的对象的class类型 。
// entry.getValue()得到对象, .getClass()得到类型
Class<?> aClass = entry.getValue().getClass();
// 只处理Controller层。所以如果不包含SlController注解的,就不处理
if(!aClass.isAnnotationPresent(SlController.class)){ continue; }
// 如果Controller层包含SlController注解,则进入处理。
//要判断类是否有第一个根RequestMapping。
String beanUrl = "";
if(aClass.isAnnotationPresent(SlRequestMapping.class)){
// 取出这个注解
SlRequestMapping annotation = aClass.getAnnotation(SlRequestMapping.class);
// 取出注解里面的参数
// 等同于取出了 Controller 层的第一个 /demo
beanUrl = annotation.value();
}
// 前缀有了,还需要获取方法,获取方法上面的注解,获取注解里面的value
Method[] methods = aClass.getMethods();
// 遍历方法
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
// 对方法进行判断,如果method方法中没有标识SlRequestMapping就不处理
if(!method.isAnnotationPresent(SlRequestMapping.class)){ continue; }
// 如果标识有就处理
SlRequestMapping annotation = method.getAnnotation(SlRequestMapping.class);
String mothodUrl = annotation.value(); // 将方法的参数解析出来 如: /query
// 然后将类的参数路径 + 方法的参数路径 = 才是访问的参数路径
String Url= beanUrl+ mothodUrl;
// 建立url和method直接的映射关系(map缓存起来)
handlerMapping.put(Url,method);
}
}
}
14、需要进行两项配置
1、需要把web.xml中的classpath*: 去掉,因为读取的时候,并没有对它进行解析,而是直接读取文件了。
2、添加使用范围
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--添加使用范围,因为在服务器中会直接使用Tomcat,而不使用它
如果不添加范围,会引起冲突的-->
<scope>provided</scope>
</dependency>
到此,初始化开发基本完成
**
15、访问测试
16、定义一个类来存储更多的东西
// 1、不仅仅要存储url和method之间的对应关系。而且需要存储更多的东西,所以定义一个类来存储
// 2、因为Controller下面的方法都叫handler,所以定义个handler实体
package com.shanglin.demo.pojo;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* @Description: 封装handler方法相关的信息
* 1、要存储url和method之间的对应关系。而且还需要存储更多的东西,所以定义一个类来存储
* 2、因为Controller下面的方法都叫handler,所以定义个handler实体
* @Author: shanglin
* @Date: 2020-08-15 9:31
*/
public class Handler {
// 因为method方法在controller类当中,controller对象当中。
//调用是传入对象的。 method.invoke(obj,); 这里调用也需要对象
private Object controller;
// 就是method方法
private Method method;
// spring中的url是支持正则的(和它匹配的url)
private Pattern pattern;
//存参数顺序,因为每个方法都要传参数并且有顺序,为了进行参数绑定,
//key是参数名,value代表是第几个参数<name,2>
private Map<String,Integer> parmIndexMapping;
public Handler(Object controller, Method method, Pattern pattern) {
this.controller = controller;
this.method = method;
this.pattern = pattern;
this.parmIndexMapping = new HashMap<>();
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public Map<String, Integer> getParmIndexMapping() {
return parmIndexMapping;
}
public void setParmIndexMapping(Map<String, Integer> parmIndexMapping) {
this.parmIndexMapping = parmIndexMapping;
}
}
17、在initHandlerMapping()中存的不仅仅是映射关系,还需整个handler方法
(需要深入)
private List<Handler> handlerMapping=new ArrayList<>();
// 构造一个HandlerMapping处理器映射器
// 因为全限定类名有了, 通过全限定类名获取Class类型,通过Class类型获取所有方法,有了方法就可以扫描方法上面的注解
// 然后获取注解里面的参数,将两个参数拼接起来就OK了。
// 目的:就是将Url和method建立关联
private void initHandlerMapping() {
// 先判断容器是否为空
if(iocMap.isEmpty()){return;}
// 如果不为空,扫描ioc容器当中对象的所有的方法,看看扫描到的方法上是否有注解RequestMapping,
// 如果有再找一下它对应url,然后建立他们的映射
for (Map.Entry<String,Object> entry: iocMap.entrySet()){
// 获取iocMap中当前遍历的对象的class类型 。entry.getValue()得到对象,.getClass()得到类型
Class<?> aClass = entry.getValue().getClass();
// 只处理Controller层。所以如果不包含SlController注解的,就不处理
if(!aClass.isAnnotationPresent(SlController.class)){ continue; }
// 如果Controller层包含SlController注解,则进入处理。要判断类是否有第一个根RequestMapping。
String beanUrl = "";
if(aClass.isAnnotationPresent(SlRequestMapping.class)){
// 取出这个注解
SlRequestMapping annotation = aClass.getAnnotation(SlRequestMapping.class);
// 取出注解里面的参数
beanUrl = annotation.value(); // 等同于取出了 Controller 层的第一个 /demo
}
// 还需要获取方法
Method[] methods = aClass.getMethods();
// 遍历方法
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
// 对方法进行判断,如果method方法中没有标识SlRequestMapping就不处理
if(!method.isAnnotationPresent(SlRequestMapping.class)){ continue; }
// 如果标识有就处理
SlRequestMapping annotation = method.getAnnotation(SlRequestMapping.class);
String mothodUrl = annotation.value(); // 将方法的参数解析出来 如: /query
// 然后将类的参数路径 + 方法的参数路径 = 才是访问的参数路径
String Url= beanUrl+ mothodUrl;
// 将method的所有信息以及url封装为一个handler,然后存到list里面
Handler handler = new Handler(entry.getValue(), method, Pattern.compile(Url));
// 计算方法的参数位置信息 query(HttpServletRequest request, HttpServletResponse response,String name)
Parameter[] parameters = method.getParameters(); // 先要获取method方法所有参数
for (int j = 0; j < parameters.length; j++) {
Parameter parameter = parameters[j]; // 将参数取出
if(parameter.getType() == HttpServletRequest.class|| parameter.getType() == HttpServletResponse.class){
// 如果是 Request和response对象,那么参数名称写HttpServletRequest或者HttpServletResponse
handler.getParmIndexMapping().put(parameter.getType().getSimpleName(),j);
}else{
handler.getParmIndexMapping().put(parameter.getName(),j);
}
}
// 建立url和method直接的映射关系(map缓存起来)
handlerMapping.add(handler);
}
}
}
18、完善post请求
pop.xml中需要添加依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求:根据url,找到对应的Method方法,进行调用
// 1、先获取uri
//String requestURI = req.getRequestURI();
// 2、获取到一个反射方法
//Method method = (Method) handlerMapping.get(requestURI);
// 3、反射调用、需要传入对象,需要传入参数,此处无法完成调用,因为没有把对象缓存起来,也没有参数
// 所以还需要改造initHandlerMapping()。因为method的调用方法如: method.invoke(对象,参数)
// method.invoke()
// 根据uri获取到能够处理当前请求的handler(从handlerMapping中获取 它其实也就是一个list)
Handler handler = getHandler(req);
// 如果handler是空的反应404
if(handler == null){
resp.getWriter().write("404 not found");
return;
}
// 如果不为空。需要绑定参数
// 先要获取所有参数类型数组,这个数组长度就是后面要传入的args数组的长度
Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
// 根据数组长度创建一个新数组,(参数数组,是要传入反射调用的)
Object[] paraValues = new Object[parameterTypes.length];
// 然后需要向参数数组中填值,而且还需要保证顺序和方法中的一致
// 参数是前端传递过来的,所以需要获取所有的参数
Map<String, String[]> parameterMap = req.getParameterMap();
// 对所有的参数进行遍历(因为同一个参数名可能的值就有多个)
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
// name=1&name=2 name[1,2] 将参数以指定的字符拼接起来
String value = StringUtils.join(param.getValue(), ",");
// 如果这些参数和方法的参数匹配上,就填充数据
// 如果不包含某个参数,则继续循环
if(!handler.getParmIndexMapping().containsKey(param.getKey())){ continue;}
// 方法形参确实有该参数名,找到他的索引位置,对应的把参数值放入到paraValues
// 因为parmIndexMapping存的就是key=参数名和value=索引位置,
Integer index = handler.getParmIndexMapping().get(param.getKey());
// 把前台传递过来的参数填充到对应的位置去
paraValues[index]=value;
// 如果是Request response
Integer reqIndex = handler.getParmIndexMapping().get(HttpServletRequest.class.getSimpleName());
paraValues[reqIndex]=req;
Integer respIndex = handler.getParmIndexMapping().get(HttpServletResponse.class.getSimpleName());
paraValues[respIndex]=resp;
// 到此paraValues参数的数组拼装完成
// 最终调用handler的method属性
try {
handler.getMethod().invoke(handler.getController(),paraValues);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
// 单独创建一个方法
private Handler getHandler(HttpServletRequest request) {
// 先判断请求是否为空
if (handlerMapping.isEmpty()) {
return null;
}
// 如果不为空,获取uri
String url = request.getRequestURI();
// 遍历handlerMapping集合,取出里面的url进行对比
for (Handler handler : handlerMapping) {
// 调用匹配方法看是否匹配url
Matcher matcher = handler.getPattern().matcher(url);
// 如果不匹配则继续循环下一个
if (!matcher.matches()) {
continue;
}
// 如果匹配就返回
return handler;
}
return null;
}
19、修改编译的插件
<!--编译插件定义编译细节,当编译器编译的时候把真实的形参赋上去-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>utf-8</encoding>
<!--编译器编译的时候记录下形参的真实名称-->
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
20、还需要重新编译一次,&测试
参数能正常过去,而且没有异常。到此手写mvc已经OK