手写SpringMVC

一、Spring MVC的运行流程

SpringMVC执行流程
⑴ 用户发送请求至前端控制器DispatcherServlet

⑵ DispatcherServlet收到请求调用HandlerMapping处理器映射器。

⑶ 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

⑷ DispatcherServlet通过HandlerAdapter处理器适配器调用处理器

⑸ 执行处理器(Controller,也叫后端控制器)。

⑹ Controller执行完成返回ModelAndView

⑺ HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet

⑻ DispatcherServlet将ModelAndView传给ViewReslover视图解析器

⑼ ViewReslover解析后返回具体View

⑽ DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。

⑾ DispatcherServlet响应用户。

1.1 Spring MVC初始化

protected void initStrategies(ApplicationContext context) {  
        initMultipartResolver(context);//文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析  
        initLocaleResolver(context);//本地化解析  
        initThemeResolver(context);//主题解析  
        initHandlerMappings(context);//通过HandlerMapping,将请求映射到处理器  
        initHandlerAdapters(context);//通过HandlerAdapter支持多种类型的处理器  
        initHandlerExceptionResolvers(context);//如果执行过程中遇到异常,将交给HandlerExceptionResolver来解析  
        initRequestToViewNameTranslator(context);//直接解析请求到视图名  
        initViewResolvers(context);//通过viewResolver解析逻辑视图到具体视图实现  
        initFlashMapManager(context);//flash映射管理器  
    }

二、Servlet

2.1 什么是Servlet

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

使用 Servlet,可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。

Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势:

  • 性能明显更好。
  • Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
  • Servlet 是独立于平台的,因为它们是用 Java 编写的。
  • 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
  • Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。

2.2 Servlet的生命周期

加载—>实例化—>服务—>销毁。

init():

在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init()。因为Servlet是单例模式,所以要注意线程安全问题。

service():

它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能。

destroy():

仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。

2.3 Servlet实例

package com.example.myspring;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: 98050
 * @Time: 2019-02-22 23:18
 * @Feature:
 */
public class Test3 extends HttpServlet {

    @Override
    public void init() throws ServletException {
        super.init();
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
    }
    
    @Override
    public void destroy() {
        super.destroy();
    }
}

在servlet中默认情况下,无论你是get还是post 提交过来都会经过service()方法来处理,然后转向到doGet或是doPost方法。

三、手写SpringMVC

3.1 思路分析

1.web.xml加载

为了读取web.xml中的配置,编写ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息。通过web.xml中加载自己写的MyDispatcherServlet和读取配置文件。

2、初始化阶段

DispatcherServlet的initStrategies方法会初始化9大组件,但是这里将实现一些SpringMVC的最基本的组件而不是全部,按顺序包括:

  • 加载配置文件
  • 扫描用户配置包下面所有的类
  • 拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写
  • 初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出

3、运行阶段

每一次请求将会调用doGet或doPost方法,所以统一运行阶段都放在doDispatch方法里处理,它会根据url请求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller中的url对应的方法,并得到结果返回。按顺序包括以下功能:

  • 异常的拦截
  • 获取请求传入的参数并处理参数
  • 通过初始化好的handlerMapping中拿出url对应的方法名,反射调用

3.2 实现

3.2.1 添加依赖

只需一个servlet

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>compile</scope>
</dependency>

3.2.2 定义注解

package com.example.myspringmvc.annotation;

import java.lang.annotation.*;

/**
 * @Author: 98050
 * @Time: 2019-02-23 14:41
 * @Feature:
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyController {
    String value() default "";
}
package com.example.myspringmvc.annotation;

import java.lang.annotation.*;

/**
 * @Author: 98050
 * @Time: 2019-02-23 14:41
 * @Feature:
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyRequestMapping {
    String value() default "";
}

3.2.3 自定义DispatcherServlet

自定义DispatcherServlet,继承HttpServlet,重写init、doGet、doPost三个方法。

框架:

package com.example.myspringmvc.dispatchservlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: 98050
 * @Feature:
 */
public class MyDispatcherServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        super.init();
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
    
}

3.2.4 配置web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <!-- Spring MVC 核心控制器 DispatcherServlet 配置 -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>com.example.myspringmvc.dispatchservlet.MyDispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <!-- 拦截所有/* 的请求,交给DispatcherServlet处理,性能最好 -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

将原来的org.springframework.web.servlet.DispatcherServlet替换为自己的DispatcherServlet:

com.example.myspringmvc.dispatchservlet.MyDispatcherServlet

3.2.5 具体实现

init()方法

配置完成后,前端所有请求就会被MyDispatcherServlet所拦截,所以在重写init方法的时候我们要明确几个任务:

  • 获取扫包范围
  • 获取包下所有类
  • 对类进行筛选,只要类上有@MyController注解的类都放入Spring MVC容器当中
  • 遍历Spring MVC容器中的类,对每一个类下所属的方法进行筛选,只要方法上有@MyRequestMapping注解,那就进行url和方法的映射

注意:扫包范围应该通过解析SpringMVC配置文件获取,这里为了方便直接写死;使用工具类获取包下所有类。

定义三个map,用来充当SpringMVC容器、url与类映射、url与方法映射。

/**
 * 用来存放spring mvc的bean
 */
private ConcurrentHashMap<String,Object> springMVCBeans = new ConcurrentHashMap<>();
/**
 * url与类映射
 */
private ConcurrentHashMap<String,Object> urlBeans = new ConcurrentHashMap<>();

/**
 * url与方法映射
 */
private ConcurrentHashMap<String,String> urlMethod = new ConcurrentHashMap<>();

因为通过Java反射机制执行目标方法时,要获取到目标类的对象,目标方法名,为了方便查询,定义上述三个map。

重写init方法:

@Override
public void init() throws ServletException {
    System.out.println("初始化");
    try {
        //1.获取扫包范围
        String packages = "com.example.myspringmvc.controller";
        //2.获取包下的所有的类
        List<Class<?>> classList = ClassUtil.getClasses(packages);
        //3.对类进行筛选,将包含MyController注解的类进行初始化,并且放入spring mvc bean容器中
        findAndInitMyControllerClass(classList);
        //4.url与方法映射
        handlerMapping(springMVCBeans);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    }
}
private void findAndInitMyControllerClass(List<Class<?>> classList) throws IllegalAccessException, InstantiationException {
    for (Class c : classList){
        MyController annotation = (MyController) c.getAnnotation(MyController.class);
        if (annotation != null){
            Object o = c.newInstance();
            String name = toLowerCaseFirstOne(c.getSimpleName());
            springMVCBeans.put(name, o);
        }
    }
}
 private String toLowerCaseFirstOne(String simpleName) {
        String first = (simpleName.charAt(0)+"").toLowerCase();
        String rest = simpleName.substring(1);
        return new StringBuffer(first).append(rest).toString();
    }
private void handlerMapping(ConcurrentHashMap<String, Object> springMVCBeans) {
    for (String key : springMVCBeans.keySet()){
        Object o = springMVCBeans.get(key);
        MyController myController = o.getClass().getAnnotation(MyController.class);
        Method[] methods = o.getClass().getMethods();
        String url = "";
        for (Method method : methods){
            MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
            if (myRequestMapping != null){
                //1.拼接url
                url = myController.value() + myRequestMapping.value();
                //2.url与方法映射
                urlMethod.put(url, method.getName());
            }
        }
        //3.url与类映射
        urlBeans.put(url, o);
    }
}

以上都是在init方法中要完成的任务,具体的请求响应放在doPost和doGet中执行。

doGet和doPost

@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 {
    try {
        doDispatch(req,resp);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

具体执行过程封装在doDispatch中,具体任务如下:

  • 获取请求的url
  • 根据url获取对应的控制器类
  • 根据url获取对应的处理方法
  • 使用Java反射机制执行目标方法
  • 获取目标方法的返回值,然后进行视图解析
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, ServletException {
    //1.获取请求url
    String url = req.getRequestURI();
    //2.获取对应的控制器
    Object o = urlBeans.get(url);
    if (o == null){
        resp.getWriter().println("404 not found");
    }
    //3.获取url对应的方法
    String methodName = urlMethod.get(url);
    //4.使用反射机制执行方法
    String result = (String) methodInvoke(o.getClass(),o,methodName);
    //5.视图展示
    viewResolver(result,req,resp);
}
private Object methodInvoke(Class<?> aClass, Object o, String methodName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    Method method = aClass.getMethod(methodName);
    Class<?>[] parameterTypes = method.getParameterTypes();
    Object result = method.invoke(o,parameterTypes);
    return result;
}
private void viewResolver(String pageName, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1.获取后缀信息
    String suffix = ".jsp";
    //2.页面目录地址
    String prefix = "/";
    req.getRequestDispatcher(prefix + pageName + suffix).forward(req, resp);
}

3.2.6 测试

index.jsp

index.jsp

TestController.java

package com.example.myspringmvc.controller;

import com.example.myspringmvc.annotation.MyController;
import com.example.myspringmvc.annotation.MyRequestMapping;

/**
 * @Author: 98050
 * @Time: 2019-02-23 15:00
 * @Feature:
 */
@MyController("/test")
public class TestController {

    @MyRequestMapping("/test")
    public String test(){
        return "index";
    }
}

结果:

test

3.3 总结

以上只是完成了一个简易版的Spring MVC,目的是学习Spring MVC的执行流程,许多细节性的问题就不过多纠结了(url地址参数读取、xml配置文件解析等)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值