第一篇 - 手寫SpringMvc框架

在這裏插入圖片描述
Github源碼下載地址:https://github.com/chenxingxing6/springmvc
CSDN源碼下載地址:https://download.csdn.net/download/m0_37499059/11783232

在這裏插入圖片描述


一、前言

SpringMVC是Spring框架的一個模塊,是基於mvc的webframework模塊。mvc是一種設計模式,即model-view-controller,mvc在b/s系統下的應用如下圖所示。
在這裏插入圖片描述

SpringMvc原理圖:

在這裏插入圖片描述


二、手寫SpringMvc

代碼下載Github:https://github.com/chenxingxing6/springmvc

我們所有的註解都自己定義,並對註解進行解析處理。通過寫這個SpringMvc框架,我們可以大致掌握SpringMvc的實現思路,用戶請求怎麼進來的,怎麼通過Mapping映射到具體需要執行的方法上,並如何對結果進行處理(Json數據格式,視圖)。

需要依賴的包:

 <dependency>
     <groupId>javax.servlet</groupId>
     <artifactId>javax.servlet-api</artifactId>
     <version>3.1.0</version>
     <optional>true</optional>
     <scope>provided</scope>  #應用服務器,比如tomcat都有這個jar包
</dependency>

在這裏插入圖片描述


GitHub裏面對進行了更新,完善了更多的功能,具體看Github.
在這裏插入圖片描述

2.1項目結構

在這裏插入圖片描述


2.2登陸測試Demo

訪問地址:http://localhost:8080/test/view?path=login

在這裏插入圖片描述
在這裏插入圖片描述


@RequestMapping("/login")
    public MyModeAndView login(@RequestParam("name") String name, @RequestParam("pwd") String pwd){
        MyModeAndView modeAndView = new MyModeAndView();
        modeAndView.setViewName("index");
        modeAndView.addObject("name", name);
        return modeAndView;
    }

<html>
<head>
    <title>登陸</title>
</head>
<body>
<div>
    <h1>歡迎登陸</h1>
    <form action="/test/login" method="get">
   <div>
       <label>用戶名:</label>
       <input name="name" type="text"/>
   </div>
    <div>
        <label>用戶名:</label>
        <input name="pwd" type="password"/>
    </div>
    <div>
        <label></label>
        <input type="submit" value="提交"/>
    </div>
    </form>
</div>
</body>
</html>


<html>
<head>
    <title>Title</title>
</head>
<body>
<div>
    <h1>手寫SpringMvc</h1>
    <h3 style="color: blue;">歡迎 ${name} 你使用本系統....</h3>
</div>
</body>
</html>

2.3核心代碼

package org.springframework.servlet;

import com.alibaba.fastjson.JSON;
import org.springframework.annotation.*;
import org.springframework.core.*;

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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * @Author: cxx
 * @Date: 2019/8/27 22:45
 */
public class MyDispatcherServlet extends HttpServlet{
    // 存放配置信息
    Properties props = null;

    // 所有的類名
    private List<String> classNames = null;

    // 實例化對象
    Map<String, Object> ioc = null;

    // Handler
    List<Handler> handlers;

    // 默認適配器
    IHandlerAdapter defaultHandlerAdapter = null;

    public MyDispatcherServlet(){
        props = new Properties();
        classNames = new ArrayList<>();
        ioc = new ConcurrentHashMap<>();
        handlers = new ArrayList<>();
        defaultHandlerAdapter = new DefaultHandlerAdapter();
    }

    // 初始化
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("------ My mvc is init start...... ------");
        // 1.加載配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        System.out.println("------ 1.加載配置文件成功-doLoadConfig() ------");

        // 2.根據配置文件掃描所有相關類
        doScanner(props.getProperty("scanPackage"));
        System.out.println("------ 2.掃描所有相關類-ddoScanner() ------");

        // 3.初始化所有相關類的實例,並將放入IOC容器中
        doInstance();
        System.out.println("------ 3.實例化成功-doInstance() ------");

        // 4.實現DI
        doAutowried();
        System.out.println("------ 4.依賴注入成功-doAutowried() ------");

        // 5.初始化HandlerMapping
        initHandlerMapping();
        System.out.println("------ 5.HandlerMapping初始化成功-initHandlerMapping() ------");

        System.out.println("------ My mvc is init end...... ------");
    }

    public void doLoadConfig(String location){
        String configName = location.split(":")[1];
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(configName);
        try {
            props.load(is);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (is != null){
                try {
                    is.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

    public void doScanner(String packageName){
        // 進行遞歸掃描
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replace(".", "/"));
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()) {
            if (file.isDirectory()){
                doScanner(packageName + "." + file.getName());
            }else {
                String className = packageName + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    /**
     * IOC容器規則 key-value
     * 1.key默認用類名小寫字段,否則優先使用用戶自定義名字
     * 2.如果是接口,用接口的類型作爲key
     */
    public void doInstance(){
        if (classNames.isEmpty()){
            return;
        }
        // 利用反射,將掃描的className進行初始化
        try {
            for (String className : classNames) {
                Class clazz = Class.forName(className);
                // 進行Bean實例化,初始化IOC
                if (clazz.isAnnotationPresent(Controller.class)){
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, clazz.newInstance());
                }else if (clazz.isAnnotationPresent(Service.class)){
                    Service service = (Service) clazz.getAnnotation(Service.class);
                    String beanName = service.value();
                    if ("".equals(beanName.trim())){
                        beanName = lowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);

                    // 接口也需要注入,接口類型作爲key
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces) {
                        ioc.put(i.getName(), instance);
                    }
                }else {
                    continue;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void doAutowried(){
        if (ioc.isEmpty()){
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 獲取到所有字段,不管什麼類型,都強制注入
            Field[] field = entry.getValue().getClass().getDeclaredFields();
            for (Field f : field) {
                if (f.isAnnotationPresent(Autowired.class)){
                    Autowired autowired = f.getAnnotation(Autowired.class);
                    String beanName = autowired.value().trim();
                    if ("".equals(beanName)){
                        // com.demo.service.ITestService
                        beanName = f.getType().getName();
                    }
                    // 不管願不願意,都需要強吻
                    f.setAccessible(true);
                    try {
                        // 例如:TestController -> TestService
                        f.set(entry.getValue(), ioc.get(beanName));
                    }catch (Exception e) {
                        e.printStackTrace();
                        continue;
                    }
                }
            }
        }
    }


    public void initHandlerMapping(){
        if (ioc.isEmpty()){
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(Controller.class)){
                continue;
            }
            String baseUrl = "";
            if (clazz.isAnnotationPresent(RequestMapping.class)){
                RequestMapping requestMapping = (RequestMapping)clazz.getAnnotation(RequestMapping.class);
                baseUrl = requestMapping.value();
            }

            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(RequestMapping.class)){
                    continue;
                }
                RequestMapping requestMapping = (RequestMapping)method.getAnnotation(RequestMapping.class);
                String regex = ("/" + baseUrl + requestMapping.value()).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                handlers.add(new Handler(pattern, entry.getValue(), method));
                System.out.println("------   Mapping: " + regex + ", method:" + method);
            }
        }
    }

    // 6.運行階段,等待請求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
            String url = req.getRequestURI();
            String contextpath= req.getContextPath();
            url = url.replace(contextpath, "").replaceAll("/+", "/");
            System.out.println("進行請求....url:" + url);
        }catch (FileNotFoundException e){
            resp.getWriter().write("404 Not Found");
            return;
        }catch (Exception e){
            resp.getWriter().write("500 error \r\n\n" + Arrays.toString(e.getStackTrace()));
            return;
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        boolean jsonResult = false;
        // 獲取適配器
        IHandlerAdapter handlerAdapter = defaultHandlerAdapter;
        Handler handler = handlerAdapter.getHandler(req, handlers);
        if (handler == null){
            throw new FileNotFoundException();
        }
        Object[] paramValues = handlerAdapter.hand(req, resp, handlers);
        Method method = handler.method;
        Object controller = handler.controller;
        String beanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());

        //如果controller或這個方法有UVResponseBody修飾,返回json
        if (controller.getClass().isAnnotationPresent(ResponseBody.class) || method.isAnnotationPresent(ResponseBody.class)){
            jsonResult = true;
        }
        Object object = method.invoke(ioc.get(beanName), paramValues);
        if (jsonResult && object !=null){
            resp.getWriter().write(JSON.toJSONString(object));
        }else {
            // 返回視圖
            doResolveView(object, req, resp);
        }
    }

    public void doResolveView(Object object, HttpServletRequest req, HttpServletResponse resp) throws Exception{
        // 視圖前綴
        String prefix = props.getProperty("view.prefix");
        // 視圖後綴
        String suffix = props.getProperty("view.suffix");

        MyModeAndView modeAndView = null;
        if (object instanceof MyModeAndView){
            modeAndView = (MyModeAndView) object;
        }else {
            modeAndView = new MyModeAndView(object.toString());
        }
        DefaultViewResolver viewResolver = new DefaultViewResolver(prefix, suffix);
        viewResolver.resolve(modeAndView, req, resp);
    }

    /**
     * 首字母小寫
     * @param old
     * @return
     */
    private static String lowerFirstCase(String old){
        char [] chars = old.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}

三、總結

服務啓動會去讀取web.xml配置文件,然後執行MyDispatcherServlet的init()方法,初始化一些配置,IOC,依賴注入,RequestMapping等,然後對發送進來的請求進行解析,通過反射方式調用到具體Controller的某個方法,然後對執行的結果進行處理。

 <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <display-name>DispatcherServlet</display-name>
        <servlet-class>org.springframework.servlet.MyDispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:application.properties</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

添加過濾器對編碼進行設置:

<!-- 過濾器 -->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

package org.springframework.web.filter;

import javax.servlet.*;
import java.io.IOException;

/**
 * @Author: cxx
 * @Date: 2019/9/15 23:56
 */
public class CharacterEncodingFilter implements Filter{
    // 編碼格式
    private String encoding;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("------ filter init .......");
        encoding = filterConfig.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("------ 過濾器處理編碼問題......" + encoding);
        servletRequest.setCharacterEncoding(encoding);
        servletResponse.setCharacterEncoding(encoding);
        servletResponse.setContentType("text/html");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

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