SpringMVC源码手写实现

                                       SpringMVC源码手写实现

                                                                                                                                                                                     作者:田超凡

                                                                                                                                                                             时间:2019-08-23

开始编码前的准备工作:

Maven依赖新增javax.servlet依赖,用于编写的MVC核心控制器继承自HttpServlet并重写对应方法,调用对应ServletAPI方法实现

Maven依赖新增fastjson依赖处理json序列化和反序列化。

pom.xml配置如下:

        <!-- TODO TCF MVC请求映射Servlet API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        
        <!-- TODO TCF Json序列化和反序列化 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>

主要实现原理简述:

(1).首先,定义模型封装类

Request封装请求方式和请求路径(注意,此处存放的请求路径是请求URL相对项目根目录的路径,调用request.getServletPath()获取)

Handle定义处理每次请求的控制器和方法,Request和Handle是一对一的关联

Param封装请求参数

View封装控制器处理方法执行完毕之后的视图解析结果,包括视图路径和绑定的模型参数

Data封装控制器处理方法执行完毕之后直接返回的响应数据,最后通过json序列化成json字符串然后写入输出流返回给前台。

(2).其次,定义ControllerHelper控制器助手类用于加载所有视图层的控制器并建立Request和Handle的关联映射

(3).最后,编写核心控制器并在web.xml中注册servlet(或者直接通过@WebServlet注解注册)

KidDispatcherServlet是整个MVC框架最核心的管理和控制器,作用等效于Spring MVC的DispatcherServlet核心控制器

对于静态资源拦截和过滤,可以通过配置默认的servlet并定义URL拦截规则实现。可在web.xml中直接配置default servlet,也可以在KidDispatcherServlet初始化方法中定义ServletRegistration并addMapping,两种方式均可用来过滤静态资源请求。

1.定义模型封装类

Request.java 封装请求方式和请求路径

package com.tcf.kid.smart.framework.mvc;

/***
 * TODO TCF 请求信息类
 * TODO TCF 封装请求方式和请求路径
 * @author 71485
 *
 */
public class Request {

    //TODO TCF 请求方式
    private String requestMethod;
    
    //TODO TCF 请求路径
    private String requestPath;
    
    public String getRequestMethod() {
        return requestMethod;
    }
    public String getRequestPath() {
        return requestPath;
    }
    
    //TODO TCF 构造注入请求方式和请求路径
    public Request(String requestMethod,String requestPath)
    {
        this.requestMethod=requestMethod;
        this.requestPath=requestPath;
    }
    
    //TODO TCF 默认无参构造
    public Request()
    {
        
    }
}
 

Handle.java 封装处理请求的控制器和处理方法

package com.tcf.kid.smart.framework.mvc;

import java.lang.reflect.Method;

/**
 * TODO TCF 请求处理类
 * TODO TCF 封装处理请求的控制器和处理方法
 * @author 71485
 *
 */
public class Handle {

    //TODO TCF 处理请求控制器
    private Class<?> handleController;
    
    //TODO TCF 处理请求方法
    private Method handleMethod;
    
    public Class<?> getHandleController() {
        return handleController;
    }
    public Method getHandleMethod() {
        return handleMethod;
    }
    
    //TODO TCF 构造注入处理请求的控制器和处理方法
    public Handle(Class<?> handleController,Method handleMethod)
    {
        this.handleController=handleController;
        this.handleMethod=handleMethod;
    }
    
    //TODO TCF 默认无参构造
    public Handle()
    {
        
    }
}
 

Param.java 封装请求参数

package com.tcf.kid.smart.framework.mvc;

import java.util.Map;

/**
 * TODO TCF 封装请求参数
 * @author 71485
 *
 */
public class Param {

    //TODO TCF 请求参数 ParameterName:ParameterValue
    private Map<String,Object> paramMap;
    
    public Map<String, Object> getParamMap() {
        return paramMap;
    }
    
    //TODO TCF 构造注入请求参数
    public Param(Map<String,Object> paramMap)
    {
        this.paramMap=paramMap;
    }
    
    //TODO TCF 默认无参构造
    public Param()
    {
        
    }
}
 

View.java 封装视图解析器

package com.tcf.kid.smart.framework.mvc;

import java.util.Map;

/***
 * TODO TCF 视图解析器类
 * TODO TCF 定义视图名称和绑定的模型参数
 * @author 71485
 *
 */
public class View {

    //TODO TCF 视图名称
    private String viewName;
    
    //TODO TCF 绑定的模型参数
    private Map<String,Object> modelParams;
    
    public String getViewName() {
        return viewName;
    }
    public void setViewName(String viewName) {
        this.viewName = viewName;
    }
    public Map<String, Object> getModelParams() {
        return modelParams;
    }
    public void setModelParams(Map<String, Object> modelParams) {
        this.modelParams = modelParams;
    }
}
 

Data.java 封装响应结果

package com.tcf.kid.smart.framework.mvc;

/***
 * TODO TCF 输出响应数据类
 * TODO TCF 控制器处理方法直接返回的相应数据,适用于ajax或直接写入输出流的情况
 * @author 71485
 *
 */
public class Data {

    //TODO TCF 输出数据
    private Object data;
    
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    
    //TODO TCF 构造注入输出数据
    public Data(Object data) 
    {
        this.data=data;
    }
    
    //TODO TCF 默认无参构造
    public Data() 
    {
        
    }
}
 

2.定义视图层相关注解

@KidController 标注和注册控制器

package com.tcf.kid.smart.framework.annotation;

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

/**
 * TODO TCF 注册控制器组件
 * @author 71485
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface KidController {

}
 

@KidAction 标注和匹配映射请求的控制器处理方法

package com.tcf.kid.smart.framework.annotation;

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

/***
 * TODO TCF 标注控制器处理方法
 * @author 71485
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface KidAction {

    String mapping();
    
    String method();
}

3.定义控制器助手类ControllerHelper,用于建立和管理Request -> Handle的请求->处理映射关系。

package com.tcf.kid.smart.framework.helper;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;

import com.tcf.kid.smart.framework.annotation.KidAction;
import com.tcf.kid.smart.framework.annotation.KidController;
import com.tcf.kid.smart.framework.mvc.Handle;
import com.tcf.kid.smart.framework.mvc.Request;

/***
 * TODO TCF 控制器助手类
 * TODO TCF 扫描所有视图层控制器和处理方法,建立请求和处理的映射关系
 * TODO TCF 初始化加载请求和处理的映射Map
 * @author 71485
 *
 */
public class ControllerHelper {

    //TODO TCF 请求和处理的映射Map
    public static Map<Request,Handle> MAPPING_MAP=new HashMap<Request,Handle>();
    
    static
    {
        //TODO TCF 建立请求和处理之间的映射关系
        initHandleMapping();
    }
    
    //TODO TCF 扫描控制器和处理方法并建立和请求的映射关系,初始化请求和处理之间的映射Map
    public static void initHandleMapping()
    {
        try
        {
            //TODO TCF 加载控制器注解标注的控制器
            Set<Class<?>> controllerClassList=ClassHelper.loadClassByAnnotation(KidController.class);
            
            if(controllerClassList!=null && controllerClassList.size()>0)
            {
                for(Class<?> controllerClass:controllerClassList)
                {
                    //TODO TCF 控制器中定义的所有方法
                    Method[] methods=controllerClass.getDeclaredMethods();
                    
                    if(methods!=null && methods.length>0)
                    {
                        for(Method method:methods)
                        {
                            //TODO TCF 默认只有使用KidAction注解标注的类才是控制器处理方法
                            if(method.isAnnotationPresent(KidAction.class))
                            {
                                KidAction action=method.getAnnotation(KidAction.class);
                                
                                if(action!=null)
                                {
                                    String actionUrl=action.mapping();
                                    String actionMethod=action.method();
                                    
                                    if(StringUtils.isNotEmpty(actionUrl) && StringUtils.isNotEmpty(actionMethod))
                                    {
                                        //TODO TCF 请求路径
                                        String requestPath=actionUrl;
                                        
                                        //TODO TCF 请求方式
                                        String requestMethod=actionMethod.toLowerCase();
                                        
                                        Request request=new Request(requestMethod,requestPath);
                                        Handle handle=new Handle(controllerClass,method);
                                        MAPPING_MAP.put(request,handle);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
    
    //TODO TCF 根据请求方式和请求路径获取处理请求信息
    public static Handle getHandleByRequest(String requestMethod,String requestPath)
    {
        Request request=new Request(requestMethod,requestPath);
        
        Handle handle=new Handle();
        for(Map.Entry<Request,Handle> mappingEntry:MAPPING_MAP.entrySet())
        {
            Request entryRequest=mappingEntry.getKey();
            
            if(entryRequest!=null)
            {
                if(requestMethod.equals(entryRequest.getRequestMethod())
                  && requestPath.equals(entryRequest.getRequestPath()))
                {
                    handle=mappingEntry.getValue();
                    break;
                }
            }
        }
        
        return handle;
    }
}
 

4.定义处理请求控制器KidDispatcherServlet

package com.tcf.kid.smart.framework.core;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;

import com.tcf.kid.smart.framework.common.Const;
import com.tcf.kid.smart.framework.helper.BeanHelper;
import com.tcf.kid.smart.framework.helper.ClassHelper;
import com.tcf.kid.smart.framework.helper.ControllerHelper;
import com.tcf.kid.smart.framework.mvc.Data;
import com.tcf.kid.smart.framework.mvc.Handle;
import com.tcf.kid.smart.framework.mvc.Param;
import com.tcf.kid.smart.framework.mvc.View;
import com.tcf.kid.smart.framework.util.CodecUtil;
import com.tcf.kid.smart.framework.util.JsonUtil;
import com.tcf.kid.smart.framework.util.PropertiesUtil;
import com.tcf.kid.smart.framework.util.ReflectUtil;
import com.tcf.kid.smart.framework.util.StreamUtil;

/***
 * TODO TCF MVC核心处理请求控制器
 * @author 71485
 *
 */
@WebServlet
public class KidDispatcherServlet extends HttpServlet{

    //TODO TCF 初始化
    @Override
    public void init(ServletConfig servletConfig) throws ServletException 
    {
        //TODO TCF 启动IOC/AOP容器,扫包
        ClassHelper.loadCoreClass();
        
        //TODO TCF 注册Servlet资源服务(各类视图/其他静态资源)
        ServletContext servletContext=servletConfig.getServletContext();
        
        //TODO TCF HTML视图资源
        ServletRegistration viewRegistration=servletContext.getServletRegistration(Const.STATIC_RESOURCE_TYPE.JSP);
        viewRegistration.addMapping(PropertiesUtil.loadPropertiesFile(Const.PROPERTIES_FILES.BASE_PROPERTIES).getProperty(Const.BASE_PROPERTIES_KEYS.VIEW_RESOURCE));

        //TODO TCF ...其他视图资源
        
        //TODO TCF 其他静态资源
        ServletRegistration staticRegistration=servletContext.getServletRegistration(Const.STATIC_RESOURCE_TYPE.DEFAULT);
        staticRegistration.addMapping(PropertiesUtil.loadPropertiesFile(Const.PROPERTIES_FILES.BASE_PROPERTIES).getProperty(Const.BASE_PROPERTIES_KEYS.STATIC_RESOURCE));
        staticRegistration.addMapping("*.js");
    }
    
    //TODO TCF 服务
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    {
        try
        {
            //TODO TCF 请求方式
            String requestMethod=request.getMethod().toLowerCase();
            
            //TODO TCF 请求路径
            //TODO TCF request.getServletPath()获取的是相对项目的纯请求路径,如/show.do
            String requestPath=request.getServletPath();
            
            if(StringUtils.isNotEmpty(requestMethod) && StringUtils.isNotEmpty(requestPath))
            {
                //TODO TCF 根据请求信息获取处理信息
                Handle handle=ControllerHelper.getHandleByRequest(requestMethod,requestPath);
                
                if(handle!=null)
                {
                    //TODO TCF 处理请求的控制器
                    Class<?> handleController=handle.getHandleController();
                    
                    //TODO TCF 处理请求的方法
                    Method handleMethod=handle.getHandleMethod();
                    
                    //TODO TCF 处理请求的控制器实例
                    Object handleControllerInstance=BeanHelper.getBeanInstance(handleController);
                    
                    //TODO TCF 请求参数名
                    Enumeration<String> parameterNames=request.getParameterNames();
                    
                    if(parameterNames!=null)
                    {
                        //TODO TCF 请求参数Map
                        Map<String,Object> paramMap=new HashMap<String,Object>();
                        while(parameterNames.hasMoreElements())
                        {
                            //TODO TCF 请求参数名
                            String parameterName=parameterNames.nextElement();
                        
                            //TODO TCF 根据请求参数名获取请求参数
                            String parameterValue=request.getParameter(parameterName);
                            
                            paramMap.put(parameterName,parameterValue);
                        }
                        
                        //TODO TCF 封装请求参数
                        Param param=new Param(paramMap);
                        
                        //TODO TCF 请求属性
                        String requestUrl=CodecUtil.encodeStr(StreamUtil.readInputStream(request.getInputStream()));
                        if(StringUtils.isNotEmpty(requestUrl))
                        {
                            String[] parameters=requestUrl.split("&");
                            
                            if(parameters!=null && parameters.length>0)
                            {
                                for(String parameter:parameters)
                                {
                                    //TODO TCF 解析参数名和参数值
                                    String[] params=parameter.split("=");
                                    
                                    if(params!=null && params.length==2)
                                    {
                                        //TODO TCF 参数名
                                        String paramName=params[0];
                                        
                                        //TODO TCF 参数值
                                        String paramValue=params[1];
                                        
                                        //TODO TCF 存入请求作用域
                                        request.setAttribute(paramName,paramValue);
                                    }
                                }
                            }
                        }
                        
                        if(handleController!=null && handleMethod!=null)
                        {
                            //TODO TCF 调用对应的处理请求控制器的处理方法
                            Object invokeResult=ReflectUtil.invokeMethod(handleControllerInstance,handleMethod,param);
                            
                            //TODO TCF 根据控制器处理方法执行之后的返回结果进行下一步处理
                            if(invokeResult!=null)
                            {
                                if(invokeResult instanceof View)
                                {
                                    //TODO TCF 返回视图
                                    View view=(View)invokeResult;
                                    
                                    if(view!=null)
                                    {
                                        //TODO TCF 返回视图名
                                        String viewName=view.getViewName();
                                        
                                        if(StringUtils.isNotEmpty(viewName))
                                        {
                                            if(viewName.startsWith("/"))
                                            {
                                                //TODO TCF 响应方式:重定向
                                                response.sendRedirect(PropertiesUtil.loadPropertiesFile(Const.PROPERTIES_FILES.BASE_PROPERTIES).getProperty(Const.BASE_PROPERTIES_KEYS.VIEW_RESOURCE)+viewName);
                                            }
                                            else
                                            {
                                                //TODO TCF 响应方式:转发,解析模型参数
                                                //TODO TCF 返回视图并传递的渲染视图的模型参数
                                                Map<String,Object> modelParams=view.getModelParams();
                                                
                                                if(modelParams!=null)
                                                {
                                                    for(Map.Entry<String,Object> paramEntry:modelParams.entrySet())
                                                    {
                                                        //TODO TCF 需要绑定的模型参数名
                                                        String modelParamName=paramEntry.getKey();
                                                        
                                                        //TODO TCF 需要绑定的模型参数值
                                                        Object modelParamValue=paramEntry.getValue();
                                                        
                                                        //TODO TCF 存入request请求作用域
                                                        request.setAttribute(modelParamName,modelParamValue);
                                                    }
                                                }
                                                
                                                //TODO TCF 转发
                                                request.getRequestDispatcher(
                                                        PropertiesUtil.loadPropertiesFile(Const.PROPERTIES_FILES.BASE_PROPERTIES)
                                                        .getProperty(Const.BASE_PROPERTIES_KEYS.VIEW_RESOURCE)
                                                        +viewName)
                                                .forward(request,response);
                                            }
                                        }
                                    }
                                }
                                else if (invokeResult instanceof Data) 
                                {
                                    //TODO TCF 直接返回响应数据(如ajax等直接将响应数据写入输出流)
                                    Data data=(Data)invokeResult;
                                    
                                    if(data!=null)
                                    {
                                        //TODO TCF JSON序列化
                                        String jsonString=JsonUtil.pojoToJson(data.getData());
                                        
                                        response.setContentType("application/json");
                                        response.setCharacterEncoding("UTF-8");
                                        PrintWriter writer=response.getWriter();
                                        
                                        writer.write(jsonString);
                                        
                                        //TODO TCF 清空缓冲区,输出响应结果
                                        writer.flush();
                                        writer.close();
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}
 

5.配置web.xml注册核心控制器KidDispatcherServlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
    <servlet-name>KidDispatcherServlet</servlet-name>
    <servlet-class>com.tcf.kid.smart.framework.core.KidDispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>KidDispatcherServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
  
</web-app>

(6).编写测试类控制器,测试返回结果是视图解析器的清空和直接返回响应数据的情况(如Ajax异步)

package com.tcf.kid.smart.framework.demo;

import java.util.HashMap;
import java.util.Map;

import com.tcf.kid.smart.framework.annotation.KidAction;
import com.tcf.kid.smart.framework.annotation.KidController;
import com.tcf.kid.smart.framework.mvc.Data;
import com.tcf.kid.smart.framework.mvc.Param;
import com.tcf.kid.smart.framework.mvc.View;

/***
 * TODO TCF 处理请求的控制器
 * @author 71485
 *
 */

@KidController
public class TestController {

    //TODO TCF EXAMPLE 1 直接返回视图解析器解析模型参数
    @KidAction(mapping="/toPage.do",method="GET")
    public View toPage(Param param)
    {
        View view=new View();
        view.setViewName("example.jsp");
        
        return view;
    }

    //TODO TCF EXAMPLE 2 直接返回响应结果
    @KidAction(mapping="/show.do",method="GET")
    public Data showProxyResult(Param param)
    {
        System.out.println("====代理的目标方法执行====");
        System.out.println(param.getParamMap().get("message")!=null?param.getParamMap().get("message").toString():"");
        
        Map<String,Object> paramMap=new HashMap<String,Object>();
        paramMap.put("name","zhangsan");
        
        return new Data(paramMap);
    }
}
 

(7).前台测试页面index.jsp和跳转后的example.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Example页面</title>

<script type="text/javascript" src="static/js/jquery-1.4.4.min.js"></script>
</head>

<body>

<h1>Hello,Welcome to Use KidFramework!</h1>
<h2>Author:田超凡</h2>
<h3>测试后台URL:/toPage.do</h3>

</body>
</html>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Example页面</title>

<script type="text/javascript" src="static/js/jquery-1.4.4.min.js"></script>
</head>

<body>

<h1 id="showResponseName">返回响应数据=><span id="showData"></span></h1>

<script type="text/javascript">
$(document).ready(function(){
    
    $.ajax({
        url:"/kid-framework-web/show.do",
        type:"GET",
        data:{
            "message":"***前台传递消息***"
        },
        dataType:"json",
        success:function(data){
            
            $("#showData").html(data.name);
            
        }
    });
    
});
</script>

</body>

</html>

运行结果图:

输入toPage.do

后台日志:

发布了100 篇原创文章 · 获赞 10 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_30056341/article/details/100035961