SpringMVC以DispatcherServlet为核心,负责协调和组织不同组件以完成请求处理并返回响应的工作,实现了MVC模式。想要实现自己的SpringMVC框架,需要从以下几点入手:
一、了解SpringMVC运行流程及九大组件
九大组件: https://blog.csdn.net/hu_zhiting/article/details/73648939参考: https://www.zhihu.com/question/38459887/answer/78224340
1、 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2、 DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;(这其中handler主要是@RequestMapping标注的那些方法)
3、 DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;(源码如下:)
public interface HandlerAdapter {
boolean supports(Object var1);
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
long getLastModified(HttpServletRequest var1, Object var2);
}
4、 HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
5、 ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、 View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
二、梳理自己的SpringMVC的设计思路
1.扫描自定义路径下的包,把这些包名读取到list类型的classNames中。
2.根据这些包名,判断哪些类加上了 Controller 注解,把这些类保存到一个名为ioc的map中,key为类名, value为实例对象。
3.遍历这些类中所有含有RequestMapping注解的方法,如有,那么将这些信息注册到一个handlerMapping中,这是一个map,其中key为url,value为对应的method方法; 然后再新建一个map:controllerMap,key为url,value为method所在类的对象实例。
4. 在扫描哪些加了Controller注解的类时,也可以遍历其中的所有对象,通过反射获取对象的所有属性值(字段)集合,然后遍历属性值集合,将属性值含有指定注解的,通过Field的set方法为该属性值赋值,这时就将对象注入了。(也就是往Controller中注入Service对象)
5.以上都是初始化的时候进行的操作, 下面来看看处理请求的过程:
5.1. 根据HttpServletRequest得到这次请求的url,如果hadlerMapping中没有这个url的记录的话直接返回;
如果有的话-->
5.2. 根据这个url取出相对应的method,然后就是反射的一些知识了,根据反射取出需要的参数列表。
根据HttpServletRequest取出传进来的参数,根据method.invoke(instance, value)来调用这个url对应的handler方法。
三、实现自己的SpringMVC框架
首先新建一个web项目:
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>MySpringMVC</servlet-name>
<servlet-class>com.lzyu.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name> // 初始化路径
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MySpringMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
application.properties:
scanPackage=com.lzyu.core //定义扫描的包的路径
新建三个注解:
@Controller:
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
/*
表示给controller注册别名
*/
String value() default "";
}
@RequestMapping
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
/*
表示访问该方法的url
*/
String value() default "";
}
@RequestParam
import java.lang.annotation.*;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
/*
表示参数的别名
*/
String value();
}
接下来就是最重要的DispatcherServlet:
package com.lzyu.servlet;
import com.lzyu.annotation.MyController;
import com.lzyu.annotation.MyRequestMapping;
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.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
public class MyDispatcherServlet extends HttpServlet{
private Properties properties = new Properties();
private List<String> classNames = new ArrayList<>();
private Map<String, Object> ioc = new HashMap<>();
private Map<String, Method> handlerMapping = new HashMap<>(); //用来查找handler
private Map<String, Object> controllerMap = new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
// 1.加载配置文件
// String getInitParameter(String name) -- 获取当前Servlet指定名称的初始化参数的值
doLoadConfig(config.getInitParameter("contextConfigLocation"));
// 2. 初始化所有相关联的类,扫描用户设定的包下面所有的类
doScanner(properties.getProperty("scanPackage"));
// 3.拿到扫描的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean)beanName默认是字母小写
doInstance();
// 4.初始化HandlerMapping(将url和method对应上)
initHandlerMapping();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
// 处理请求
doDispatch(req, resp);
}catch (Exception e){
}
}
public void doDispatch(HttpServletRequest req, HttpServletResponse resp)throws Exception{
if (handlerMapping.isEmpty()){
return;
}
String url = req.getRequestURI(); //返回请求行中的资源名部分
String contextPath = req.getContextPath(); // 返回指示请求上下文的请求URI部分。
url = url.replace(contextPath,"").replaceAll("/+","/"); //???
if(!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404 NOT FOUND");
return;
}
Method method = this.handlerMapping.get(url); // 处理的具体方法
// 获取方法的参数列表
Class<?>[] parameterTypes = method.getParameterTypes(); // 方法的参数
// 获取请求的参数
Map<String, String[]> parameterMap = req.getParameterMap();
// 保存参数值
Object[] paramValues = new Object[parameterTypes.length];
// 方法的参数列表
for (int i = 0; i < parameterTypes.length; i++) {
String requestParam = parameterTypes[i].getSimpleName(); //方法的参数名
if(requestParam.equals("HttpServletRequest")){ //强转类型
paramValues[i] = req;
continue;
}
if(requestParam.equals("HttpServletResponse")){
paramValues[i] = req;
continue;
}
if(requestParam.equals("String")){
for(Map.Entry<String, String[]> param:parameterMap.entrySet()){
String value = Arrays.toString(param.getValue()).
replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
paramValues[i]=value;
}
}
}
try{
method.invoke(this.controllerMap.get(url), paramValues); //第一个参数是method所对应的实例,在ioc容器中
}catch (Exception e){
e.printStackTrace();
}
}
private void doLoadConfig(String location){
//把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();
}
}
}
}
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);
}
}
}
private void doInstance(){
if(classNames.isEmpty()){
return;
}
for(String className:classNames){
try{
// 把类搞出来,反射来实例化(只有加MyController需要实例化)
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(MyController.class)){
ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
}else {
continue;
}
}catch (Exception e){
e.printStackTrace();
continue;
}
}
}
private void initHandlerMapping(){
if(ioc.isEmpty()){
return;
}
try{
for(Map.Entry<String, Object> entry:ioc.entrySet()){
Class<? extends Object> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(MyController.class)){
continue;
}
// 拼url时,是controller头的url拼上方法上的url
String baseUrl = "";
if(clazz.isAnnotationPresent(MyRequestMapping.class)){ //1.显示加了RequestMappring注解的类
MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
baseUrl = annotation.value();
}
Method[] methods = clazz.getMethods();
for(Method method:methods){
if(!method.isAnnotationPresent(MyRequestMapping.class)){
continue;
}
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String url = annotation.value();
url = (baseUrl+"/"+url).replaceAll("/+", "/");
handlerMapping.put(url, method);
controllerMap.put(url, clazz.newInstance());
System.out.println(url+","+method);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 把字符串的首字母小写
* @param name
* @return
*/
private String toLowerFirstWord(String name){
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
}
测试函数:
@MyController
@MyRequestMapping("/test")
public class testController {
@MyRequestMapping("/doTest")
public void test1(HttpServletRequest request, HttpServletResponse response,
@MyRequestParam("param") String param){
System.out.println(param);
try {
response.getWriter().write( "doTest method success! param:"+param);
} catch (IOException e) {
e.printStackTrace();
}
}
@MyRequestMapping("/doTest2")
public void test2(HttpServletRequest request, HttpServletResponse response){
try {
response.getWriter().println("doTest2 method success!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上就是一个mini的SpringMVC框架。
参考:
https://my.oschina.net/liughDevelop/blog/1622646?p=3
https://www.cnblogs.com/Shock-W/p/6617068.html
https://blog.csdn.net/briblue/article/details/73824058