手写 Springmvc 框架

本文深入探讨了Java Servlet的基本概念与优势,详细讲解了Servlet的生命周期及其在Web服务器中的作用。同时,文章还全面介绍了SpringMVC框架的运行流程,包括其核心组件与工作原理,以及如何手写一个简单的SpringMVC实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是 Servlet?

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

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

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

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

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

init():

在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init()。

service():

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

destroy():

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

SpringMVC的运行流程

在这里插入图片描述

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
  5. 执行处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
  9. ViewReslover解析后返回具体View
  10. DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
  11. DispatcherServlet响应用户。

手写SpringMVC基本实现

引入依赖
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
自定义 Controller 注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtController {
}
自定义 RequestMapping 注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtRequestMapping {
    String value() default "";
}
自定义前端控制器 ExtDispatcherServlet
public class ExtDispatcherServlet extends HttpServlet {

    /**
     * springmvc 容器对象 key:类名id value:对象
     */
    private ConcurrentHashMap<String, Object> springmvcBeans = new ConcurrentHashMap<String, Object>();

    /**
     * springmvc 容器对象 key:请求地址 value:对象
     */
    private ConcurrentHashMap<String, Object> urlBeans = new ConcurrentHashMap<String, Object>();

    /**
     * springmvc 容器对象 key:请求地址 value:方法名称
     */
    private ConcurrentHashMap<String, String> urlMethods = new ConcurrentHashMap<String, String>();

    @Override
    public void init() throws ServletException {
        //1.获取当前包下所有的类
        List<Class<?>> classes = ClassUtil.getClasses("com.xiaoming.day19.controller");
        //2.将扫包范围所有的类,注入到 springmvc 容器里面,存放在 Map 集合中,key:默认类名小写,value 对象
        try {
            findClassMVCAnnotation(classes);
        } catch (Exception e) {
            // TODO
        }
        //3.将 url 映射和方法进行关联
        handlerMapping();
    }

    @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 {
        //1.获取请求的url
        String requestURI = req.getRequestURI();
        //2.从 map 集合获取控制器对象
        Object object = urlBeans.get(requestURI);
        if (object == null){
            resp.getWriter().println("404 not found url");
        }
        //3.根据url找到对应的方法
        String methodName = urlMethods.get(requestURI);
        if (methodName == null){
            resp.getWriter().println("not found method");
        }
        //4.使用反射机制执行方法,返回执行结果
        String resultPage = (String) methodInvoke(object, methodName);
//        resp.getWriter().println(resultPage);
        //5.调用视图转换器渲染给页面显示
        extResourceViewResoler(resultPage, req, resp);
    }

    /**
     * 调用视图转换器渲染给页面显示
     * @param resultPage
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    private void extResourceViewResoler(String resultPage, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String prefix = "/";
        String suffix = ".jsp";
        req.getRequestDispatcher(prefix + resultPage + suffix).forward(req,resp);
    }

    /**
     * 使用反射机制执行方法
     * @param object 目标对象
     * @param methodName 方法名称
     * @return 执行结果
     */
    private Object methodInvoke(Object object, String methodName){
        Class<?> classInfo = object.getClass();
        try {
            Method method = classInfo.getMethod(methodName);
            Object result = method.invoke(object);
            return result;
        } catch (Exception e) {
            return null;
        } 
    }

    /**
     * 将扫包范围所有的类,注入到 springmvc 容器里面,存放在 Map 集合中,key:默认类名小写,value 对象
     *
     * @param classes
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public void findClassMVCAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
        for (Class<?> classInfo : classes) {
            //判断类上是否有注解
            ExtController extController = classInfo.getAnnotation(ExtController.class);
            if (extController != null) {
                //默认类名小写
                String beanId = ClassUtil.toLowerCaseFirstOne(classInfo.getSimpleName());
                //对象
                Object object = ClassUtil.newInstance(classInfo);
                springmvcBeans.put(beanId, object);
            }
        }
    }

    /**
     * 将 url 映射和方法进行关联
     */
    public void handlerMapping() {
        //1.遍历 springmvc bean 容器 判断类上是否有 url 映射注解
        for (Map.Entry<String, Object> mvcBean : springmvcBeans.entrySet()) {
            //2.遍历所有方法上是否有url注解
            //获取 bean 对象
            Object object = mvcBean.getValue();
            //3.判断类上是否有 url 映射注解
            Class<?> classInfo = object.getClass();
            ExtRequestMapping extRequestMapping = classInfo.getAnnotation(ExtRequestMapping.class);
            String baseUrl = "";
            if (extRequestMapping != null) {
                //获取类上的 url 地址
                baseUrl = extRequestMapping.value();
            }
            //4.判断方法上是否有url映射地址
            Method[] declaredMethods = classInfo.getDeclaredMethods();
            for (Method method : declaredMethods) {
                //判断方法上是否有url映射注释
                ExtRequestMapping methodExtDeclaredAnnotation = method.getAnnotation(ExtRequestMapping.class);
                if (methodExtDeclaredAnnotation != null) {
                    String methodUrl = baseUrl + methodExtDeclaredAnnotation.value();
                    //springmvc 容器对象 key:请求地址 value:对象
                    urlBeans.put(methodUrl, object);
                    //springmvc 容器对象 key:请求地址 value:方法名称
                    urlMethods.put(methodUrl, method.getName());
                }
            }
        }
    }
}
控制器 ExtIndexController
@ExtController
@ExtRequestMapping("/ext")
public class ExtIndexController {

    @ExtRequestMapping("/test")
    public String test(){
        System.out.println("我是手写 springmvc 框架");
        return "index";
    }
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
   version="2.4">


   <!-- Spring MVC 核心控制器 DispatcherServlet 配置 -->
   <servlet>
      <servlet-name>dispatcher</servlet-name>
      <servlet-class>com.xiaoming.day19.ext.springmvc.servlet.ExtDispatcherServlet</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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值