SpringMVC原理及手撕源码

目录

  • 简介
    • MVC的工作流程
  • 手撕源码
    • 自定义注解
    • 接口方法
    • 定义controller
    • DispacherServlet

简介

使用过spring mvc的小伙伴都知道,mvc在使用的时候,我们只需要在controller上注解上@controller跟@requestMapping(“URL”),当我们访问对应的路径的时候,框架便会帮我们去映射到指定的controller里面的指定方法,那么这一切都是怎么做到的呢?还有我们所传递过去的参数,为什么通过request.getParam就能轻易地 拿到呢?大家都知道mvc的核心控制器DispacherServlet的基本运行流程,那么他的内部是怎么运行的呢,我们来做一下简单的实现,让我们能进一步的了解MVC。以助于我们今后的开发。

MVC的工作流程

SpringMVC原理及手撕源码_第1张图片
1、 用户发送请求至前端控制器DispatcherServlet。

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

3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

4、 DispatcherServlet调用HandlerAdapter处理器适配器。

5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

6、 Controller执行完成返回ModelAndView。

7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

9、 ViewReslover解析后返回具体View。

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

11、 DispatcherServlet响应用户。

手撕源码

首先创建一个springboot的项目
引入pom

<dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

我们这边主要是简化注解形式的启动方式
自定义注解实现自己的controller,Service,RequestMapping和Autowired

自定义注解

首先是controller

@Target(ElementType.TYPE)//表示注解运行在哪里 这里表示只能注解再类上面
@Retention(RetentionPolicy.RUNTIME)//表示注解的(生命周期)哪来出现
public @interface LulfController {
     
}

然后是Service

@Target({
     ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LulfService {
     
    String value() default "";
}

然后是RequestMapping

@Target({
     ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LulfRequestMapping {
     
    String value();
}

然后是Autowired

@Target({
     ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LulfAutowired {
     
    String value() default "";
}

接口方法

定义等下调用的接口方法

public interface IHelloService {
     

    String get(String name);
}

接口方法实现类
这边我们用到了自己的@LulfService注解

@LulfService
public class HelloServiceImpl implements IHelloService {
     
    @Override
    public String get(String name) {
     
        return "hello , " + name;
    }
}

定义controller

首先定义父类的controller

public class BaseController {
     

    protected HttpServletRequest request;
    protected HttpServletResponse response;

    public void init(HttpServletRequest request, HttpServletResponse response) {
     
        this.request = request;
        this.response = response;
    }

    public HttpServletRequest getRequest() {
     
        return request;
    }

    public void setRequest(HttpServletRequest request) {
     
        this.request = request;
    }

    public HttpServletResponse getResponse() {
     
        return response;
    }

    public void setResponse(HttpServletResponse response) {
     
        this.response = response;
    }
}

然后定义我们的测试controller
用上我们的自定义注解

@LulfController
@LulfRequestMapping("/lulf")
public class TestController extends BaseController {
     

    @LulfAutowired
    private IHelloService helloService;

    @LulfRequestMapping("/index1.do")
    public void index1() {
     
        try {
     
            response.getWriter().write("index" + helloService.get("lulf"));
        } catch (IOException e) {
     
            e.printStackTrace();
        }
    }

    @LulfRequestMapping("/index2.do")
    public void index2() {
     
        try {
     
            response.getWriter().write("index1" + helloService.get("lulf1"));
        } catch (IOException e) {
     
            e.printStackTrace();
        }
    }

    @LulfRequestMapping("/index3.do")
    public void index3() {
     
        try {
     
            response.getWriter().write("index2" + helloService.get("lulf2"));
        } catch (IOException e) {
     
            e.printStackTrace();
        }
    }

    @LulfRequestMapping("/index.do")
    public void index() {
     
        try {
     
            response.getWriter().write("index3" + helloService.get("lulf3"));
        } catch (IOException e) {
     
            e.printStackTrace();
        }
    }
}

此时我们可以测试下

public class test {
     

    public static void main(String[] args) {
     
        // Class
        Class clazz = TestController.class;
        //判断这个类是否存在@LulfController注解
        if (clazz.isAnnotationPresent(LulfController.class)) {
     
            String path = "";
            //判断clazz是否存在注解@LulfRequestMapping注解
            if (clazz.isAnnotationPresent(LulfRequestMapping.class)) {
     
                //取出注解的值 放入path
                LulfRequestMapping reqAnno = (LulfRequestMapping) clazz.getAnnotation(LulfRequestMapping.class);
                path = reqAnno.value().toString();
            }
            //拿到控制类所有公开方法遍历
            Method[] ms = clazz.getMethods();
            for (Method method : ms) {
     
                //如果不存在该注解  就进入下一轮
                if (!method.isAnnotationPresent(LulfRequestMapping.class)) {
     
                    continue;
                }
                System.out.println("方法" + method.getName() + ",映射的对外路径:"
                        + path + method.getAnnotation(LulfRequestMapping.class).value().toString());
            }
        }
    }

简单的说就是获取类,然后判断类是否含有某个注解,遍历方法,判断方法是否含有某个注解
SpringMVC原理及手撕源码_第2张图片

DispacherServlet

这样我们就可以拿到指定的类里面的指定的一些注解的值,还可以做一系列的操作。好,那么现在我们需要想到的就是核心控制器DispacherServlet了。既然是servlet,我们先来看一下servlet的生命周期。Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

Servlet 通过调用 init () 方法进行初始化。
Servlet 调用 service() 方法来处理客户端的请求。
Servlet 通过调用 destroy() 方法终止(结束)。
最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
  既然知道了servlet的生命周期,那就好办了,我们可以通过servlet的初始化,将指定包下的类都扫描起来,然后再重写service()方法去处理这些请求,不久可以了么?接下去我们试一试。

创建自己的DispacherServlet:我是再spring boot环境下去操作的。我们要配置好拦截路径,基准包并重写init(),service()方法

package com.example.mvc.servlet;

import com.example.mvc.annotation.LulfAutowired;
import com.example.mvc.annotation.LulfController;
import com.example.mvc.annotation.LulfRequestMapping;
import com.example.mvc.annotation.LulfService;
import com.example.mvc.controller.BaseController;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
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.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName LulfDispacherServlet
 * @Description TODO
 * @Autuor lulinfeng
 * @Date 2020/10/12
 * @Version 1.0
 */
@WebServlet(urlPatterns = {
     "*.do"}, loadOnStartup = 1, initParams = {
     @WebInitParam(name = "basePackage", value = "com.example")})
public class LulfDispacherServlet extends HttpServlet {
     

    private static final long serialVersionUID = 1L;

    //保存url和Method的对应关系
    private Map<String, Method> handlerMapping = new HashMap<String, Method>();

    //保存扫描的所有的类名
    private List<String> classNames = new ArrayList<String>();

    //存放所扫描出来的类及其实例
    private Map<String, Object> ioc = new HashMap<String, Object>();

    public LulfDispacherServlet() {
     
        super();
    }

    @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 {
     
        //访问地址http://localhost:8080/lulf/index.do
        //这里拿到uri : /lulf/index.do
        String uri = req.getRequestURI();
        //从方法map里获取到映射到的方法实例 : public void com.example.demo.annotation.TestController.index()
        //处理成相对路径
        if (!this.handlerMapping.containsKey(uri)) {
     
            resp.getWriter().write("404 Not Found!!!");
            return;
        }
        Method method = this.handlerMapping.get(uri);
        //通过反射拿到method所在class,拿到class之后还是拿到class的名称
        //再调用toLowerFirstCase获得beanName
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        BaseController controller;
        try {
     
            //获取实例
            controller = (BaseController) ioc.get(beanName);
            //初始化该controller的请求与响应
            //也就是我们的请求中参数怎么通过requset.getParam方法拿到的原因
            System.out.println(req.getRequestURI());
            controller.init(req, resp);
            //然后调用该方法
            method.invoke(controller);
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }

    /**
     * 如果类名本身是小写字母,确实会出问题
     * 这个方法是private的,传值也是自己传,类也都遵循了驼峰命名法
     * 默认传入的值,存在首字母小写的情况,也不可能出现非字母的情况
     * 为了简化程序逻辑,不做其他判断了
     **/
    private String toLowerFirstCase(String simpleName) {
     
        char[] chars = simpleName.toCharArray();
        //之所以加,是因为大小写字母的ASCII码相差32,
        // 而且大写字母的ASCII码要小于小写字母的ASCII码
        //在Java中,对char做算学运算,实际上就是对ASCII码做算学运算
        chars[0] += 32;
        return String.valueOf(chars);
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
     
        //获取基础扫描包: 这里设定为com.example
        String basePackage = config.getInitParameter("basePackage");
        //1 扫描包得到所有的class 并且注入ioc
        doScanner(basePackage);
        //2、初始化扫描到的类,并且将它们放入到ICO容器之中
        doInstance();
        //可参考DispacherServlet 的初始化流程
        //可参考DispacherServlet#initStrategies(ApplicationContext context)
        doAutowired();
        //4、初始化HandlerMapping
        initHandlerMapping();
    }

    /**
     * 扫描相关类
     **/
    private void doScanner(String scanPackage) {
     
        //scanPackage = com.example ,存储的是包路径
        //转换为文件路径,实际上就是把.替换为/就OK了
        //classpath
        URL url = this.getClass().getClassLoader().getResource("" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
     
            if (file.isDirectory()) {
     
                doScanner(scanPackage + "." + file.getName());
            } else {
     
                if (!file.getName().endsWith(".class")) {
     
                    continue;
                }
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(className);
            }
        }
    }

    private void doInstance() {
     
        //初始化,为DI做准备
        if (classNames.isEmpty()) {
     
            return;
        }
        try {
     
            for (String className : classNames) {
     
                Class<?> clazz = Class.forName(className);
                //判断需要初始化的类,满足条件才让它初始化,即加了特定注解的类才初始化
                //为了简化代码逻辑,主要体会设计思想,只举例 @Controller和@Service,
                //如果类上打了@LulfController注解
                if (clazz.isAnnotationPresent(LulfController.class)) {
     
                    Object instance = clazz.newInstance();
                    //Spring默认类名首字母小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                }
                //如果类上打了@LulfController注解
                else if (clazz.isAnnotationPresent(LulfService.class)) {
     
                    //1、自定义的beanName
                    LulfService service = clazz.getAnnotation(LulfService.class);
                    String beanName = service.value();
                    //2、默认类名首字母小写
                    if ("".equals(beanName.trim())) {
     
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);
                    //3、根据类型自动赋值,投机取巧的方式
                    for (Class<?> i : clazz.getInterfaces()) {
     
                        if (ioc.containsKey(i.getName())) {
     //接口若有多个实现
                            throw new Exception("The “" + i.getName() + "” is exists!!");
                        }
                        //把接口的类型直接当成key了
                        ioc.put(i.getName(), instance);
                    }
                } else {
     
                    continue;
                }
            }
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }

    /**
     * 自动依赖注入DI
     **/
    private void doAutowired() {
     
        if (ioc.isEmpty()) {
     
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
     
            //Declared 所有的,特定的 字段,包括private/protected/default
            //正常来说,普通的OOP编程只能拿到public的属性
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
     
                if (!field.isAnnotationPresent(LulfAutowired.class)) {
     
                    continue;
                }
                LulfAutowired autowired = field.getAnnotation(LulfAutowired.class);
                //如果用户没有自定义beanName,默认就根据类型注
                String beanName = autowired.value().trim();
                if ("".equals(beanName)) {
     
                    //获得接口的类型,作为key待会拿这个key到ioc容器中去取值
                    beanName = field.getType().getName();
                }
                //如果是public以外的修饰符,只要加了@Autowired注解,都要强制赋值
                //反射中叫做暴力访问,下面打开暴力访问的开关
                field.setAccessible(true);
                try {
     
                    //用反射机制,动态给字段赋值
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
     
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 初始化url和Method的一对一对应关系
     **/
    private void initHandlerMapping() {
     
        if (ioc.isEmpty()) {
     
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
     
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(LulfController.class)) {
     
                continue;
            }
            //保存写在类上面的@LulfRequestMapping("/lulf")
            String baseUrl = "";
            if (clazz.isAnnotationPresent(LulfRequestMapping.class)) {
     
                LulfRequestMapping requestMapping = clazz.getAnnotation(LulfRequestMapping.class);
                baseUrl = requestMapping.value();
            }
            //默认获取所有的public方法
            for (Method method : clazz.getMethods()) {
     
                if (!method.isAnnotationPresent(LulfRequestMapping.class)) {
     
                    continue;
                }
                LulfRequestMapping requestMapping = method.getAnnotation(LulfRequestMapping.class);
               //优化
                // //demo///query
                String url = ("/" + baseUrl + "/" + requestMapping.value())
                        .replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.println("Mapped :" + url + "," + method);
            }
        }
    }
}

启动APP

@SpringBootApplication
@ServletComponentScan
public class App 
{
     
    public static void main( String[] args )
    {
     
        SpringApplication.run(App.class, args);
    }
}

工程目录如下:
SpringMVC原理及手撕源码_第3张图片

你可能感兴趣的:(Spring,java)