实现SpringMVC底层机制(一)

文章目录

1.环境配置

1.创建maven项目
2.创建文件目录
3.导入jar包
xml 复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>sun-springmvc</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>sun-springmvc Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--servlet原生api-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <!--在项目打包时不会带上这个jar-->
      <scope>provided</scope>
    </dependency>
    <!--解析xml-->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>
    <!--常用工具类-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.5</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>sun-springmvc</finalName>
  </build>
</project>

2.开发核心控制器

文件目录
1.流程图
2.编写核心控制器SunDispatcherServlet.java
java 复制代码
package com.Sun.sunspringmvc.servlet;

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

/**
 * 充当中央控制器
 *
 * @author 孙显圣
 * @version 1.0
 */
public class SunDispatcherServlet extends HttpServlet {
    @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.类路径下编写spring配置文件sunspringmvc.xml
4.配置中央控制器web.xml
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>
  <display-name>Archetype Created Web Application</display-name>

  <!--配置中央控制器-->
  <servlet>
    <servlet-name>SunDispatcherServlet</servlet-name>
    <servlet-class>com.Sun.sunspringmvc.servlet.SunDispatcherServlet</servlet-class>
    <!--init---param设置spring配置文件的位置-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:sunspringmvc.xml</param-value>
    </init-param>
    <!--服务器启动时实例化servlet,将其放到容器中,并且调用init方法-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>SunDispatcherServlet</servlet-name>
    <!--拦截所有请求-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>


</web-app>
5.配置tomcat,完成测试
1.配置发布方式
2.配置热加载
3.修改SunDispatcherServlet.java
4.完成测试

3.完成客户端/浏览器可以请求控制层

文件目录
1.思路分析
2.编写MonsterController.java
java 复制代码
package com.Sun.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class MonsterController {
    public void listMonster(HttpServletRequest request, HttpServletResponse response) {
        //设置mine类型
        response.setContentType("text/html;charset=utf-8");
        try {
            PrintWriter writer = response.getWriter();
            writer.write("<h1>妖怪列表信息</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
3.自定义注解
1.Controller.java
java 复制代码
package com.Sun.sunspringmvc.annotation;

import java.lang.annotation.*;

/**
 * 用于标识一个Controller
 *
 * @author 孙显圣
 * @version 1.0
 */
@Target(ElementType.TYPE) //作用于类型
@Retention(RetentionPolicy.RUNTIME) //作用范围
@Documented
public @interface Controller {
}
2.RequestMapping.java
java 复制代码
package com.Sun.sunspringmvc.annotation;

import java.lang.annotation.*;

/**
 * 用于指定映射路径
 *
 * @author 孙显圣
 * @version 1.0
 */
@Target(ElementType.METHOD) //作用于方法
@Retention(RetentionPolicy.RUNTIME) //作用范围
@Documented
public @interface RequestMapping {
}
4.自定义容器(1),在tomcat启动时读取配置文件,获取要扫描的包的工作路径
1.SunWebApplicationContext.java
java 复制代码
package com.Sun.sunspringmvc.context;

import com.Sun.sunspringmvc.xml.XmlParser;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class SunWebApplicationContext {
    //存放所有要扫描的包下的class文件的全路径
    private List<String> classFullPathList = new ArrayList<String>();

    //初始化容器
    public void init() {
        //读取spring配置文件,获取要扫描的包的信息
        String basePage = XmlParser.getBasePage("sunspringmvc.xml");
        //完成对指定包的扫描
        scanPage(basePage);
    }

    //创建方法,完成对指定包的扫描,获取所有class文件的全路径
    public void scanPage(String packFullName) {
        //将包的全类名中的点替换为斜杠
        String packPath = packFullName.replaceAll("\\.", "/");

        //通过类加载器来获取这个包的工作路径,就是获取工作路径下的类路径下的文件路径
        URL resource = SunWebApplicationContext.class.getClassLoader().getResource(packPath);
        System.out.println(resource);
    }
}
2.修改SunDispatcherServlet.java
java 复制代码
package com.Sun.sunspringmvc.servlet;

import com.Sun.sunspringmvc.context.SunWebApplicationContext;

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.IOException;

/**
 * 充当中央控制器
 *
 * @author 孙显圣
 * @version 1.0
 */
public class SunDispatcherServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        //初始化容器
        SunWebApplicationContext sunWebApplicationContext = new SunWebApplicationContext();
        sunWebApplicationContext.init();

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost");
    }
}
3.单元测试,启动tomcat
5.自定义容器(2),在tomcat启动的时候完成对指定包的扫描
1.修改SunWebApplicationContext.java
java 复制代码
package com.Sun.sunspringmvc.context;

import com.Sun.sunspringmvc.xml.XmlParser;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class SunWebApplicationContext {
    //存放所有要扫描的包下的class文件的全路径
    private List<String> classFullPathList = new ArrayList<String>();

    //初始化容器
    public void init() {
        //读取spring配置文件,获取要扫描的包的信息
        String basePage = XmlParser.getBasePage("sunspringmvc.xml");
        //初始化容器
        //根据逗号进行分割,得到多个要扫描的包的全路径,遍历将里面的class文件全路径放到列表中
        String[] split = basePage.split(",");
        for (String packPath : split) {
            scanPage(packPath);
        }
    }

    //创建方法,完成对指定包的扫描,获取所有class文件的全路径
    public void scanPage(String packFullName) {
        //将包的全类名中的点替换为斜杠
        String packPath = packFullName.replaceAll("\\.", "/");

        //通过类加载器来获取这个包的工作路径,就是获取工作路径下的类路径下的文件路径
        URL url = SunWebApplicationContext.class.getClassLoader().getResource(packPath);
        //得到路径
        String file = url.getFile();
        //根据这个文件夹来创建一个file对象,从而遍历里面所有的class文件得到所有class文件的全路径
        File packDirectory = new File(file);
        if (packDirectory.isDirectory()) {
            //如果是文件夹则列出里面的所有文件对象
            File[] files = packDirectory.listFiles();
            //遍历这些文件对象,实际上就那个包下的所有class文件对象
            for (File classFile : files) {
                //如果这里的文件对象还是文件夹,则进行递归扫描
                if (classFile.isDirectory()) {
                    scanPage(packFullName + "." + classFile.getName());
                } else {
                    //如果这里的文件对象指的都是文件,则将其放到classFullPathList中
                    //得到当前文件的全类名 = 包的全路径 + class文件的名字去掉.class
                    String classFullPath = packFullName + "." + classFile.getName().replaceAll(".class", "");
                    //放到列表中
                    classFullPathList.add(classFullPath);
                }
            }
        }
    }

}
2.debug测试
6.将自定义容器(3),符合要求的类反射创建对象,放到单例池
1.修改SunWebApplicationContext.java增加方法,添加属性
java 复制代码
    //编写方法,将符合要求的类反射创建对象,并封装到单例池中
    public void executeInstance(){
        //遍历所有全类名
        for (String classPath : classFullPathList) {
            try {
                //反射
                Class<?> aClass = Class.forName(classPath);
                //判断是否有Controller注解
                if (aClass.isAnnotationPresent(Controller.class)) {
                    //有注解,当他是单例的,反射创建bean对象,放到单例池中,默认首字母小写
                    //获取类名首字母小写
                    String name = aClass.getSimpleName().substring(0, 1).toLowerCase() + aClass.getSimpleName().substring(1);
                    //放到单例池中
                    singleObjects.put(name, aClass.newInstance());
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
2.debug查看单例池
7.完成url和控制器方法映射
1.创建映射bean,SunHandler.java
java 复制代码
package com.Sun.sunspringmvc.handler;

import java.lang.reflect.Method;

/**
 * 用于存放有注解的类的映射信息
 *
 * @author 孙显圣
 * @version 1.0
 */
public class SunHandler {
    private String url; //映射的url
    private Object controller; //controller对象
    private Method method; //方法对象,用于反射调用方法

    public SunHandler(String url, Object controller, Method method) {
        this.url = url;
        this.controller = controller;
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    @Override
    public String toString() {
        return "SunHandler{" +
                "url='" + url + '\'' +
                ", controller=" + controller +
                ", method=" + method +
                '}';
    }
}
2.修改中央控制器SunWebApplicationContext.java添加方法和属性
java 复制代码
    //初始化映射对象列表,获取映射对象并且将其放到映射列表中
    private void initHandlerMapping() {
        //判断单例池是否为空
        if (sunWebApplicationContext.singleObjects.isEmpty()) {
            return;
        }
        //取出单例池里的所有对象
        for (Map.Entry<String, Object> entry : sunWebApplicationContext.singleObjects.entrySet()) {
            //反射
            Class<?> aClass = entry.getValue().getClass();
            //判断是否有colltroller注解
            if (aClass.isAnnotationPresent(Controller.class)) {
                //反射获取所有方法对象
                Method[] declaredMethods = aClass.getDeclaredMethods();
                //判断方法里是否有requestmapping注解
                for (Method declaredMethod : declaredMethods) {
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        //获取这个方法的注解信息
                        String url = declaredMethod.getAnnotation(RequestMapping.class).value();
                        //将信息封装到映射bean对象中
                        SunHandler sunHandler = new SunHandler(url, entry.getValue(), declaredMethod);
                        //添加到列表中
                        handlers.add(sunHandler);
                    }
                }
            }
        }
    }
3.debug查看映射对象列表
8.完成请求分发到目标方法
1.修改SunDispatcherServlet.java,添加两个方法并在dopost中请求分发
java 复制代码
package com.Sun.sunspringmvc.servlet;

import com.Sun.sunspringmvc.annotation.Controller;
import com.Sun.sunspringmvc.annotation.RequestMapping;
import com.Sun.sunspringmvc.context.SunWebApplicationContext;
import com.Sun.sunspringmvc.handler.SunHandler;

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.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 充当中央控制器
 *
 * @author 孙显圣
 * @version 1.0
 */
public class SunDispatcherServlet extends HttpServlet {
    //存放所有的映射关系
    private List<SunHandler> handlers = new ArrayList<SunHandler>();
    private SunWebApplicationContext sunWebApplicationContext = null;

    @Override
    public void init(ServletConfig config) throws ServletException {
        //初始化容器
        sunWebApplicationContext = new SunWebApplicationContext();
        sunWebApplicationContext.init();
        //初始化映射列表
        initHandlerMapping();
        System.out.println("ss");
    }

    @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 {
        //请求分发
        executeDispatch(req, resp);
    }

    //初始化映射对象列表,获取映射对象并且将其放到映射列表中
    private void initHandlerMapping() {
        //判断单例池是否为空
        if (sunWebApplicationContext.singleObjects.isEmpty()) {
            return;
        }
        //取出单例池里的所有对象
        for (Map.Entry<String, Object> entry : sunWebApplicationContext.singleObjects.entrySet()) {
            //反射
            Class<?> aClass = entry.getValue().getClass();
            //判断是否有colltroller注解
            if (aClass.isAnnotationPresent(Controller.class)) {
                //反射获取所有方法对象
                Method[] declaredMethods = aClass.getDeclaredMethods();
                //判断方法里是否有requestmapping注解
                for (Method declaredMethod : declaredMethods) {
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        //获取这个方法的注解信息
                        String url = declaredMethod.getAnnotation(RequestMapping.class).value();
                        //将信息封装到映射bean对象中
                        SunHandler sunHandler = new SunHandler(url, entry.getValue(), declaredMethod);
                        //添加到列表中
                        handlers.add(sunHandler);
                    }
                }
            }
        }
    }

    //根据请求对象得到映射对象
    private SunHandler getSunHandler(HttpServletRequest request) {
        //获取uri: /sun-springmvc/list/monster
        String requestURI = request.getRequestURI();
        String contextPath = request.getServletContext().getContextPath();
        //遍历映射对象列表,查看列表中是否有这个uri
        for (SunHandler handler : handlers) {
            //这里拼接一个上下文路径
            if ((contextPath + "/" + handler.getUrl()).equals(requestURI)) {
                //返回这个映射对象
                return handler;
            }
        }
        return null;
    }

    //请求分发
    private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
        //获取映射对象
        SunHandler sunHandler = getSunHandler(request);
        //映射对象不等于空则反射调用controller的方法
        if (sunHandler != null) {
            try {
                sunHandler.getMethod().invoke(sunHandler.getController(), request, response);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        } else {
            //当映射对象是空的时候,返回404
            try {
                response.getWriter().write("<h1>404 not found!</h1>");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }
    }
}
2.单元测试

4.当前阶段完成的功能

1.初始化阶段
  • tomcat服务器启动,自动装载中央控制器(servlet),调用init方法
  • 初始化spring容器
    • 创建spring容器实例,调用init方法
    • 读取spring配置文件,得到要扫描的包的工作路径
    • 扫描指定的包,获取所有class文件的全路径
    • 扫描所有class文件,将包含Controller注解的类反射创建对象放到单例池中(这里假设都是单例的)
  • 初始化映射对象列表
    • 扫描所有单例池中的对象
    • 反射获取这个对象对应类的所有方法,如果方法包含RequestMapping注解,则将这个对象,url,Method对象封装到映射对象中,并且添加到映射对象列表
2.完成请求分发
  • 根据请求对象得到映射对象
    • 获取请求的uri
    • 遍历对象映射列表查看是否有匹配的映射对象,如果有则返回映射对象
  • 请求分发
    • 首先根据请求对象得到映射对象
    • 如果得到了就反射调用方法
    • 没有得到则返回404
相关推荐
呆呆小雅几秒前
C#关键字volatile
java·redis·c#
Monly211 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
Ttang233 分钟前
Tomcat原理(6)——tomcat完整实现
java·tomcat
goTsHgo5 分钟前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc
钱多多_qdd14 分钟前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring
waicsdn_haha16 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
Q_192849990626 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
Code_流苏29 分钟前
VSCode搭建Java开发环境 2024保姆级安装教程(Java环境搭建+VSCode安装+运行测试+背景图设置)
java·ide·vscode·搭建·java开发环境
良许Linux31 分钟前
0.96寸OLED显示屏详解
linux·服务器·后端·互联网
求知若饥43 分钟前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs