【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框架介紹

相關文章
相關標籤/搜索