1. 程式人生 > >跟我一起造輪子 手寫springmvc

跟我一起造輪子 手寫springmvc

targe leg unit nco 容器 ner 並且 dir 有關

  作為java程序員,項目中使用到的主流框架多多少少和spring有關聯,在面試的過程難免會問一些spring springmvc spring boot的東西,比如設計模式的使用、 怎麽實現springioc 怎麽實現springmvc諸如此類的問題,今天我們就來探尋spring mvc的實現,然後自己實現一個簡單的spring mvc

一. 了解spring mvc的基本運行流程

  ps: 網上一大堆關於springmvc的詳細講解,在這裏就不累贅了

  技術分享圖片

  小結:spring mvc的核心是DispatcherServlet,DispatcherServlet繼承於HttpServlet,可以說spring mvc是基於Servlet的一個實現,DispatcherServlet負責協調和組織不同組件以完成請求處理並返回響應的工作,實現了MVC模式。

二. 梳理簡單SpringMVC的設計思路

  1. 初始化容器

       1.1 讀取配置文件

          1.1.1.加載配置文件信息到DispatcherServlet

       1.2 根據配置掃描包、初始化容器和組件

          1.2.1.根據配置信息遞歸掃描包

          1.2.2.把包下的類實例化 並且掃描註解

          1.2.3.根據類的方法和註解,初始化HandlerMapping

  2. 處理業務請求

      2.1 處理請求業務

        2.2.1 首先拿到請求URI

          2.2.2 根據URI,在HandlerMapping中查找和URI對應的Handler

        2.2.3 根據Handler裏面的method中的參數名稱和http中的請求參數匹配,填充method參數,反射調用

三. 沒時間解釋了,快上車

    ps :環境基於maven idea tomat(端口8080) servlet

  1.搭建一個基本web項目,並導入idea配置servlet 和javassist pom依賴 如下

    創建命令: mvn archetype:generate -DgroupId=com.adminkk -DartifactId=adminkk-mvc -DpackageName=com.adminkk -Dversion=1.0

技術分享圖片

pom依賴

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.adminkk</groupId>
  <artifactId>adminkk-mvc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <packaging>war</packaging>

  <name>adminkk-mvc</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>

    <!--servlet-->
      <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
        <scope>provided</scope>
      </dependency>

    <!-- asm -->
    <dependency>
      <groupId>asm</groupId>
      <artifactId>asm</artifactId>
      <version>3.3.1</version>
    </dependency>

    <!-- javassist -->
    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.23.1-GA</version>
    </dependency>


  </dependencies>
</project>

    

2.創建mvc的註解 Controller RequestMapping 和統一異常處理類、方法參數工具類ParameterNameUtils  

package com.adminkk.annotation;

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

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {

    public String value() default "";

    public String description() default "";
}

package com.adminkk.annotation;

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

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {

    public String value() default "";

    public String method() default "";

    public String description() default "";
}
package com.adminkk.exception;

public  final  class MvcException extends RuntimeException{

    public MvcException() {
        super();
    }

    public MvcException(String message) {
        super(message);
    }


}
package com.adminkk.tools;

import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

import java.lang.reflect.Method;


public final class ParameterNameUtils {


    public final static String[] getParameterNamesByJavassist(final Class<?> clazz, final Method method) {

        ClassPool pool = ClassPool.getDefault();
        try {
            CtClass ctClass = pool.get(clazz.getName());
            CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName());

            // 使用javassist的反射方法的參數名
            MethodInfo methodInfo = ctMethod.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
                    .getAttribute(LocalVariableAttribute.tag);
            if (attr != null) {

                String[] rtv = new String[ctMethod.getParameterTypes().length];
                int len = ctMethod.getParameterTypes().length;
                // 非靜態的成員函數的第一個參數是this
                int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
                for (int i = 0; i < len; i++) {
                    rtv[i] = attr.variableName(i + pos);
                }
                return rtv;
            }
        } catch (NotFoundException e) {
            System.out.println("獲取異常"+ e.getMessage());
        }
        return new String[0];
    }



}

3.創建 HandlerMapping類 主要是兩個方法 doInit初始化 doService處理請求 相關代碼如下

package com.adminkk.handler;

import com.adminkk.scan.FileScaner;
import com.adminkk.scan.Scaner;
import com.adminkk.scan.XmlScaner;
import com.adminkk.tools.ParameterNameUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final  class HandlerMapping {


    private static  final Map<String,Handler> handlerMapping = new HashMap<String, Handler>();

    private static  final List<Scaner> scaners = new ArrayList<>(2);
    static {
        scaners.add(new XmlScaner());
        scaners.add(new FileScaner());
    }

    public  static void scanPackage(String scanUrl) throws RuntimeException, IllegalAccessException, InstantiationException, ClassNotFoundException {

        for (Scaner scaner : scaners) {
            scaner.doScane(scanUrl);
        }

    }




    public static void doInit(String scanUrl) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
        scanPackage(scanUrl);
    }



    public static void doService(HttpServletRequest request, HttpServletResponse response) {

        String requestURI = request.getRequestURI();
        System.out.println("請求地址是="+ requestURI);
        Handler handler = handlerMapping.get(requestURI);
        if(handler == null){
            System.out.println("請求地址是="+ requestURI+" 沒有配置改路徑");
            return;
        }
        Method method = handler.getMethod();
        Object instance = handler.getInstance();
        response.setCharacterEncoding("UTF-8");
        //response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;

        try {

            //這裏是簡單的解析 可以像springmvc那樣解析處理
            Map<String, String[]> parameterMap = request.getParameterMap();
            String[] parameters = ParameterNameUtils.getParameterNamesByJavassist(instance.getClass(),method);
            Object[]  parameter = new Object[parameters.length];
            if(parameters != null && parameters.length > 0){
                for (int i = 0; i < parameters.length; i++) {
                    final String simpleName = parameters[i];
                    StringBuilder parameterSb = new  StringBuilder();
                    final String[] parameterStr = parameterMap.get(simpleName);
                    if(parameterStr != null){
                        for (int j = 0; j < parameterStr.length; j++) {
                            parameterSb.append(parameterStr[j]);
                        }
                    }
                    parameter[i] = parameterSb.toString();
                }
            }

            writer = response.getWriter();
            String result = (String) method.invoke(instance,parameter);
            writer.print(result);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("請求地址是="+ requestURI+" 執行異常");
            writer.print("業務執行異常");
        }finally {
            writer.flush();
            writer.close();
        }
    }

    public static Handler addHandlerMapping(String url,Handler handler) {
        return handlerMapping.put(url,handler);
    }

    public static Handler getHandlerMapping(String url) {
        return handlerMapping.get(url);
    }



}

掃描包

package com.adminkk.scan;

public interface Scaner {

    void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException;
}

package com.adminkk.scan;

import com.adminkk.exception.MvcException;
import com.adminkk.factory.BeanPostProcessor;
import com.adminkk.factory.MvcBeanPostProcessor;
import com.adminkk.factory.ServiceBeanPostProcessor;
import com.adminkk.handler.HandlerMapping;
import javassist.ClassClassPath;
import javassist.ClassPool;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public final class FileScaner implements Scaner{

public FileScaner() {
}

public static final List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
static {
beanPostProcessorList.add(new MvcBeanPostProcessor());
beanPostProcessorList.add(new ServiceBeanPostProcessor());
}

@Override
public void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
if(scanUrl == null || scanUrl.length() == 0){
throw new MvcException("容器基礎掃描路徑為空,請檢查參數配置");
}
String baseUrl = HandlerMapping.class.getResource("/").getPath();
String codeUrl = scanUrl.replaceAll("\\.", "/");
String path = baseUrl + codeUrl;
File file = new File(path);
if(file == null || !file.exists()){
throw new MvcException("找不到對應掃描路徑,請檢查參數配置");
}
recursionRedFile(scanUrl,file);
}

//遞歸讀取文件
private void recursionRedFile(String scanUrl,File file) throws MvcException, ClassNotFoundException, IllegalAccessException, InstantiationException {

if(!file.exists()){
return;
}

//讀取java文件
if(file.isFile()){

String beanName = scanUrl.replaceAll(".class","");
Class<?> forName = Class.forName(beanName);
//放到Javassist容器裏面
ClassPool pool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(forName);
pool.insertClassPath(classPath);
if(forName.isAnnotation() || forName.isEnum() || forName.isInterface() ){
return;
}
Object newInstance = forName.newInstance();

//前置執行
for (int i = 0; i < beanPostProcessorList.size() ; i++) {
BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);
beanPostProcessor.postProcessBeforeInitialization(newInstance,beanName);
}

//後置執行
for (int i = beanPostProcessorList.size()-1; i > 0 ; i++) {
BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);
beanPostProcessor.postProcessAfterInitialization(newInstance,beanName);
}
return;
}

//文件夾下面的文件都遞歸處理
if(file.isDirectory()){
File[] files = file.listFiles();
if(files != null && files.length >0){
for (int i = 0; i < files.length; i++) {
File targetFile = files[i];
recursionRedFile(scanUrl+"."+targetFile.getName(),targetFile);
}
}
}

}
}
 
package com.adminkk.scan;

public final class XmlScaner implements Scaner{

    public XmlScaner() {
    }

    @Override
    public void doScane(String scanUrl) {
        //可自行擴展
    }


}

  掃描bean

package com.adminkk.factory;

import com.adminkk.exception.MvcException;

public interface BeanPostProcessor {

    Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException;

    Object postProcessAfterInitialization(Object object, String beanName) throws MvcException;
}

package com.adminkk.factory;

import com.adminkk.annotation.Controller;
import com.adminkk.annotation.RequestMapping;
import com.adminkk.exception.MvcException;
import com.adminkk.handler.Handler;
import com.adminkk.handler.HandlerMapping;

import java.lang.reflect.Method;

public class MvcBeanPostProcessor implements BeanPostProcessor{


    //掃描Controller業務
    @Override
    public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException {

        Class<?> objectClass = object.getClass();
        if(objectClass.getAnnotation(Controller.class) != null){
            RequestMapping calssRequestMappingAnnotation = objectClass.getAnnotation(RequestMapping.class);
            StringBuilder urlSb = new StringBuilder();
            if(calssRequestMappingAnnotation != null){
                urlSb.append(calssRequestMappingAnnotation.value());
            }
            Method[] methods = objectClass.getMethods();
            if(methods != null && methods.length > 0 ){
                for (int i = 0; i < methods.length; i++) {
                    Method method = methods[i];
                    RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
                    if(methodAnnotation != null){
                        String methodValue = methodAnnotation.value();
                        String url = new StringBuilder().append(urlSb).append(methodValue).toString();
                        Handler handler = HandlerMapping.getHandlerMapping(url);
                        if(handler == null){
                            handler = new Handler();
                            handler.setMethod(method);
                            handler.setInstance(object);
                            HandlerMapping.addHandlerMapping(url,handler);
                        }else {
                            throw new MvcException("請求路徑"+ url + "已經存在容器中");
                        }
                    }
                }

            }
        }

        return object;
    }

    @Override
    public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException {
        return null;
    }
}
package com.adminkk.factory;

import com.adminkk.exception.MvcException;

public class ServiceBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException {
        //可自行擴展
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException {
        //可自行擴展
        return null;
    }
}

  

5.創建 DispatcherServlet

package com.adminkk.servlet;

import com.adminkk.handler.HandlerMapping;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "DispatcherServlet",loadOnStartup=1,urlPatterns={"/"})
public final  class DispatcherServlet extends HttpServlet {



    public static final String BASE_SCAN_URL = "com.adminkk";

    //初始化容器
    @Override
    public void init() throws ServletException {
        doInit();
    }

    //處理業務請求
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doService(req,resp);
    }

    private void doService(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
        try {
            HandlerMapping.doService(req,resp);
        }catch (Exception e){
            e.printStackTrace();
            throw new ServletException(e.getMessage());
        }
    }

    private void doInit() throws ServletException {
        try {     
            HandlerMapping.doInit(this.BASE_SCAN_URL);
        }catch (Exception e){
            e.printStackTrace();
            throw new ServletException(e.getMessage());
        }

    }
}

  

  好了,目前為止我們就寫好了簡版的springmvc 下面開始測試

package com.adminkk.controller;

import com.adminkk.annotation.Controller;
import com.adminkk.annotation.RequestMapping;


@Controller
@RequestMapping("/mvc")
public class MvcController {


    @RequestMapping("/index")
    public String index(){
        return  "adminkk-mvc system is running";
    }


    @RequestMapping("/arg")
    public String parameter(String argOne, String argTwo){
        return  "argOne = " + argOne + " argTwo = " + argTwo;
    }
}

  

      訪問地址 http://localhost:8080/mvc/index

      技術分享圖片

      訪問地址: http://localhost:8080/mvc/arg?argOne=argOne&argTwo=argTwo

      技術分享圖片

總結:整體實現簡單的springmvc,設計上還可以擴展更多,難點在於method 獲取方法上的參數名稱,由於jdk1.8以前是不支持的,需要借用第三方工具 比如 asm javassist黑科技工具包來幫助實現,spring-core使用的是LocalVariableTableParameterNameDiscoverer底層是調用asm,我們這裏使用的是javassist。延用這套思路還可以和spring項目結合,寫一個 基於spring的springmvc項目

源代碼 : https://gitee.com/chenchenche/mvc

寫博客不容易,希望大家多多提建議

下一篇預告 跟我一起造輪子 手寫分布式im系統(上)

跟我一起造輪子 手寫springmvc