手写实现Spring MVC

发布时间:2024-04-01 13:01

⼿写实现Spring MVC 框架

根据SpringMVC执⾏的⼤致原理,⼿写一个spring mvc的简易版框架

\"手写实现Spring

第 1 节 代码的整体结构

\"手写实现Spring

第 2 节 依赖文件pom.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/xsd/maven-4.0.0.xsd\">
  <modelVersion>4.0.0modelVersion>

  <groupId>com.lagou.edugroupId>
  <artifactId>my-mvcartifactId>
  <version>1.0-SNAPSHOTversion>
  <packaging>warpackaging>

  <name>my-mvc Maven Webappname>
  
  <url>http://www.example.comurl>

  <properties>
    <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    <maven.compiler.source>11maven.compiler.source>
    <maven.compiler.target>11maven.compiler.target>
  properties>

  <dependencies>
    <dependency>
      <groupId>junitgroupId>
      <artifactId>junitartifactId>
      <version>4.12version>
      <scope>testscope>
    dependency>
    
    <dependency>
      <groupId>javax.servletgroupId>
      <artifactId>javax.servlet-apiartifactId>
      <version>4.0.1version>
      <scope>providedscope>
    dependency>
    <dependency>
      <groupId>commons-langgroupId>
      <artifactId>commons-langartifactId>
      <version>2.6version>
    dependency>
  dependencies>
  <build>
    <plugins>
      
      <plugin>
        <groupId>org.apache.maven.pluginsgroupId>
        <artifactId>maven-compiler-pluginartifactId>
        <version>3.1version>
        <configuration>
          <source>11source>
          <target>11target>
          <encoding>UTF-8encoding>
          
          <compilerArgs>-parameterscompilerArgs>
        configuration>
      plugin>
      
      <plugin>
        <groupId>org.apache.tomcat.mavengroupId>
        <artifactId>tomcat7-maven-pluginartifactId>
        <version>2.2version>
        <configuration>
          <port>8082port>
          <path>/path>
        configuration>
      plugin>
    plugins>
  build>
project>

第 3 节 具体代码实现之自定义注解

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

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

@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    String value() default \"\";
}

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

第 4 节 具体代码实现之properties文件和web.xml文件

springmvc.properties文件

scanPackage=com.lagou.demo

web.xml文件



<web-app>
  <display-name>Archetype Created Web Applicationdisplay-name>
  
  <servlet>
    <servlet-name>mymvcservlet-name>
    <servlet-class>com.lagou.edu.mvcframework.servlet.MyDispatcherServletservlet-class>
    <init-param>
      <param-name>contextConfigLocationparam-name>
      <param-value>springmvc.propertiesparam-value>
    init-param>
  servlet>
  <servlet-mapping>
    <servlet-name>mymvcservlet-name>
    <url-pattern>/* url-pattern>
  servlet-mapping>
web-app>

第 5 节 具体代码实现之自己写的测试controller

controller

package com.lagou.demo.controller;

import com.lagou.demo.service.IDemoService;
import com.lagou.edu.mvcframework.annotation.MyAutowired;
import com.lagou.edu.mvcframework.annotation.MyController;
import com.lagou.edu.mvcframework.annotation.MyRequestMapping;

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


/**
 * @author lane
 * @date 2021年04月02日 下午2:28
 */
@MyController
@MyRequestMapping(\"/demo\")
public class DemoController {
    @MyAutowired
    private IDemoService demoService;
    @MyRequestMapping(\"/query\")
    public String query(HttpServletRequest request, HttpServletResponse response,String name){
      return  demoService.get(name);

    }
}

service

package com.lagou.demo.service;

/**
 * @author lane
 * @date 2021年04月02日 下午2:30
 */
public interface IDemoService {
    String get(String name);
}

impl

package com.lagou.demo.service.impl;

import com.lagou.demo.service.IDemoService;
import com.lagou.edu.mvcframework.annotation.MyService;

/**
 * @author lane
 * @date 2021年04月02日 下午2:31
 */
@MyService
public class DemoServiceImpl implements IDemoService {
    @Override
    public String get(String name) {
        System.out.println(\"service 实现类中的name为:\"+name);
        return name;
    }
}

第 6 节 具体代码实现之Handler对象

主要是因为反射调用metho.invoke(obj,args[])需要传递参数,故而封装成对象,以便和url绑定

package com.lagou.edu.mvcframework.pojo;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * uri和方法映射需要多个参数封装才行
 * @author lane
 * @date 2021年04月06日 上午11:13
 */
public class Handler {
    //对象
    private Object controller;
    //方法
    private Method method;
    //url 可以存字符串,pattern是正则类型
    private Pattern pattern;
    //参数顺序,参数绑定,key是参数名value 是参数的顺序
    private Map<String,Integer> paramIndexMapping;

    public Handler(Object controller, Method method, Pattern pattern) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        this.paramIndexMapping = new HashMap<>();
    }

    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;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }

    public Map<String, Integer> getParamIndexMapping() {
        return paramIndexMapping;
    }

    public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
        this.paramIndexMapping = paramIndexMapping;
    }
}

第 7 节 具体代码实现之自己手写的DispatcherServlet(核心)

package com.lagou.edu.mvcframework.servlet;

import com.lagou.edu.mvcframework.annotation.MyAutowired;
import com.lagou.edu.mvcframework.annotation.MyController;
import com.lagou.edu.mvcframework.annotation.MyRequestMapping;
import com.lagou.edu.mvcframework.annotation.MyService;
import com.lagou.edu.mvcframework.pojo.Handler;
import org.apache.commons.lang.StringUtils;

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.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author lane
 * @date 2021年04月02日 上午11:35
 */
public class MyDispatcherServlet extends HttpServlet {
    //配置文件信息
    private Properties properties = new Properties();
    //缓存扫描到的类的全限定类名
    private List<String> classNames = new ArrayList<>();
    //ioc容器
    private Map<String, Object> ioc = new HashMap<>();
    //handlerMapping映射器
    //private Map handlerMapping = new HashMap<>();
    //添加新的handlerMapping
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、 加载配置文件 springmvc.properties
        String contextConfigLocation = config.getInitParameter(\"contextConfigLocation\");
        System.out.println(contextConfigLocation);
        doLoadConfig(contextConfigLocation);

        //2、 扫描相关的类相关的注解
        doScan(properties.getProperty(\"scanPackage\"));
        //3、 初始化bean对象(ioc注解实现)
        doInstance();
        //4、 实现依赖注入 di
        doAutowired();
        //5、 构造handlerMapping处理器映射器,建立url和方法的对应关系
        initHandlerMapping();
        System.out.println(\"spring mvc 初始化完成~~~~~~~~\");
        //6、 等待请求进入

    }
    /**
     * 构建handlerMapping,建立url和method之间到关系
     * @author lane
     * @date 2021/4/2 下午4:26
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty())return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(MyController.class)){continue;}

            String baseUrl = \"\";
            //是否有注解@MyRequestMapping
            if (aClass.isAnnotationPresent(MyRequestMapping.class)){
                String value = aClass.getAnnotation(MyRequestMapping.class).value();
                baseUrl = value;
            }
            //获取方法
            Method[] methods = aClass.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if (!method.isAnnotationPresent(MyRequestMapping.class)){continue;}
                MyRequestMapping methodAnnotation = method.getAnnotation(MyRequestMapping.class);
                String methodValue = methodAnnotation.value();
                baseUrl = baseUrl+methodValue;
                //handlerMapping.put(baseUrl,method);
                //把方法信息封装成一个handler对象
                Handler handler = new Handler(entry.getValue(),method, Pattern.compile(baseUrl));
                //获取参数信息
                Parameter[] parameters = method.getParameters();
                //绑定参数顺序
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];
                    if(parameter.getType()==HttpServletRequest.class||parameter.getType()==HttpServletResponse.class){
                        //如果参数为request或response那么就是获取简单的名字为HttpServletRequest或HttpServletResponse
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),j);
                    }else {
                        //
                        handler.getParamIndexMapping().put(parameter.getName(),j);
                    }
                    
                }
                //缓存起来url和method之间的关系
                handlerMapping.add(handler);
            }

        }


    }

    //di
    private void doAutowired() {
        if (ioc.isEmpty()) return;
        // 遍历容器获取里面的对象属性
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            //遍历属性
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];
                //判断是否存在@Myautowired注解
                if (declaredField.isAnnotationPresent(MyAutowired.class)) {
                    MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
                    declaredField.setAccessible(true);
                    String value = annotation.value();
                    //判断是否注解存在值,注入属性对象到遍历对象中
                    try {
                        if (!\"\".equals(value.trim())) {
                            //如果注解值不为空,就以注解中的value为key获取对象进行di
                            declaredField.set(entry.getValue(), ioc.get(value));

                        } else {
                            //如果注解中的值为空,就获取接口的全线定名为key获取对象进行注入
                            declaredField.set(entry.getValue(), ioc.get(declaredField.getType().getName()));

                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }

        }


    }

    //ioc容器
    private void doInstance() {
        if (classNames.size() == 0) return;
        try {
            for (int i = 0; i < classNames.size(); i++) {
                String className = classNames.get(i);
                Class<?> aClass = Class.forName(className);
                //如果有controller注解
                if (aClass.isAnnotationPresent(MyController.class)) {
                    //获取controller类名小写作为bean的ID,就不自定义beanid了
                    String aClassName = aClass.getSimpleName();
                    //首字母小写
                    String beanNameLower = lowerFirst(aClassName);
                    ioc.put(beanNameLower, aClass.newInstance());

                } else if (aClass.isAnnotationPresent(MyService.class)) {

                    MyService annotation = aClass.getAnnotation(MyService.class);
                    //获取注解的value值
                    String value = annotation.value();
                    //判断是否指定value,若指定就按指定的为key,否则就类名首字母小写
                    if (!\"\".equals(value.trim())) {
                        ioc.put(value, aClass.newInstance());
                    } else {
                        ioc.put(lowerFirst(aClass.getSimpleName()), aClass.newInstance());
                    }
                    //service层一般是有接口的在放一份接口为id对象到ioc中,便于接口依赖注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (int j = 0; j < interfaces.length; j++) {
                        //接口的全线定名作为id
                        ioc.put(interfaces[j].getName(), aClass.newInstance());
                    }
                }
            }


        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    //首字母小写方法
    public String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        if (chars[0] >= \'A\' && chars[0] <= \'Z\') {
            chars[0] = (char) (chars[0] + 32);
        }
        return String.valueOf(chars);

    }

    //扫描类
    private void doScan(String scanPackage) {
        // 获取路径
        String scanPagePath = Thread.currentThread().getContextClassLoader().getResource(\"\").getPath() + scanPackage.replaceAll(\"\\\\.\", \"/\");
        System.out.println(scanPagePath);
        File pack = new File(scanPagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                //递归扫描 com.lagou.edu.controller
                doScan(scanPackage + \".\" + file.getName());
            } else if (file.getName().endsWith(\".class\")) {
                String className = scanPackage + \".\" + file.getName().replaceAll(\".class\", \"\");
                classNames.add(className);
            }
        }
    }

    //加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        //获取文件流
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @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 {
        // 处理请求,获取uri
        Handler handler = getHandler(req);
        if (handler==null){
            resp.getWriter().write(\"404 not found!\");
            return;
        }
        // 参数绑定
        // 获取我们要传入的参数数组类型,因而获取其长度
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        // 传入参数的数组,以便反射调用方法执行
        Object[] paramValues = new Object[parameterTypes.length];
        // 获取请求中的参数集合
        Map<String, String[]> parameterMap = req.getParameterMap();
        // 遍历所有的参数,填充除了request 和response
        for (Map.Entry<String,String[]> parameter : parameterMap.entrySet() ) {
            //多个同类型的参数值改成,拼接形式
            String value = StringUtils.join(parameter.getValue(), \",\");
            //判断参数是否在我们的handler对象的参数集合中
            if (!handler.getParamIndexMapping().containsKey(parameter.getKey())){continue;}
            //如果存在则获取index
            Integer index = handler.getParamIndexMapping().get(parameter.getKey());
            //放入要传的参数有序数组中
            paramValues[index] = value;
        }
        //最后放入req和resp
        Integer reqIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        Integer respIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        paramValues[reqIndex] = req;
        paramValues[respIndex] = resp;
        //调用handler的method方法执行
        try {
            handler.getMethod().invoke(handler.getController(), paramValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }
    // 获取handler对象
    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()){return null;}
        String url = req.getRequestURI();
        System.out.println(url);
        for (Handler handler:handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (!matcher.matches()){return null;}
            return handler;
        }
        return null;
    }

}

第 8 节 乱码解决

8.1 Post请求乱码,web.xml中加⼊过滤器

  <filter>
    <filter-name>encodingfilter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
    <init-param>
      <param-name>encodingparam-name>
      <param-value>UTF-8param-value>
    init-param>
  filter>
  <filter-mapping>
    <filter-name>encodingfilter-name>
    <url-pattern>/*url-pattern>
  filter-mapping>
8.2 Get请求乱码 tomcat配置文件server.xml加上编码
<Connector URIEncoding=\"utf-8\" connectionTimeout=\"20000\" port=\"8080\"
protocol=\"HTTP/1.1\" redirectPort=\"8443\"/>

第 9 节 测试结果

406, 2021 2:14:37 下午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler [\"http-bio-8082\"]
406, 2021 2:14:37 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service Tomcat
406, 2021 2:14:37 下午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet Engine: Apache Tomcat/7.0.47
406, 2021 2:14:38 下午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler [\"http-bio-8082\"]
springmvc.properties
/Volumes/workspace/startspace/my-mvc/target/classes/com/lagou/demo
/Volumes/workspace/startspace/my-mvc/target/classes/com/lagou/demo/controller
/Volumes/workspace/startspace/my-mvc/target/classes/com/lagou/demo/service
/Volumes/workspace/startspace/my-mvc/target/classes/com/lagou/demo/service/impl
spring mvc 初始化完成~~~~~~~~
/demo/query
service 实现类中的name为:lisi

第 10 节 总结

虽然只是简单的实现的Spring mvc功能,但是里面包含了很多知识比如自定义注解和注解实现ioc等,还是很值得去反复学习和敲代码debug的。再总结下mvc的执行过程就是

//1、 加载配置文件 springmvc.properties
String contextConfigLocation = config.getInitParameter(\"contextConfigLocation\");
doLoadConfig(contextConfigLocation);
//2、 扫描相关的类相关的注解
doScan(properties.getProperty(\"scanPackage\"));
//3、 初始化bean对象(ioc注解实现)
doInstance();
//4、 实现依赖注入 di
doAutowired();
//5、 构造handlerMapping处理器映射器,建立url和方法的对应关系
initHandlerMapping();
System.out.println(\"spring mvc 初始化完成~~~~~~~~\");
//6、 等待请求进入

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号