手写springmvc(简易版,涉及springmvc的原理)

摘要:学习编程技术的最快方式是读懂技术的原理,如果有这个条件的话,我们可以对技术的原理进行简单的实现,也编写一个简易版的该编程技术。在这篇文章中,我会先简单地介绍一下springmvc的原理,在理解原理的基础上,手写一个简易版的springmvc。如果不懂springmvc的小伙伴,也可以读该文章内容来理解springmvc的原理。

1.springmvc的原理

当客户端发送请求过来时,服务器需要对该请求进行响应。当服务器接收请求时,服务器首先会对发送请求过来的url地址进行解析,将解析出客户端发送过来的请求数据、请求资源、响应数据格式等信息,客户端会进行相应的响应(响应数据或响应视图)。在springmvc中,配置了前端控制器DispatcherServlet,这个前端控制器会根据请求的url地址解析出请求资源路径path,根据路径找出客户端访问的某个Controller类中的某个方法(每个方法都绑定了请求路径,如果是该请求路径,就执行该方法,并返回相应的响应数据)。

2.手写springmvc的思路

(0)初始化spring容器(这步不进行操作,如果要写的话,我们可以写一个spring(手写spring),springmvc之所以需要进行spring让容器的初始化,是因为springmvc的Controller需要交给spring管理)

(1)扫描包 doScan(AppConfig.class);扫描出所有的controller类

(2)实例化DispatcherServlet对象,doGet()方法中,解析handler()

(3)解析所有的controller中的方法, @RequestMapping注解

(4)内嵌tomcat服务器

(5)编写DispacherServlet前端控制器

(6)编写Controller(用于测试)

3.手写springmvc

(1)导入相应的依赖

<!--        tomcat依赖  -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.38</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>9.0.38</version>
        </dependency>

        <!--Servlet坐标-->
<!--        <dependency>-->
<!--            <groupId>javax.servlet</groupId>-->
<!--            <artifactId>servlet-api</artifactId>-->
<!--            <version>2.5</version>-->
<!--        </dependency>-->

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1-b09</version>
        </dependency>


        <!-- 打印 日志-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.22</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.8</version>
            <scope>compile</scope>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>compile</scope>
        </dependency>

(2)搭建项目结构

(3)编写javax.servlet.ServletContainerInitizerwe文件(内容写的应该是我们的程序中实现ServletContainerInitializer接口的全限定类名)(这是Servlet3.0的特性,程序会自动进行回调该类的onStartup()方法))

com.fs.util.MyWebApplicationInitializer

(4)编写MyWebApplicationIntializer类(implements ServletContainerInitializer)

package com.fs.util;


import com.fs.ann.Controller;
import com.fs.ann.ControllerScan;
import com.fs.ann.RequestMapping;
import com.fs.app.AppConfig;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import java.io.File;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;


@Slf4j(topic = "e")
public class MyWebApplicationInitializer implements ServletContainerInitializer{
   // implements ServletContainerInitializer

    // key : controller类名小写,value : 实例化的controller对象
    static Map<String,Object> controllerMap = new HashMap<>();

    // 存储所有的方法和他对应的访问路径;
    // key : method方法上的对应的访问路径@RequestMapping ,value : 对应的Method
    static Map<String, Method> controllerMethodMap = new HashMap<>();

    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        log.debug("执行了onStartup方法");
        // 0.初始化spring容器
//        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
//        context.register(AppConfig.class);

        // 1.扫描包 doScan(AppConfig.class);扫描出所有的controller类
        doScan(AppConfig.class);

        // 2.实例化DispatcherServlet对象,doGet()方法中,解析handler()
//        DispatcherServlet servlet = new DispatcherServlet(context);

        // 实例化MyDispatcherServlet对象
        initServlet(servletContext);


        // 解析所有的controller中的方法, @RequestMapping注解
        parseHandler();

        log.debug("controllerMap : " + controllerMap);
        log.debug("controllerMethodMap : " + controllerMethodMap);
    }

    public void doScan(Class clazz) {
        // 判断是否添加ControllerScan请求
        if(clazz.isAnnotationPresent(ControllerScan.class)) {
            // 获取需要扫描的包名
            ControllerScan declaredAnnotation = (ControllerScan) clazz.getDeclaredAnnotation(ControllerScan.class);
            String scanPackageName = declaredAnnotation.value();
            // 扫描包名路径下的所有文件,查询出所有的controller,并存储到map到当中
            scan(scanPackageName);
        }

    }

    public void scan(String scanPagekeName){
        // 项目的文件输出路径(相对路径)
        String rootUrl = this.getClass().getClassLoader().getResource("").getPath();

        // 将包名的.符号转换/ 定义一个方法 pointToSlash()
        String pathName = pointToSlash(rootUrl + scanPagekeName);

        // 遍历该路径(目录)下的所有目录和文件(递归)
        File file = new File(pathName);

        for (File listFile : file.listFiles()) {
            // 判断是否目录
            if (listFile.isDirectory()) {
                // 如果当前文件是目录
                scan(scanPagekeName + "/" +  listFile.getName());
                continue;
            }

            // 当前文件不是一个目录的情况下
            String className = listFile.getName(); // 类名.class
//            String classSimpleName = toSimpleClassName(className); // 类名小写

            // 获取包名+".类名"
            String packageName = scanPagekeName + "." + className.split("\\.")[0];
            // 将所有的/符号转化成.
            packageName = slashToPoint(packageName);

            Class<?> aClass = null;
            try {
                aClass = Class.forName(packageName);
                // 检测该类中是否有@Controller注解
                if (aClass.isAnnotationPresent(Controller.class)) {
                    // 实例化对象
                    Controller declaredAnnotation = aClass.getDeclaredAnnotation(Controller.class);
                    String controllerName = declaredAnnotation.value();
                    if(controllerName.isEmpty()) {
                        // 如果是空字符串,则使用controller的默认命名方式
                        // 获取类名,类名小写
                        controllerName = controllerName + toSimpleClassName(className);
                    }
                    Object object = aClass.newInstance();
                    controllerMap.put(controllerName,object);
                } else {
                    continue;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }

        }
    }

    public String pointToSlash(String path) {
        path = path.replaceAll("\\.","/");
        return path;
    }

    public String slashToPoint(String path) {
        path = path.replaceAll("/","\\.");
        return path;
    }

    // 类名小写(无后缀)
    public static String toSimpleClassName(String className) {
        className = className.split("\\.")[0];
        char[] array = className.toCharArray();
        array[0] = (char)(array[0] + 32);
        return String.valueOf(array);
    }

    public void initServlet(ServletContext servletContext) {
        // 实例化servlet对象
        MyDispatcherServlet servlet = new MyDispatcherServlet();
        // 交给tomcat服务器进行管理
        ServletRegistration.Dynamic registration = servletContext.addServlet("app",servlet);

        registration.setLoadOnStartup(1);
        registration.addMapping("*.do");
    }

    public void parseHandler() {
        Set<Map.Entry<String, Object>> entries = controllerMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String key = entry.getKey();
            Object obj = entry.getValue();

            Class<?> aClass = obj.getClass();

            // 获取所有的@RequestMapping方法
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                // 检查controller的方法上是否有@RequestMapping注解
                if(method.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                    String s = annotation.value();
                    controllerMethodMap.put(s,method);
                }
            }
        }

    }
}

(5)编写MyDispatcherServlet类(对应springmvc中的DispatherServlet前端控制器)

package com.fs.util;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "e")
public class MyDispatcherServlet extends HttpServlet {

    @SneakyThrows
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取请求的路径
        String servletPath = req.getServletPath();

        // 2. 查询对应的方法
        Method method = MyWebApplicationInitializer.controllerMethodMap.get(servletPath);

        // 3. 查询对应的对象
        String simpleName = method.getDeclaringClass().getSimpleName();
        simpleName = MyWebApplicationInitializer.toSimpleClassName(simpleName);
        Object object  = MyWebApplicationInitializer.controllerMap.get(simpleName);

        // 4. 查询对应方法上的参数
        Class<?>[] parameterTypes = method.getParameterTypes();

        // 由于parameterType数组中的值都是唯一的,所以可以用 == 来进行判断
        // req.getClass() == parameterTypes[0]
        if(parameterTypes.length == 0) {
            log.debug("执行无参数的方法");
            method.invoke(object);
        } else if(parameterTypes.length == 1) {
            log.debug("执行1个参数的方法");
            if("HttpServletRequest".equals(parameterTypes[0].getSimpleName())) {
                // 一个参数: HttpServletReqeust;
                method.invoke(object,req);
            } else {
                // 一个参数: HttpServletResponse;
                method.invoke(object,resp);
            }
        } else if(parameterTypes.length == 2){
            log.debug("执行两个参数的方法");
            if("HttpServletRequest".equals(parameterTypes[0].getSimpleName())) {
                // 第一个参数: HttpServletReqeust;
                method.invoke(object,req,resp);
            } else {
                // 第一个参数: HttpServletResponse;
                method.invoke(object,resp,req);
            }

        }
    }
}

(6)内嵌tomcat服务器

package com.fs.util;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;

public class SpringApplication {

    public static void main(String[] args) throws LifecycleException {
        //记录以后静态文件存储的位置
        String baseDir = Thread.currentThread().getContextClassLoader().getResource("public").getPath();
        //实例化tomcat对象
        Tomcat tomcat = new Tomcat();
        //指定项目访问的路径项目名
        //指定静态资源位置
        tomcat.addWebapp("/bookstore",baseDir);
        //设置端口号
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setPort(8080);
        connector.setThrowOnFailure(true);

        //应用端口号
        tomcat.getService().addConnector(connector);
        tomcat.setConnector(connector);

        //启动tomcat
        tomcat.start();
        //阻塞等待
        tomcat.getServer().await();
    }
}

(6)AppConfig类(配置类,给定需要扫描的包)

package com.fs.app;

import com.fs.ann.ControllerScan;

@ControllerScan("com.fs")
public class AppConfig {

}

(7)其它(自定义注解,Controller类,日志文件(logback日志))

package com.fs.ann;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    String value() default "";
}
package com.fs.ann;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface ControllerScan {
    String value();
}
package com.fs.ann;


import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String value();
}
package com.fs.web.controller;

import com.fs.ann.Controller;
import com.fs.ann.RequestMapping;
import lombok.extern.slf4j.Slf4j;

@Controller
@Slf4j(topic = "e")
public class BController {

    @RequestMapping("/bTest.do")
    public void bTest() {
        log.debug("BController类中的bTest()方法执行了");
    }
}
package com.fs.web.controller;

import com.fs.ann.Controller;
import com.fs.ann.RequestMapping;
import lombok.extern.slf4j.Slf4j;

@Controller
@Slf4j(topic = "e")
public class BController {

    @RequestMapping("/bTest.do")
    public void bTest() {
        log.debug("BController类中的bTest()方法执行了");
    }
}
package com.fs.web;

import com.fs.ann.Controller;
import com.fs.ann.RequestMapping;
import lombok.extern.slf4j.Slf4j;

@Controller
@Slf4j(topic = "e")
public class AController {

    @RequestMapping("/a.do")
    public void aTest() {
        log.debug("执行了AController的aTest()方法");
    }
}
package com.fs.web;

import com.fs.ann.Controller;
import com.fs.ann.RequestMapping;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
@Slf4j(topic = "e")
public class IndexController {

    // 两个参数
    @RequestMapping("/index.do")
    public void index(HttpServletRequest request, HttpServletResponse response) {
        log.debug("执行了IndexController类中的index()方法");
    }

    // response参数
    @RequestMapping("/login.do")
    public void login(HttpServletResponse response) {
        log.debug("login():欢迎登录");
    }

    // request参数
    @RequestMapping("/register.do")
    public void register(HttpServletRequest request) {
        log.debug("register()执行了");
    }

    // 无参
    @RequestMapping("/test.do")
    public void test() {
        log.debug("IndexController类中的test()方法执行了");
    }

}
<configuration>
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<Pattern>
				%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %boldYellow(%msg%n)
			</Pattern>
		</encoder>
	</appender>

	<logger name="e" level="debug" additivity="false">
		<appender-ref ref="STDOUT"/>
	</logger>

<!--	<root level="DEBUG">-->
<!--		<appender-ref ref="STDOUT"/>-->
<!--	</root>-->

</configuration>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手写Spring MVC是指自己实现一个简单的Spring MVC框架,而不是使用官方提供的Spring MVC框架。在手写Spring MVC时,你需要实现以下几个关键部分: 1. 创建一个前端控制器(Front Controller):前端控制器是整个请求处理过程的入口点,负责接收请求并进行路由。你可以使用Servlet作为前端控制器,接收所有的HTTP请求,并将它们分发给相应的控制器。 2. 定义控制器:控制器负责处理特定URL的请求,并根据请求参数进行相应的处理。你可以使用注解(如@RequestMapping)来定义控制器方法映射关系。 3. 实现视图解析器(View Resolver):视图解析器负责解析控制器返回的逻辑视图名,并将其转换为具体的视图对象或视图模板。你可以使用模板引擎(如Thymeleaf、Freemarker等)来渲染动态内容。 4. 注册控制器和视图解析器:在前端控制器中,你需要注册所有的控制器和视图解析器,以便能够正确地处理请求和渲染视图。 5. 处理请求和响应:在控制器中,你需要编写相应的方法来处理请求,并根据业务逻辑生成响应。你可以使用HttpServletRequest和HttpServletResponse对象来访问请求参数和生成响应。 6. 配置URL映射:你需要在配置文件中配置URL与控制器方法映射关系,以便能够正确地将请求分发给对应的控制器。 手写Spring MVC的过程可以帮助你更好地理解Spring MVC框架的工作原理和核心组件。但请注意,手写一个完整的Spring MVC框架可能会比较繁琐和复杂,特别是对于初学者来说。因此,如果你只是想学习Spring MVC的基本原理和用法,我建议你先阅读官方文档或参考一些教程来快速入门。如果你确实有兴趣手写Spring MVC,你可以参考引用中的博客文章,里面提供了一个手写Spring MVC框架的实现示例。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [手写 springmvc](https://download.csdn.net/download/knight_black_bob/10207699)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [SpringMvc手写简单实现篇 - MVC完结篇](https://blog.csdn.net/qq_35551875/article/details/121811048)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值