跟我一起造轮子 手写springmvc

  作为java程序员,项目中使用到的主流框架多多少少和spring有关联,在面试的过程难免会问一些spring springmvc spring boot的东西,比如设计模式的使用、 怎么实现springioc 怎么实现springmvc诸如此类的问题,今天我们就来探寻spring mvc的实现,然后自己实现一个简单的spring mvc

一. 了解spring mvc的基本运行流程

  ps: 网上一大堆关于springmvc的详细讲解,在这里就不累赘了

  

  小结:spring mvc的核心是DispatcherServlet,DispatcherServlet继承于HttpServlet,可以说spring mvc是基于Servlet的一个实现,DispatcherServlet负责协调和组织不同组件以完成请求处理并返回响应的工作,实现了MVC模式。

二. 梳理简单SpringMVC的设计思路

  1. 初始化容器

       1.1 读取配置文件

          1.1.1.加载配置文件信息到DispatcherServlet

       1.2  根据配置扫描包、初始化容器和组件

          1.2.1.根据配置信息递归扫描包

          1.2.2.把包下的类实例化 并且扫描注解

          1.2.3.根据类的方法和注解,初始化HandlerMapping

  2. 处理业务请求

      2.1 处理请求业务

        2.2.1 首先拿到请求URI

            2.2.2 根据URI,在HandlerMapping中查找和URI对应的Handler

               2.2.3 根据Handler里面的method中的参数名称和http中的请求参数匹配,填充method参数,反射调用

三. 没时间解释了,快上车

    ps :环境基于maven idea tomat(端口8080) servlet

  1.搭建一个基本web项目,并导入idea配置servlet 和javassist pom依赖 如下

    创建命令: mvn archetype:generate -DgroupId=com.adminkk -DartifactId=adminkk-mvc -DpackageName=com.adminkk -Dversion=1.0

pom依赖

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.adminkk</groupId>
  <artifactId>adminkk-mvc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <packaging>war</packaging>

  <name>adminkk-mvc</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>

    <!--servlet-->
      <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
        <scope>provided</scope>
      </dependency>

    <!-- asm -->
    <dependency>
      <groupId>asm</groupId>
      <artifactId>asm</artifactId>
      <version>3.3.1</version>
    </dependency>

    <!-- javassist -->
    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.23.1-GA</version>
    </dependency>

  </dependencies>
</project>

    

2.创建mvc的注解 Controller RequestMapping 和统一异常处理类、方法参数工具类ParameterNameUtils  

package com.adminkk.annotation;

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

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {

    public String value() default "";

    public String description() default "";
}
package com.adminkk.annotation;

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

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {

    public String value() default "";

    public String method() default "";

    public String description() default "";
}
package com.adminkk.exception;

public  final  class MvcException extends RuntimeException{

    public MvcException() {
        super();
    }

    public MvcException(String message) {
        super(message);
    }

}
package com.adminkk.tools;

import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

import java.lang.reflect.Method;

public final class ParameterNameUtils {

    public final static String[] getParameterNamesByJavassist(final Class<?> clazz, final Method method) {

        ClassPool pool = ClassPool.getDefault();
        try {
            CtClass ctClass = pool.get(clazz.getName());
            CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName());

            // 使用javassist的反射方法的参数名
            MethodInfo methodInfo = ctMethod.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
                    .getAttribute(LocalVariableAttribute.tag);
            if (attr != null) {

                String[] rtv = new String[ctMethod.getParameterTypes().length];
                int len = ctMethod.getParameterTypes().length;
                // 非静态的成员函数的第一个参数是this
                int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
                for (int i = 0; i < len; i++) {
                    rtv[i] = attr.variableName(i + pos);
                }
                return rtv;
            }
        } catch (NotFoundException e) {
            System.out.println("获取异常"+ e.getMessage());
        }
        return new String[0];
    }

}

3.创建 HandlerMapping类 主要是两个方法  doInit初始化 doService处理请求 相关代码如下

package com.adminkk.handler;

import com.adminkk.scan.FileScaner;
import com.adminkk.scan.Scaner;
import com.adminkk.scan.XmlScaner;
import com.adminkk.tools.ParameterNameUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final  class HandlerMapping {

    private static  final Map<String,Handler> handlerMapping = new HashMap<String, Handler>();

    private static  final List<Scaner> scaners = new ArrayList<>(2);
    static {
        scaners.add(new XmlScaner());
        scaners.add(new FileScaner());
    }

    public  static void scanPackage(String scanUrl) throws RuntimeException, IllegalAccessException, InstantiationException, ClassNotFoundException {

        for (Scaner scaner : scaners) {
            scaner.doScane(scanUrl);
        }

    }

    public static void doInit(String scanUrl) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
        scanPackage(scanUrl);
    }

    public static void doService(HttpServletRequest request, HttpServletResponse response) {

        String requestURI = request.getRequestURI();
        System.out.println("请求地址是="+ requestURI);
        Handler handler = handlerMapping.get(requestURI);
        if(handler == null){
            System.out.println("请求地址是="+ requestURI+" 没有配置改路径");
            return;
        }
        Method method = handler.getMethod();
        Object instance = handler.getInstance();
        response.setCharacterEncoding("UTF-8");
        //response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;

        try {

            //这里是简单的解析 可以像springmvc那样解析处理
            Map<String, String[]> parameterMap = request.getParameterMap();
            String[] parameters = ParameterNameUtils.getParameterNamesByJavassist(instance.getClass(),method);
            Object[]  parameter = new Object[parameters.length];
            if(parameters != null && parameters.length > 0){
                for (int i = 0; i < parameters.length; i++) {
                    final String simpleName = parameters[i];
                    StringBuilder parameterSb = new  StringBuilder();
                    final String[] parameterStr = parameterMap.get(simpleName);
                    if(parameterStr != null){
                        for (int j = 0; j < parameterStr.length; j++) {
                            parameterSb.append(parameterStr[j]);
                        }
                    }
                    parameter[i] = parameterSb.toString();
                }
            }

            writer = response.getWriter();
            String result = (String) method.invoke(instance,parameter);
            writer.print(result);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("请求地址是="+ requestURI+" 执行异常");
            writer.print("业务执行异常");
        }finally {
            writer.flush();
            writer.close();
        }
    }

    public static Handler addHandlerMapping(String url,Handler handler) {
        return handlerMapping.put(url,handler);
    }

    public static Handler getHandlerMapping(String url) {
        return handlerMapping.get(url);
    }

}

扫描包

package com.adminkk.scan;

public interface Scaner {

    void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException;
}

package com.adminkk.scan;

import com.adminkk.exception.MvcException;import com.adminkk.factory.BeanPostProcessor;import com.adminkk.factory.MvcBeanPostProcessor;import com.adminkk.factory.ServiceBeanPostProcessor;import com.adminkk.handler.HandlerMapping;import javassist.ClassClassPath;import javassist.ClassPool;

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

public final  class FileScaner implements Scaner{

public FileScaner() {    }

public static final List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();    static {        beanPostProcessorList.add(new MvcBeanPostProcessor());        beanPostProcessorList.add(new ServiceBeanPostProcessor());    }

@Override    public void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException {        if(scanUrl == null || scanUrl.length() == 0){            throw new MvcException("容器基础扫描路径为空,请检查参数配置");        }        String baseUrl = HandlerMapping.class.getResource("/").getPath();        String codeUrl = scanUrl.replaceAll("\\.", "/");        String path =  baseUrl + codeUrl;        File file = new File(path);        if(file == null || !file.exists()){            throw new MvcException("找不到对应扫描路径,请检查参数配置");        }        recursionRedFile(scanUrl,file);    }

//递归读取文件    private  void recursionRedFile(String scanUrl,File file) throws MvcException, ClassNotFoundException, IllegalAccessException, InstantiationException {

if(!file.exists()){            return;        }

//读取java文件        if(file.isFile()){

String beanName = scanUrl.replaceAll(".class","");            Class<?> forName = Class.forName(beanName);            //放到Javassist容器里面            ClassPool pool = ClassPool.getDefault();            ClassClassPath classPath = new ClassClassPath(forName);            pool.insertClassPath(classPath);            if(forName.isAnnotation() || forName.isEnum() || forName.isInterface() ){                return;            }            Object newInstance = forName.newInstance();

//前置执行            for (int i = 0; i < beanPostProcessorList.size() ; i++) {                BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);                beanPostProcessor.postProcessBeforeInitialization(newInstance,beanName);            }

//后置执行            for (int i = beanPostProcessorList.size()-1; i > 0  ; i++) {                BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);                beanPostProcessor.postProcessAfterInitialization(newInstance,beanName);            }            return;        }

//文件夹下面的文件都递归处理        if(file.isDirectory()){            File[] files = file.listFiles();            if(files != null && files.length >0){                for (int i = 0; i < files.length; i++) {                    File targetFile = files[i];                    recursionRedFile(scanUrl+"."+targetFile.getName(),targetFile);                }            }        }

}}
 
package com.adminkk.scan;

public final class XmlScaner implements Scaner{

    public XmlScaner() {
    }

    @Override
    public void doScane(String scanUrl) {
        //可自行扩展
    }

}

  扫描bean

package com.adminkk.factory;

import com.adminkk.exception.MvcException;

public interface BeanPostProcessor {

    Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException;

    Object postProcessAfterInitialization(Object object, String beanName) throws MvcException;
}
package com.adminkk.factory;

import com.adminkk.annotation.Controller;
import com.adminkk.annotation.RequestMapping;
import com.adminkk.exception.MvcException;
import com.adminkk.handler.Handler;
import com.adminkk.handler.HandlerMapping;

import java.lang.reflect.Method;

public class MvcBeanPostProcessor implements BeanPostProcessor{

    //扫描Controller业务
    @Override
    public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException {

        Class<?> objectClass = object.getClass();
        if(objectClass.getAnnotation(Controller.class) != null){
            RequestMapping calssRequestMappingAnnotation = objectClass.getAnnotation(RequestMapping.class);
            StringBuilder urlSb = new StringBuilder();
            if(calssRequestMappingAnnotation != null){
                urlSb.append(calssRequestMappingAnnotation.value());
            }
            Method[] methods = objectClass.getMethods();
            if(methods != null && methods.length > 0 ){
                for (int i = 0; i < methods.length; i++) {
                    Method method = methods[i];
                    RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
                    if(methodAnnotation != null){
                        String methodValue = methodAnnotation.value();
                        String url = new StringBuilder().append(urlSb).append(methodValue).toString();
                        Handler handler = HandlerMapping.getHandlerMapping(url);
                        if(handler == null){
                            handler = new Handler();
                            handler.setMethod(method);
                            handler.setInstance(object);
                            HandlerMapping.addHandlerMapping(url,handler);
                        }else {
                            throw new MvcException("请求路径"+ url + "已经存在容器中");
                        }
                    }
                }

            }
        }

        return object;
    }

    @Override
    public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException {
        return null;
    }
}
package com.adminkk.factory;

import com.adminkk.exception.MvcException;

public class ServiceBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException {
        //可自行扩展
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException {
        //可自行扩展
        return null;
    }
}

  

5.创建 DispatcherServlet

package com.adminkk.servlet;

import com.adminkk.handler.HandlerMapping;

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

@WebServlet(name = "DispatcherServlet",loadOnStartup=1,urlPatterns={"/"})
public final  class DispatcherServlet extends HttpServlet {

    public static final String BASE_SCAN_URL = "com.adminkk";

    //初始化容器
    @Override
    public void init() throws ServletException {
        doInit();
    }

    //处理业务请求
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doService(req,resp);
    }

    private void doService(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
        try {
            HandlerMapping.doService(req,resp);
        }catch (Exception e){
            e.printStackTrace();
            throw new ServletException(e.getMessage());
        }
    }

    private void doInit() throws ServletException {
        try {
            HandlerMapping.doInit(this.BASE_SCAN_URL);
        }catch (Exception e){
            e.printStackTrace();
            throw new ServletException(e.getMessage());
        }

    }
}

  

  好了,目前为止我们就写好了简版的springmvc 下面开始测试

package com.adminkk.controller;

import com.adminkk.annotation.Controller;
import com.adminkk.annotation.RequestMapping;

@Controller
@RequestMapping("/mvc")
public class MvcController {

    @RequestMapping("/index")
    public String index(){
        return  "adminkk-mvc system is running";
    }

    @RequestMapping("/arg")
    public String parameter(String argOne, String argTwo){
        return  "argOne = " + argOne + " argTwo = " + argTwo;
    }
}

  

      访问地址 http://localhost:8080/mvc/index

      

      访问地址: http://localhost:8080/mvc/arg?argOne=argOne&argTwo=argTwo

      

总结:整体实现简单的springmvc,设计上还可以扩展更多,难点在于method 获取方法上的参数名称,由于jdk1.8以前是不支持的,需要借用第三方工具 比如 asm javassist黑科技工具包来帮助实现,spring-core使用的是LocalVariableTableParameterNameDiscoverer底层是调用asm,我们这里使用的是javassist。延用这套思路还可以和spring项目结合,写一个 基于spring的springmvc项目

源代码 : https://gitee.com/chenchenche/mvc

写博客不容易,希望大家多多提建议

下一篇预告 跟我一起造轮子 手写分布式im系统(上)

原文地址:https://www.cnblogs.com/xrog/p/9820168.html

时间: 2024-10-11 22:38:55

跟我一起造轮子 手写springmvc的相关文章

1小时手写SpringMVC T5大牛带你解读Spring核心源码(附详细视频教程)

SpringMVC简介 SpringMVC是当前最优秀的MVC框架,自从Spring 2.5版本发布后,由于支持注解配置,易用性有了大幅度的提高.Spring 3.0更加完善,实现了对Struts 2的超越.现在越来越多的开发团队选择了Spring MVC. Spring为展现层提供的基于MVC设计理念的优秀的Web框架,是目前最主流的MVC框架之一 Spring3.0后全面超越Struts2,成为最优秀的MVC框架 Spring MVC通过一套MVC注解,让POJO成为处理请求的控制器,而无须

(二)springMvc原理和手写springMvc框架

我们从两个方面了解springmvc执行原理,首先我们去熟悉springmvc执行的过程,然后知道原理后通过手写springmvc去深入了解代码中执行过程. (一)SpringMVC流程图 (二)SpringMVC流程 1.  用户发送请求至前端控制器DispatcherServlet. 2.  DispatcherServlet收到请求调用HandlerMapping处理器映射器. 3.  处理器映射器找到具体的处理器(可以根据xml配置.注解进行查找),生成处理器对象及处理器拦截器(如果有则

手写SpringMVC 框架

手写SpringMVC框架 细嗅蔷薇 心有猛虎 背景:Spring 想必大家都听说过,可能现在更多流行的是Spring Boot 和Spring Cloud 框架:但是SpringMVC 作为一款实现了MVC 设计模式的web (表现层) 层框架,其高开发效率和高性能也是现在很多公司仍在采用的框架:除此之外,Spring 源码大师级的代码规范和设计思想都十分值得学习:退一步说,Spring Boot 框架底层也有很多Spring 的东西,而且面试的时候还会经常被问到SpringMVC 原理,一般

纯手写SpringMVC架构,用注解实现springmvc过程(动脑学院Jack老师课后自己练习的体会)

1.第一步,首先搭建如下架构,其中,annotation中放置自己编写的注解,主要包括service controller qualifier RequestMapping 第二步:完成对应的annotation: package com.cn.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retent

携程系统架构师带你手写spring mvc,解读spring核心源码!

讲师简介: James老师 系统架构师.项目经理 十余年Java经验,曾就职于携程.人人网等一线互联网公司,专注于java领域,精通软件架构设计,对于高并发.高性能服务有深刻的见解, 在服务化基础架构和微服务技术有大量的建设和设计经验. 课程内容: 1.为什么读Spring源码? 如果你是一名JAVA开发人员,你一定用过Spring Framework. 作为一款非常经典的开源框架,从2004年发布的1.0版本到现在的5.0版本,经历了14年的洗礼, 持久不衰 与其说现在是JAVA的天下, 不如

手写 Spring 事务、IOC、DI 和 MVC

Spring AOP 原理 什么是 AOP? AOP 即面向切面编程,利用 AOP 可以对业务进行解耦,提高重用性,提高开发效率 应用场景:日志记录,性能统计,安全控制,事务处理,异常处理 AOP 底层实现原理是采用代理实现的 Spring 事务 基本特性: 原子性 隔离性 一致性 持久性 事务控制分类: 编程式事务:手动控制事务操作 声明式事务:通过 AOP 控制事务 编程式事务实现 使用编程事务实现手动事务 @Component @Scope("prototype") public

手写IOC-SPRINGPMVC-CONNPOOL

(一)  手写IOC思路 1.扫包,将所有class文件加载到内存,判断类上是否加了ExtService注解,有就添加入map中 ,  map<String ,Object>:  key是类名,value是对象 2.遍历map,获取每个对象的所有属性,判断属性上是否有ExtAutowire,有就以属性名称为key从map中得到对应的value,通过反射给属性赋值 项目结构: (二) 手写springmvc思路 1.创建一个前端控制器(Servlet)2.在init方法中,将扫包范围的有ExtC

重复造轮子,编写一个轻量级的异步写日志的实用工具类(LogAsyncWriter)

一说到写日志,大家可能推荐一堆的开源日志框架,如:Log4Net.NLog,这些日志框架确实也不错,比较强大也比较灵活,但也正因为又强大又灵活,导致我们使用他们时需要引用一些DLL,同时还要学习各种用法及配置文件,这对于有些小工具.小程序.小网站来说,有点“杀鸡焉俺用牛刀”的感觉,而且如果对这些日志框架不了解,可能输出来的日志性能或效果未毕是与自己所想的,鉴于这几个原因,我自己重复造轮子,编写了一个轻量级的异步写日志的实用工具类(LogAsyncWriter),这个类还是比较简单的,实现思路也很

Spring系列之手写一个SpringMVC

目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 Spring系列之AOP的原理及手动实现 Spring系列之手写注解与配置文件的解析 引言 在前面的几个章节中我们已经简单的完成了一个简易版的spring,已经包括容器,依赖注入,AOP和配置文件解析等功能.这一节我们来实现一个自己的springMvc. 关于MVC/SpringMVC springMvc是一个基于mvc模式的web框架,SpringMVC框架是一种提供了MVC(模型 - 视图 - 控制器)架