【Spring】手写Spring MVC

Spring MVC原理

Spring的MVC框架主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成。html

完整的Spring MVC处理 流程以下:前端

SpringMVC接口解释

DispatcherServlet接口:java

Spring提供的前端控制器,全部的请求都有通过它来统一分发。在DispatcherServlet将请求分发给Spring Controller以前,须要借助于Spring提供的HandlerMapping定位到具体的Controller。node

HandlerMapping接口:git

可以完成客户请求到Controller映射。web

Controller接口:spring

须要为并发用户处理上述请求,所以实现Controller接口时,必须保证线程安全而且可重用。数组

Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。spring-mvc

从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程当中的控制器,而ModelAndView是Http请求过程当中返回的模型(Model)和视图(View)。安全

ViewResolver接口:

Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。

手写Spring MVC

本次实现没有视图解析内容。主要包括,自动扫描class类、经过解析注解实现bean的实例化、bean之间的依赖注入、经过注解映射路径返回正确的处理方法。

Spring MVC框架主要依赖于Java的反射机制实现。实现原理与上面描述一致。

核心Servlet

工程名MySpringMVC
代码存放servlet包。
DispatcherServlet

package zqq.servlet;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import zqq.annotations.EnjoyAuthowired;
import zqq.annotations.EnjoyController;
import zqq.annotations.EnjoyRequestMapping;
import zqq.annotations.EnjoyRequestParam;
import zqq.annotations.EnjoyService;
import zqq.controller.ZqqController;

/**
 * Servlet implementation class DispatcherServlet
 */
public class DispatcherServlet extends HttpServlet
{
    private static final long serialVersionUID = 1L;

    // 扫描获得的类名集合
    List<String> classNames = new ArrayList<String>();

    // 存放全部Spring实例的Map
    Map<String, Object> beans = new HashMap<String, Object>();

    // 存放全部路径映射
    Map<String, Object> handlerMap = new HashMap<String, Object>();

    /**
     * @see HttpServlet#HttpServlet()
     */
    public DispatcherServlet()
    {
    }

    /**
     * @see Servlet#init(ServletConfig)
     */
    public void init(ServletConfig config) throws ServletException
    {
        // 一、扫描工程有多少class
        doScanPackage("zqq");
        // 打印全部class
        for (String name : classNames)
        {
            System.out.println(name);
        }
        // 二、实例化
        doInstance();
        for (Map.Entry<String, Object> entry : beans.entrySet())
        {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        // 三、注入
        doIoC();

        // 四、请求映射
        buildMapping();

        for (Map.Entry<String, Object> entry : handlerMap.entrySet())
        {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        doPost(request, response);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        // springmvc /zqq/query
        String uri = request.getRequestURI();

        String context = request.getContextPath(); // springmvc

        String path = uri.replace(context, ""); // /zqq/query

        // 获取映射对应的method
        Method method = (Method) handlerMap.get(path);

        ZqqController instance = (ZqqController) beans.get("/" + path.split("/")[1]);
        
        Object args[] = this.hand(request, response, method);
        
        try
        {
            method.invoke(instance, args);
        } catch (IllegalAccessException e)
        {
            e.printStackTrace();
        } catch (IllegalArgumentException e)
        {
            e.printStackTrace();
        } catch (InvocationTargetException e)
        {
            e.printStackTrace();
        }
        
    }

    /**
     * @author qqz
     * @date 2018年7月12日 上午1:02:44 扫描当前路径下有多少个class类
     * @param string
     */
    private void doScanPackage(String basePackage)
    {
//      URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
        String filepath = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/")).getFile();
        try
        {
            filepath= java.net.URLDecoder.decode(filepath,"utf-8");
        } catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }  
//      String fileStr = url.getFile();
        
        // 目录对象
        File file = new File(filepath);
        String[] filesStr = file.list();

        // 递归处理路径basepackage下的类文件
        for (String path : filesStr)
        {
            File filePath = new File(filepath + path);
            if (filePath.isDirectory())
            {
                doScanPackage(basePackage + "." + path);
            } else
            {
                // 获得class 全类名路径 zqq.controller.ZqqController
                classNames.add(basePackage + "." + filePath.getName());
            }
        }
    }

    /**
     * @author qqz
     * @date 2018年7月12日 上午1:11:04 TODO
     */
    private void doInstance()
    {
        if (classNames.size() <= 0)
        {
            System.out.println("scan classes failed!");
        }

        for (String className : classNames)
        {
            // 去掉.class后缀
            String cn = className.replace(".class", "");

            try
            {
                Class<?> clazz = Class.forName(cn);
                // 处理带有EnjoyController注解的类
                if (clazz.isAnnotationPresent(EnjoyController.class))
                {
                    // 实例化对象
                    Object instance = clazz.newInstance();
                    EnjoyRequestMapping reqMapping = clazz.getAnnotation(EnjoyRequestMapping.class);
                    String key = reqMapping.value();
                    beans.put(key, instance);
                } else if (clazz.isAnnotationPresent(EnjoyService.class))
                {
                    // 实例化对象
                    Object instance = clazz.newInstance();
                    EnjoyService service = clazz.getAnnotation(EnjoyService.class);
                    String key = service.value();
                    beans.put(key, instance);
                } else
                {
                    continue;
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
            {
                e.printStackTrace();
            }
        }
    }

    /**
     * @author qqz 属性注入
     * @date 2018年7月12日 上午1:21:10 TODO
     */
    private void doIoC()
    {
        if (beans.entrySet().size() <= 0)
        {
            System.out.println("instance bean failed.");
            return;
        }

        for (Map.Entry<String, Object> entry : beans.entrySet())
        {
            Object instance = entry.getValue();
            Class<?> clazz = instance.getClass();

            if (clazz.isAnnotationPresent(EnjoyController.class))
            {
                // 获取类中全部属性
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields)
                {
                    // 获取声明注入的属性
                    if (field.isAnnotationPresent(EnjoyAuthowired.class))
                    {
                        EnjoyAuthowired authowired = field.getAnnotation(EnjoyAuthowired.class);
                        // 获取注解EnjoyAutowired中命名的值
                        String value = authowired.value();
                        // 放开权限设置属性值
                        field.setAccessible(true);
                        try
                        {
                            field.set(instance, beans.get(value));
                        } catch (IllegalArgumentException e)
                        {
                            e.printStackTrace();
                        } catch (IllegalAccessException e)
                        {
                            e.printStackTrace();
                        }
                    } else
                    {
                        continue;
                    }
                }
            }

        }
    }

    /**
     * @author qqz
     * @date 2018年7月12日 上午1:32:25 TODO
     */
    private void buildMapping()
    {
        if (beans.entrySet().size() <= 0)
        {
            System.out.println("instance bean failed.");
            return;
        }

        for (Map.Entry<String, Object> entry : beans.entrySet())
        {
            Object instance = entry.getValue();
            Class<?> clazz = instance.getClass();
            // 映射是在Controller层
            if (clazz.isAnnotationPresent(EnjoyController.class))
            {
                // 获取类映射
                EnjoyRequestMapping requestMapping = clazz.getAnnotation(EnjoyRequestMapping.class);
                String classPath = requestMapping.value();

                // 获取方法上的映射
                Method[] methods = clazz.getMethods();

                for (Method method : methods)
                {
                    if (method.isAnnotationPresent(EnjoyRequestMapping.class))
                    {
                        EnjoyRequestMapping requestMapping1 = method.getAnnotation(EnjoyRequestMapping.class);

                        String methodPath = requestMapping1.value();

                        // 构建方法路径与方法的映射
                        handlerMap.put(classPath + methodPath, method);

                    } else
                    {
                        continue;
                    }
                }

            }
        }
    }

    /**
     * @author qqz
     * @date 2018年7月12日 上午1:59:48
     * 方法参数注解解析
     * @param request
     * @param response
     * @param method
     * @return
     */
    private static Object[] hand(HttpServletRequest request, HttpServletResponse response, Method method)
    {
        // 拿到当前执行的方法有哪些参数
        Class<?>[] paramClazzs = method.getParameterTypes();

        // 根据参数的个数,new 一个参数的数组, 将方法里全部参数赋值到args来
        Object[] args = new Object[paramClazzs.length];

        int arg_i = 0;
        int index = 0;
        for (Class<?> paramClazz : paramClazzs)
        {
            if (ServletRequest.class.isAssignableFrom(paramClazz))
            {
                args[arg_i++] = request;
            }

            if (ServletResponse.class.isAssignableFrom(paramClazz))
            {
                args[arg_i++] = response;
            }

            // 从0-3判断有没有RequestParam注解,很明显paramClazz为0和1时,不是,当为2和3时为@RequestParam,须要
            // 解析[@zqq.annotation.EnjoyRequestParam(value=name)]
            Annotation[] paramAns = method.getParameterAnnotations()[index];
            if (paramAns.length > 0)
            {
                for (Annotation paramAn : paramAns)
                {
                    if (EnjoyRequestParam.class.isAssignableFrom(paramAn.getClass()))
                    {
                        EnjoyRequestParam rp = (EnjoyRequestParam)paramAn;
                        //找到注解里的name和age
                        args[arg_i++] = request.getParameter(rp.value());
                    }
                }
            }
            index ++;
        }
        return args;
    }
}

注解定义

代码存放的包annotations中。包括以下几个

EnjoyAuthowired.java
EnjoyController.java
EnjoyRequestMapping.java
EnjoyRequestParam.java
EnjoyService.java

属性注解EnjoyAuthowired.java

package zqq.annotations;

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

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

Controller注解EnjoyController.java

package zqq.annotations;

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

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

映射注解EnjoyRequestMapping.java

package zqq.annotations;

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

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

参数注解EnjoyRequestParam.java

package zqq.annotations;

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

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface EnjoyRequestParam
{
    String value() default "";
}

Service注解EnjoyService.java

package zqq.annotations;


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

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

控制层

代码存放controller包。

/**
 * ZqqController.java
 */
package zqq.controller;

import java.io.IOException;
import java.io.PrintWriter;

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

import zqq.annotations.EnjoyAuthowired;
import zqq.annotations.EnjoyController;
import zqq.annotations.EnjoyRequestMapping;
import zqq.annotations.EnjoyRequestParam;
import zqq.service.ZqqService;

@EnjoyController
@EnjoyRequestMapping("/zqq")
public class ZqqController
{
    @EnjoyAuthowired("ZqqServiceImpl")
    private ZqqService zqqService;

    @EnjoyRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp, @EnjoyRequestParam("name") String name,
            @EnjoyRequestParam("age") String age)
    {
        PrintWriter pw;
        try
        {
            pw = resp.getWriter();
            String result = zqqService.query(name, age);
            pw.write(result);
        } catch (IOException e)
        {
            e.printStackTrace();
        }

    }
}

Service层

代码存放service包

/**
 * ZqqService.java
 */
package zqq.service;
public interface ZqqService
{
    String query(String name,String age);
}

Service实现类存放service/impl

/**
 * ZqqServiceImpl.java
 */
package zqq.service.impl;

import zqq.annotations.EnjoyService;
import zqq.service.ZqqService;

@EnjoyService("ZqqServiceImpl")
public class ZqqServiceImpl implements ZqqService
{

    /*
     * (non-Javadoc)
     * 
     * @see zqq.service.ZqqService#query(java.lang.String, java.lang.String)
     */
    @Override
    public String query(String name, String age)
    {
        return "{name:" + name + ",age:" + age + "}";
    }

}

web层

src/main/webapp/WEB-INF/web.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>DispatcherServlet</servlet-name>
    <display-name>DispatcherServlet</display-name>
    <description></description>
    <servlet-class>zqq.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

使用

部署后访问localhost:8080/MySpringMVC/zqq/query?name=zqq&age=18
能够在页面上看到请求中的name和age。

项目码云路径

MySpringMVC

参考资料:
SpringMVC框架介绍