分析SpringMVC的工作流程并实现一个简易版的SpringMV框架

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

 
 
 

猜你喜欢

转载自blog.csdn.net/u012156116/article/details/80795424