# 手写springmvc框架
**Repository Path**: ddfeiyu/framework-easy-springmvc
## Basic Information
- **Project Name**: 手写springmvc框架
- **Description**: 在Web应用程序设计中,MVC模式已经被广泛使用。
SpringMVC以DispatcherServlet为核心,负责协调和组织不同组件以完成请求处理并返回响应的工作,实现了MVC模式。
想要实现自己的SpringMVC框架,需要从以下几点入手: 一、了解SpringMVC运行流程及九大组件 二、梳理自己的SpringMVC的设计思路 三、实现自己的SpringMVC框架
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 4
- **Forks**: 1
- **Created**: 2021-06-27
- **Last Updated**: 2023-02-12
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# framework-easy-springmvc
#### 介绍
实现了IOC特性的SpringMVC
在Web应用程序设计中,MVC模式已经被广泛使用。
SpringMVC以DispatcherServlet为核心,负责协调和组织不同组件以完成请求处理并返回响应的工作,实现了MVC模式。
想要实现自己的SpringMVC框架,需要从以下几点入手:
一、了解SpringMVC运行流程及九大组件
二、梳理自己的SpringMVC的设计思路
三、实现自己的SpringMVC框架
#### 软件架构
软件架构说明
一、了解SpringMVC运行流程及九大组件
1、SpringMVC的运行流程

⑴ 用户发送请求至前端控制器DispatcherServlet
⑵ DispatcherServlet收到请求调用HandlerMapping处理器映射器。
⑶ 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
⑷ DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
⑸ 执行处理器(Controller,也叫后端控制器)。
⑹ Controller执行完成返回ModelAndView
⑺ HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
⑻ DispatcherServlet将ModelAndView传给ViewReslover视图解析器
⑼ ViewReslover解析后返回具体View
⑽ DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
⑾ DispatcherServlet响应用户。
从上面可以看出,DispatcherServlet有接收请求,响应结果,转发等作用。有了DispatcherServlet之后,可以减少组件之间的耦合度。
2、SpringMVC的九大组件
protected void initStrategies(ApplicationContext context) {
//用于处理上传请求。处理方法是将普通的request包装成
//MultipartHttpServletRequest,后者可以直接调用getFile方法获取File.
initMultipartResolver(context);
//SpringMVC主要有两个地方用到了Locale:
// 一是ViewResolver视图解析的时候;
// 二是用到国际化资源或者主题的时候。
initLocaleResolver(context);
//用于解析主题。
// SpringMVC中一个主题对应一个properties文件,
// 里面存放着跟当前主题相关的所有资源、//如图片、css样式等。SpringMVC的主题也支持国际化,
initThemeResolver(context);
//用来查找Handler的。
initHandlerMappings(context);
//从名字上看,它就是一个适配器。
// Servlet需要的处理方法的结构却是固定的,
//都是以request和response为参数的方法。
//如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?
//这就是HandlerAdapter要做的事情
initHandlerAdapters(context);
//其它组件都是用来干活的。
// 在干活的过程中难免会出现问题,出问题后怎么办呢?
//这就需要有一个专门的角色对异常情况进行处理,
//在SpringMVC中就是HandlerExceptionResolver。
initHandlerExceptionResolvers(context);
//有的Handler处理完后并没有设置View也没有设置ViewName,
//这时就需要从request获取ViewName了,
//如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。
initRequestToViewNameTranslator(context);
//ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。
//View是用来渲染页面的,也就是将程序返回的参数填入模板里,
//生成html(也可能是其它类型)文件。
initViewResolvers(context);
//用来管理FlashMap的,FlashMap主要用在redirect重定向中传递参数。
initFlashMapManager(context);
}
二、梳理SpringMVC的设计思路
本文只实现自己的@Controller、@RequestMapping、@RequestParam注解起作用,其余SpringMVC功能读者可以尝试自己实现。
1、读取配置

其中:
DispatcherServlet : org.springframework.web.servlet.DispatcherServlet : springmvc
FrameworkServlet:org.springframework.web.servlet.FrameworkServlet :springmvc
HttpServletBean:org.springframework.web.servlet.HttpServletBean : springmvc
org.springframework.context.ApplicationContextAware : spring-context
javax.servlet.http.HttpServlet: servlet-api
javax.servlet.GenericServlet: servlet-api
javax.servlet.Servlet: servlet-api
javax.servlet.ServletConfig: servlet-api
[Servlet到底是个什么东西?](http://Servlet到底是个什么东西?教你玩转Servlet详细解说!!)
关于 HttpServlet : [什么是HttpServlet](http://https://blog.csdn.net/qq_41007534/article/details/99696559)
从图中可以看出,SpringMVC本质上是一个Servlet,这个 Servlet 继承自 HttpServlet。
FrameworkServlet负责初始化SpringMVC的容器,并将Spring容器设置为父容器。
因为本文只是实现SpringMVC,对于Spring容器不做过多讲解。
为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息。
通过web.xml中加载我们自己写的MyDispatcherServlet和读取配置文件。
2、初始化阶段
在前面我们提到DispatcherServlet的initStrategies方法会初始化9大组件,但是这里将实现一些SpringMVC的最基本的组件而不是全部,按顺序包括:
2.1、加载配置文件
2.2、扫描用户配置包下面所有的类
2.3、拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写
2.4、实例化Spring中的依赖注入。
2.5、初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出
3、运行阶段
每一次请求将会调用doGet或doPost方法,所以统一运行阶段都放在doDispatch方法里处理,它会根据url请求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller中的url对应的方法,并得到结果返回。
按顺序包括以下功能:
3.1、异常的拦截
3.2、获取请求传入的参数并处理参数
3.3、通过初始化好的handlerMapping中拿出url对应的方法名,反射调用
三、实现自己的SpringMVC框架
工程文件及目录:

war包:

首先,新建一个maven项目,在pom.xml中导入以下依赖:
```
4.0.0
easy
easy-springmvc
1.0-SNAPSHOT
war
UTF-8
1.8
1.8
1.8
javax.servlet
javax.servlet-api
3.0.1
provided
com.alibaba
fastjson
1.2.35
MyEasySpringMVC
```
接着,我们在WEB-INF下创建一个web.xml,如下配置:
```
MyEasySpringMVC
easy.springmvc.servlet.MyEasyDispatcherServlet
contextConfigLocation
application.properties
1
MyEasySpringMVC
/*
```
application.properties文件中只是配置要扫描的包到SpringMVC容器中。
```
scanPackage=easy.test
```
创建自己的Controller注解,它只能标注在类上面:
```
package easy.springmvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) // 只能注解到类
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyEasyController {
/**
* controller注册别名
* @return
*/
String value() default "";
}
```
创建自己的RequestMapping注解,可以在类和方法上:
```
package easy.springmvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE ,ElementType.METHOD}) // 只能注解到类和方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyEasyRequestMapping {
/**
* 表示访问该方法的url
* @return
*/
String value() default "";
}
```
创建自己的RequestParam注解,可以在参数上:
```
package easy.springmvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER) // 只能注解到参数上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyEasyRequestParam {
/**
* 参数别名,必填
* @return
*/
String value();
}
```
创建自己的Autowired注解,可以在构造器。方法,成员变量上,目前实现了成员变量注解
```
package easy.springmvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) // 构造器。方法,成员变量
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyEasyAutowired {
/**
* @return
*/
String value() default "";
}
```
然后创建MyEasyDispatcherServlet这个类,去继承HttpServlet,
```
package easy.springmvc.servlet;
import com.alibaba.fastjson.JSON;
import easy.springmvc.annotation.MyEasyAutowired;
import easy.springmvc.annotation.MyEasyController;
import easy.springmvc.annotation.MyEasyRequestMapping;
import easy.springmvc.annotation.MyEasyRequestParam;
import easy.springmvc.annotation.MyEasyService;
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.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
/**
*
* 2.1、加载配置文件
* 2.2、扫描用户配置包下面所有的类
* 2.3、拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写
* 2.4、初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出
*/
public class MyEasyDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
private List classNames = new ArrayList<>();
private Map ioc = new HashMap<>();
private Map handlerMapping = new HashMap<>();
private Map controllerMap =new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
//1.加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//2.初始化所有相关联的类,扫描用户设定的包下面所有的类
String packageName = properties.getProperty("scanPackage");
System.out.println("MyEasyDispatcherServlet.doScanner packageName: "+packageName);
doScanner(packageName);
//3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean) beanName默认是首字母小写
doInstance();
// 4.实例化Spring中的依赖注入
doAutowired();
//5.初始化HandlerMapping(将url和method对应上)
initHandlerMapping();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
String method = req.getMethod();
System.out.println("MyEasyDispatcherServlet.doGet, url : "+url+", contextPath: "+contextPath+", method: "+method);
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
String method = req.getMethod();
System.out.println("MyEasyDispatcherServlet.doPost, url : "+url+", contextPath: "+contextPath+", method: "+method);
//处理请求
doDispatch(req,resp);
} catch (Exception e) {
resp.getWriter().write("500!! Server Exception");
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
if (handlerMapping.isEmpty()) {
return;
}
String url = req.getRequestURI();
String contextPath = req.getContextPath();// /MyEasySpringMVC,
String methodStr = req.getMethod();// GET POST
System.out.println("MyEasyDispatcherServlet.doDispatch, url : "+url+", contextPath: "+contextPath+", req method: "+methodStr);
url = url.replace(contextPath, "").replaceAll("/+", "/");
System.out.println("MyEasyDispatcherServlet.doDispatch, url : "+url);
if (!this.handlerMapping.containsKey(url) || "/favicon.ico".equals(url)) {
resp.getWriter().write("404 NOT FOUND!");
return;
}
Method method = this.handlerMapping.get(url);
System.out.println("MyEasyDispatcherServlet.doDispatch, url : "+url+", method: "+ method.getDeclaringClass() +"."+ method.getName());
//获取方法的参数列表
Class>[] parameterTypes = method.getParameterTypes();
System.out.println("MyEasyDispatcherServlet.doDispatch, url : "+url+", parameterTypes: "+ JSON.toJSONString(parameterTypes));
//获取请求的参数
Map parameterMap = req.getParameterMap();
System.out.println("MyEasyDispatcherServlet.doDispatch, url : "+url+", parameterMap: "+ JSON.toJSONString(parameterMap));
//保存参数值
Object [] paramValues = new Object[parameterTypes.length];
//方法的参数列表
for (int i = 0; i param : parameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
paramValues[i]=value;
}
}*/
}
//获取请求中的集合
for(Map.Entry entry : parameterMap.entrySet()){
String value = entry.getValue()[0];
String name = entry.getKey();
int index = hasRequestParam(method, name);
// ===hasRequestParam===请求参数是:param========hhh, index= 2
System.out.println("===hasRequestParam===请求参数是: ,name= " + name + ", value= " + value+", index= "+index);
if(index != -1){
paramValues[index] = value;
}else {
List names = getparamterNames(method);
System.out.println("===getparamterNames===请求参数是:names="+names+" ,name= " + name + ", value= " + value+", index= "+index);
for(int i = 0; i < names.size(); i ++){
if(name.equals(names.get(i))){
paramValues[i] = value;
break;
}
}
}
}
//利用反射机制来调用
try {
System.out.println("MyEasyDispatcherServlet.doDispatch, url : "+url+", contextPath: "+contextPath+", " +
"利用反射机制来调用 : method: "+ method.getDeclaringClass() +"."+ method.getName() +
" , paramValues: "+ Arrays.asList(paramValues));
method.invoke(this.controllerMap.get(url), paramValues);//第一个参数是method所对应的实例 在ioc容器中
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 判断控制器方法的参数,是否有RequestParam注解,且找到对应的value值,如果找到 返回这个参数的位置,如果没找到返回-1
* @param method
* @param name
* @return
*/
public int hasRequestParam(Method method, String name){
Parameter[] parameters = method.getParameters();
for(int i = 0; i < parameters.length; i ++){
Parameter p = parameters[i];
boolean b = p.isAnnotationPresent(MyEasyRequestParam.class);
if(b){
MyEasyRequestParam requestParam = p.getAnnotation(MyEasyRequestParam.class);
String requestParamValue = requestParam.value();
if(name.equals(requestParamValue)){
return i;
}
}
}
return -1;
}
/**
* 获取控制器方法的参数的名字
* @param method
* @return
*/
public List getparamterNames(Method method){
List list = new ArrayList();
Parameter[] parameters = method.getParameters();
for(Parameter parameter : parameters){
list.add(parameter.getName());
}
return list;
}
/**
* 加载配置文件
* @param location
*/
private void doLoadConfig(String location){
System.out.println("加载配置文件");
//把web.xml中的contextConfigLocation对应value值的文件加载到流里面
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
try {
//用Properties文件加载文件里的内容
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关流
if(null!=resourceAsStream){
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
StringBuilder prop = new StringBuilder();
Iterator propNameSetIt = properties.stringPropertyNames().iterator();
while (propNameSetIt.hasNext()){
String key = propNameSetIt.next();
Object value = properties.get(key);
prop.append("key: ").append(key).append(" , value: ").append(String.valueOf(value)).append("\n");
}
System.out.println("加载配置文件完毕: "+prop.toString());
}
}
/**
* 初始化所有相关联的类,扫描用户设定的包下面所有的类
* @param packageName
*/
private void doScanner(String packageName) {
//把所有的.替换成/
URL url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if(file.isDirectory()){
//递归读取包
doScanner(packageName+"."+file.getName());
}else{
String className = packageName +"." +file.getName().replace(".class", "");
classNames.add(className);
}
}
System.out.println("MyEasyDispatcherServlet.doScanner, classNames: "+ JSON.toJSONString(classNames));
}
/**
* 拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean) beanName默认是首字母小写
*/
private void doInstance() {
System.out.println("拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean) beanName默认是首字母小写");
if (classNames.isEmpty()) {
return;
}
for (String className : classNames) {
try {
// 读取类,通过反射来实例化(只有加@MyController需要实例化)
Class> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(MyEasyController.class)){
String classSimpleName = toLowerFirstWord(clazz.getSimpleName());
Object beanInstance = clazz.newInstance();
ioc.put(toLowerFirstWord(classSimpleName), beanInstance);
// ,beanInstance: "+beanInstance.toString()+
System.out.println("MyEasyDispatcherServlet.doInstance,【MyEasyController】 classSimpleName: "+classSimpleName+" ,beanInstance: "+beanInstance.toString());
}else if(clazz.isAnnotationPresent(MyEasyService.class)){
MyEasyService annotation = clazz.getAnnotation(MyEasyService.class);
String beanName = annotation.value();
Object beanInstance = clazz.newInstance();
if (Objects.equals("", beanName)) {
// 多个接口
Class>[] interfaces = clazz.getInterfaces();
for(Class> c1 : interfaces){
String classSimpleName = toLowerFirstWord(c1.getSimpleName());
ioc.put(classSimpleName, beanInstance);
System.out.println("MyEasyDispatcherServlet.doInstance,【MyEasyService】 classSimpleName: "+classSimpleName+" ,beanInstance: "+beanInstance.toString());
}
}else {
ioc.put(beanName,beanInstance);
}
}else{
continue;
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
Iterator> iocItor = ioc.entrySet().iterator();
while (iocItor.hasNext()){
Map.Entry e = iocItor.next();
String beanSimpleName = e.getKey();
Object beanInstance = e.getValue();
System.out.println("MyEasyDispatcherServlet.doInstance ,beanSimpleName: "+beanSimpleName+", beanInstance: "+beanInstance);
}
System.out.println("MyEasyDispatcherServlet.doInstance完成 ");
}
/**
* 实例化Spring中的依赖注入
*/
private void doAutowired(){
System.out.println("实例化Spring中的依赖注入--doAutowired--,ioc.size(): "+ioc.size());
if (ioc.size() ==0 ){
return;
}
Iterator> iocIter = ioc.entrySet().iterator();
while (iocIter.hasNext()){
// /*classSimpleName*/, Object /*Class instance*/
Map.Entry entry = iocIter.next();
String classSimpleName = entry.getKey();
Object beanInstance = entry.getValue();
// FIXME :目前实现成员变量的依赖注入
// TODO:后续处理构造器和方法
Field[] declaredFields = beanInstance.getClass().getDeclaredFields();
System.out.println("MyEasyDispatcherServlet.doAutowired, classSimpleName: "+classSimpleName+" ,beanInstance: "+beanInstance.toString()+", declaredFields size: "+declaredFields.length+" : "+Arrays.asList(declaredFields));
for (Field declaredField: declaredFields) {
if (declaredField.isAnnotationPresent(MyEasyAutowired.class)){
declaredField.setAccessible(true);
MyEasyAutowired myEasyAutowired = declaredField.getAnnotation(MyEasyAutowired.class);
String autowiredBeanName = myEasyAutowired.value();
if (Objects.equals("",autowiredBeanName) || Objects.equals(null, autowiredBeanName)){
Class> declaredFieldType = declaredField.getType();
autowiredBeanName = toLowerFirstWord(declaredFieldType.getSimpleName());
}
System.out.println("MyEasyDispatcherServlet.doAutowired, classSimpleName: "+classSimpleName+" ,beanInstance:"+beanInstance+" , declaredField: "+declaredField.toString()+", declaredField.hashCode(): "+declaredField.hashCode()+", autowiredBeanName: "+autowiredBeanName+", autowiredBeanInstance: "+ioc.get(autowiredBeanName));
try {
Object helloService = declaredField.get(beanInstance);
System.out.println("declaredField:"+declaredField+", 反射get helloService: "+helloService+", beanInstance: "+beanInstance);
declaredField.setAccessible(true);
declaredField.set(beanInstance, ioc.get(autowiredBeanName));
helloService = declaredField.get(beanInstance);
System.out.println("declaredField:"+declaredField+", 反射set helloService: "+helloService+", beanInstance: "+beanInstance);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 初始化HandlerMapping(将url和method对应上)
*/
private void initHandlerMapping(){
if(ioc.isEmpty()){
return;
}
try {
for (Map.Entry entry: ioc.entrySet()) {
Class extends Object> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(MyEasyController.class)){
continue;
}
//拼url时,是controller头的url拼上方法上的url
String baseUrl ="";
String classSimpleName = "";
Object beanInstance = null;
if(clazz.isAnnotationPresent(MyEasyRequestMapping.class)){
MyEasyRequestMapping annotation = clazz.getAnnotation(MyEasyRequestMapping.class);
baseUrl = annotation.value();
classSimpleName = toLowerFirstWord(clazz.getSimpleName());
beanInstance = ioc.get(classSimpleName);
}
System.out.println("MyEasyDispatcherServlet.initHandlerMapping, baseUrl: "+ baseUrl+", classSimpleName: "+classSimpleName+", beanInstance: "+beanInstance);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if(!method.isAnnotationPresent(MyEasyRequestMapping.class)){
continue;
}
MyEasyRequestMapping annotation = method.getAnnotation(MyEasyRequestMapping.class);
String url = annotation.value();
url = (baseUrl+"/"+url).replaceAll("/+", "/");
if (handlerMapping.containsKey(url)){
throw new Exception(" Ambiguous url :"+url);
}
handlerMapping.put(url, method);
// 注意,这里的beanInstance要和ioc容器中的beanInstance是同一个
controllerMap.put(url, beanInstance);
System.out.println("MyEasyDispatcherServlet.initHandlerMapping========url: "+url+" ,method: "+method+", beanInstance: "+beanInstance.toString());
}
}
System.out.println("MyEasyDispatcherServlet.initHandlerMapping完成, handlerMapping: "+ JSON.toJSONString(handlerMapping));
System.out.println("MyEasyDispatcherServlet.initHandlerMapping完成, controllerMap: "+ JSON.toJSONString(controllerMap));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 把字符串的首字母小写
* @param name
* @return
*/
public static String toLowerFirstWord(String name){
/* char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);*/
return name.substring(0,1).toLowerCase() + name.substring(1);
}
}
```
重写init方法、doGet、doPost方法,以及加上我们第二步分析时要实现的功能:
2.1、加载配置文件
2.2、扫描用户配置包下面所有的类
2.3、拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写
2.4、初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出
`
`
#### 安装教程
1. 启动tomcat ,进入manger页: http://localhost:8080/manager/html
2. tomcat部署war包

部署日志
```
28-Jun-2021 16:26:48.937 信息 [http-nio-8080-exec-17] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [E:\work-install\install\apache-tomcat-9.0.12\webapps\MyEasySpringMVC.war]
28-Jun-2021 16:26:49.014 信息 [http-nio-8080-exec-17] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
加载配置文件
加载配置文件完毕: key: scanPackage , value: easy.springmvc
MyEasyDispatcherServlet.doScanner, classNames: ["easy.springmvc.annotation.MyEasyController","easy.springmvc.annotation.MyEasyRequestMapping","easy.springmvc.annotation.MyEasyRequestParam"]
MyEasyDispatcherServlet.doScanner, classNames: ["easy.springmvc.annotation.MyEasyController","easy.springmvc.annotation.MyEasyRequestMapping","easy.springmvc.annotation.MyEasyRequestParam","easy.springmvc.servlet.MyEasyDispatcherServlet"]
MyEasyDispatcherServlet.doScanner, classNames: ["easy.springmvc.annotation.MyEasyController","easy.springmvc.annotation.MyEasyRequestMapping","easy.springmvc.annotation.MyEasyRequestParam","easy.springmvc.servlet.MyEasyDispatcherServlet","easy.test.TestController"]
拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean) beanName默认是首字母小写
MyEasyDispatcherServlet.doInstance完成, ioc: {"testController":{}}
/test/doTest,public void easy.test.TestController.doTest2(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
/test/doTest2,public void easy.test.TestController.doTest(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.lang.String)
MyEasyDispatcherServlet.initHandlerMapping完成, handlerMapping: {"/test/doTest":{"accessible":false,"annotatedExceptionTypes":[],"annotatedParameterTypes":[{"annotations":[],"declaredAnnotations":[],"type":"javax.servlet.http.HttpServletRequest"},{"annotations":[],"declaredAnnotations":[],"type":"javax.servlet.http.HttpServletResponse"}],"annotatedReceiverType":{"annotations":[],"declaredAnnotations":[],"type":"easy.test.TestController"},"annotatedReturnType":{"annotations":[],"declaredAnnotations":[],"type":"void"},"annotations":[{"value":"/doTest"}],"bridge":false,"declaringClass":"easy.test.TestController","default":false,"exceptionTypes":[],"genericExceptionTypes":[],"genericParameterTypes":["javax.servlet.http.HttpServletRequest","javax.servlet.http.HttpServletResponse"],"genericReturnType":"void","modifiers":1,"name":"doTest2","parameterAnnotations":[[],[]],"parameterCount":2,"parameterTypes":["javax.servlet.http.HttpServletRequest","javax.servlet.http.HttpServletResponse"],"returnType":"void","synthetic":false,"typeParameters":[],"varArgs":false},"/test/doTest2":{"accessible":false,"annotatedExceptionTypes":[],"annotatedParameterTypes":[{"annotations":[],"declaredAnnotations":[],"type":"javax.servlet.http.HttpServletRequest"},{"annotations":[],"declaredAnnotations":[],"type":"javax.servlet.http.HttpServletResponse"},{"annotations":[],"declaredAnnotations":[],"type":"java.lang.String"}],"annotatedReceiverType":{"annotations":[],"declaredAnnotations":[],"type":"easy.test.TestController"},"annotatedReturnType":{"annotations":[],"declaredAnnotations":[],"type":"void"},"annotations":[{"value":"/doTest2"}],"bridge":false,"declaringClass":"easy.test.TestController","default":false,"exceptionTypes":[],"genericExceptionTypes":[],"genericParameterTypes":["javax.servlet.http.HttpServletRequest","javax.servlet.http.HttpServletResponse","java.lang.String"],"genericReturnType":"void","modifiers":1,"name":"doTest","parameterAnnotations":[[],[],[{"value":"param"}]],"parameterCount":3,"parameterTypes":["javax.servlet.http.HttpServletRequest","javax.servlet.http.HttpServletResponse","java.lang.String"],"returnType":"void","synthetic":false,"typeParameters":[],"varArgs":false}}
MyEasyDispatcherServlet.initHandlerMapping完成, controllerMap: {"/test/doTest":{},"/test/doTest2":{}}
28-Jun-2021 16:26:49.135 信息 [http-nio-8080-exec-17] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [E:\work-install\install\apache-tomcat-9.0.12\webapps\MyEasySpringMVC.war] has finished in [198] ms
```
3. 访问 http://localhost:8080/MyEasySpringMVC/test/doTest2?param=test

访问 http://localhost:8080/MyEasySpringMVC/test/doTest

访问 http://localhost:8080/MyEasySpringMVC/test/doTest3

#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)