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

 

後臺日誌:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章