一、Git项目地址:
地址:https://github.com/kobeyk/handwriting-springmvc.git
网上可以搜到很多,大致思路和手写步骤如下:
1、仿Spring注解,自定义一套属于自己的注解,如仿写常用的@Controller、@Service、@AutoWired等
2、自定义DispatcherServlet类,继承HttpServlet类,重写几个方法,如servlet的初始化方法init()
3、在init方法中,做一些Servlet容器初始化的工作:
3.1 扫描package下面的所有class,将class的完全限定名保存在List集合中
3.2 实例化第一步集合中的Bean对应的实例,实例的过程采用的是Java的反射机制,且哪些bean是需要实例化的是有过滤条件 的,如加了@Controller注解的bean类才可以newInstance(),实例化的结果,放在beanMap中进行存储,map中的key就是 bean的name,value就是bean实例。
3.3 实例化单个bean后,接着要处理单个bean中依赖注入的问题,也就是要处理@AutoWired注解的类字段了
3.4 bean依赖注入的问题解决后,剩下的就是解决前端请求url和后端method之间的映射(Mapping)了,也就是说,前端发送url请求后,后端,也就是Servlet容器是如何知道要去找哪个Controller中的哪个对应的method去执行呢?这就需要我们再次通过反射技术,去找寻url和method之间的关系了,找到后,存放在mapping容器中,以便于doGet和doPost方法中取出调用。
4、最后,配置项目的web.xml
二、项目结构
三、核心代码(部分)
public class MyDispatcherServlet extends HttpServlet {
// 重写三个方法,一个是父类GenericServlet的初始化方法init(),两个分别是父类HttpServlet的doGet和doPost请求处理方法
/**存放指定的扫描包下面的所有的class*/
List<String> classNames = new ArrayList<>();
/**存放符合条件的实例化后的bean(类似于简版的ioc容器),如加了xxx注解的bean要放进来*/
Map<String,Object> beanMap = new HashMap<>(16);
/**url和method映射关系 (请求api地址和处理请求的方法的键值对)*/
Map<String, Method> urlMethodMap = new HashMap<>(16);
@Override
public void init() throws ServletException {
// 1、第一步,扫描指定的包(主要是扫描xxx.class文件)
scanPackage("com.appleyk");
System.out.println("classNames = " + classNames);
// 2、利用反射,实例化bean(将实例化的bean,放入bean容器map中)
beanInstance();
System.out.println("beanMap = " + beanMap);
//3、处理@XXAutoWired注解,bean实例中的字段如果有这个注解(依赖其他bean),则将该注解对应的实例从beanMap中取出,并赋予该字段
doAutoWired();
// 4、处理请求的url与控制器类中方法的mapping(映射)关系
handleMapping();
System.out.println("urlMethodMap = " + urlMethodMap);
}
/**
* 获取指定扫描包下面的所有class的名称(限定类名)
* @param packageStr 要扫描的包名
*/
public void scanPackage(String packageStr){
// 1、将xx.xx结构的包名,转换为实际意义上的xx/xx/路径
String pageckageDir = packageStr.replaceAll("\\.", "/");
// 2、根据包路径,拿到当前类路径(classPath)
URL resource = this.getClass().getClassLoader().getResource(pageckageDir);
// 3、拿到资源的文件(全)路径
String fileStr = resource.getFile();
// 4、根据路径创建文件
File file = new File(fileStr);
// 5、判断文件是否存在,不存在直接返回
if(!file.exists()){
return;
}
// 6、获取classes文件下面的所有文件集合(可能是文件,也有可能是目录)
File[] files = file.listFiles();
for (File filePath : files) {
String className = packageStr + "." + filePath.getName().replace(".class","");
if(filePath.isDirectory()){
// 如果是文件夹的话,递归继续获取类限定名
scanPackage(className);
}else{
// 如果是文件的话,直接包名+“.”+文件拼接成类限定名,然后加入到集合中
classNames.add(className);
}
}
}
/**
* 首字母小写
*/
private String lowerFirstCase(String str){
char[] chars = str.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
四、如何更改MVC项目的contextPath
五、启动Tomcat,来一波测试,验证手写300多行代码的实际效果
(1)run
(2)浏览器输入请求url
https://localhost:8080/springmvc/user/query
A: 默认不给参数的调用情况
B: 带参数的调用情况
https://localhost:8080/springmvc/user/query?name=appleyk&sex=%E7%94%B7