從零到一實現SpringMVC

目標:

  實現SpringMVC的基本功能。主要包括:

  • ChenController 控制類實現
  • ChenService 服務類實現
  • ChenQualifier 自動裝配功能的實現
  • ChenRequestMapping 地址映射功能實現
  • ChenRequestParam 請求參數映射功能實現

實現:

思路:

  自定義ChenDispatcherServlet,完成請求分發的功能。大致步驟如下:

  • 首先要根據一個基本包進行掃描,掃描裏面的子包以及子包下的類,將全類名添加到List集和中。
  • 其次要把掃描出來的類進行實例化。以全類名爲keyclass實例對象爲value,放入到hashMap容器中。
  • 依賴注入,把service層的實例注入到controller,即初始化controller層的成員變量。
  • 建立pathmethod的映射關係,保存在hashMap容器中。

一、前期準備

  實現自定義註解,並用自定義註解模擬正常的業務邏輯,實現將用戶發送給服務器的數據回寫給用戶的功能。

1、加入依賴

  本項目SpringMVC是對servlet的封裝,所以需要引入servletjar包。源碼如下:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<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.njust.springmvcchen</groupId>
    <artifactId>springmvcchen</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>springmvcchen Maven Webapp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>3.0-alpha-1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>springmvcchen</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <source>8</source>
                        <target>8</target>
                        <!--反射獲取方法參數名稱-->
                        <compilerArgument>-parameters</compilerArgument>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

2、自定義控制層註解

  定義ChenController 類,使得該註解的作用範圍在接口或類上 。同時保證註解會在class字節碼文件中存在,在運行時可以通過反射獲取到。設置value屬性值,默認爲空。源碼如下:

ChenController .java

package com.njust.springmvc.annotation;

import java.lang.annotation.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:00
 * @description:
 */
@Target({ElementType.TYPE}) //作用範圍:用在接口或類上
@Retention(RetentionPolicy.RUNTIME) // 註解會在class字節碼文件中存在,在運行時可以通過反射獲取到
@Documented//明該註解將被包含在javadoc中
public @interface ChenController {
    String value() default "";
}


3、自定義服務層註解

  定義ChenService 類,和控制層功能基本一致,定義該註解作用範圍在接口或類上 。同時保證 註解會在class字節碼文件中存在,在運行時可以通過反射獲取到,設置value屬性值,默認爲空。源碼如下:

ChenService .java

package com.njust.springmvc.annotation;

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

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:06
 * @description:
 */
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenService {
    String value() default "";
}


4、自定義依賴注入註解

  定義ChenQualifier類,和控制層功能基本一致,定義該註解作用範圍在字段、枚舉的常量上 。同時保證 註解會在class字節碼文件中存在,在運行時可以通過反射獲取到,設置value屬性值,默認爲空。源碼如下:

ChenQualifier .java

package com.njust.springmvc.annotation;

import java.lang.annotation.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:04
 * @description:
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenQualifier {
    String value() default "";

}

5、自定義地址映射註解

  定義ChenRequestMapping 類,和控制層功能基本一致,定義該註解作用範圍在字段、枚舉的常量以及方法上 。同時保證 註解會在class字節碼文件中存在,在運行時可以通過反射獲取到,設置value屬性值,默認爲空。源碼如下:

ChenRequestMapping .java

package com.njust.springmvc.annotation;

import java.lang.annotation.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:04
 * @description:
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenRequestMapping {
    //路徑ChenRequestMapping(value)
    String value() default "";
}


6、自定義請求參數映射註解

  定義ChenRequestParam 類,和控制層功能基本一致,定義該註解作用範圍在方法參數上 。同時保證註解會在class字節碼文件中存在,在運行時可以通過反射獲取到,設置value屬性值,默認爲空。源碼如下:

ChenRequestParam .java

package com.njust.springmvc.annotation;

import java.lang.annotation.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:04
 * @description:
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenRequestParam {
    String value() default "";
}


7、業務服務接口

  定義BaseServiceUse 類,模擬服務的基本操作,此處是查詢、新增、更新的功能。源碼如下:

BaseServiceUse .java

package com.njust.springmvc.service;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:08
 * @description:
 */
public interface BaseServiceUse {

    String query(String name, String age);

    String insert(String param);

    String update(String param);
}


8、業務服務實現

  定義BaseServiceUseImpl類,該類十分簡單,實現BaseServiceUse 接口裏面的方法,直接返回字符串數據,模擬從數據庫取得的信息。使用自定義註解ChenService,將該類注入到容器中。源碼如下:

BaseServiceUseImpl.java

package com.njust.springmvc.service.impl;

import com.njust.springmvc.annotation.ChenService;
import com.njust.springmvc.service.BaseServiceUse;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:08
 * @description:
 */
@ChenService(value = "baseServiceUseImpl")
public class BaseServiceUseImpl implements BaseServiceUse {
    @Override
    public String query(String name, String age) {
        return "{name=" + name + ",age=" + age + "}";
    }

    @Override
    public String insert(String param) {
        return "insert successful.....";
    }

    @Override
    public String update(String param) {
        return "update successful.....";
    }
}


9、業務控制層

  定義BaseControllerUse 類,通過自定義註解ChenQualifierBaseServiceUse自動裝配到ChenController實例中。使用自定義註解ChenController,將該類注入到容器中。使用自定義註解ChenRequestMapping,將請求映射到不同的方法實現。其中query()函數,直接將用戶發送過來的數據回寫給用戶,功能十分簡單。源碼如下:

BaseControllerUse .java

package com.njust.springmvc.controller;

import com.njust.springmvc.annotation.ChenController;
import com.njust.springmvc.annotation.ChenQualifier;
import com.njust.springmvc.annotation.ChenRequestMapping;
import com.njust.springmvc.annotation.ChenRequestParam;
import com.njust.springmvc.service.BaseServiceUse;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:10
 * @description:
 */
@ChenController(value = "baseControllerUse")
//@ChenRequestMapping(value = "/chen")
public class BaseControllerUse {
    @ChenQualifier(value = "baseServiceUseImpl")
    private BaseServiceUse baseServiceUse;

    @ChenRequestMapping(value = "/query")
    public void query(HttpServletRequest request, HttpServletResponse response,
                      @ChenRequestParam(value = "name") String name,
                      @ChenRequestParam(value = "age") String age) {

        try {
            PrintWriter writer = response.getWriter();
            String result = baseServiceUse.query(name, age);
            writer.write(result);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @ChenRequestMapping("/insert")
    public void insert(HttpServletRequest request,
                       HttpServletResponse response) {
        try {
            PrintWriter pw = response.getWriter();
            String result = baseServiceUse.insert("0000");

            pw.write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @ChenRequestMapping("/update")
    public void update(HttpServletRequest request,
                       HttpServletResponse response, String param) {
        try {
            PrintWriter pw = response.getWriter();
            String result = baseServiceUse.update(param);

            pw.write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}


10、通用工具類

  定義CommonUtil 類,實現子類獲取等功能。源碼如下:

CommonUtil .java

package com.njust.springmvc.utils;


/**
 * @author Chen
 * @version 1.0
 * @date 2020/4/1 14:59
 * @description:
 */
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
public class CommonUtil {

    //獲取某個類的實現類
    public static List<Class<?>> getAllAssignedClass(Class<?> cls) throws Exception {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        for (Class<?> c : getClasses(cls)) {
            if (cls.isAssignableFrom(c) && !cls.equals(c)) {
                classes.add(c);
            }
        }
        return classes;
    }

    public static List<Class<?>> getClasses(Class<?> cls) throws Exception {
        String pk = cls.getPackage().getName();
        String path = pk.replace('.', '/');
        ClassLoader classloader = Thread.currentThread().getContextClassLoader();
        URL url = classloader.getResource(path);
        return getClasses(new File(url.getFile()), pk);
    }

    //根據路徑獲取
    public static List<Class<?>> getClasses(File dir, String pk) throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        if (!dir.exists()) {
            return classes;
        }
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                classes.addAll(getClasses(f, pk + "." + f.getName()));
            }
            String name = f.getName();
            if (name.endsWith(".class")) {
                classes.add(Class.forName(pk + "." + name.substring(0, name.length() - 6)));
            }
        }
        return classes;
    }

    //動態獲取,根據反射,比如獲取xx.xx.xx.xx.Action 這個所有的實現類。 xx.xx.xx.xx 表示包名  Action爲接口名或者類名
    public static List<Class<?>> getAllActionSubClass(String classPackageAndName) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Field field = null;
        Vector v = null;
        Class<?> cls = null;
        List<Class<?>> allSubclass = new ArrayList<Class<?>>();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?> classOfClassLoader = classLoader.getClass();
        cls = Class.forName(classPackageAndName);
        while (classOfClassLoader != ClassLoader.class) {
            classOfClassLoader = classOfClassLoader.getSuperclass();
        }
        field = classOfClassLoader.getDeclaredField("classes");
        field.setAccessible(true);
        v = (Vector) field.get(classLoader);
        for (int i = 0; i < v.size(); ++i) {
            Class<?> c = (Class<?>) v.get(i);
            if (cls.isAssignableFrom(c) && !cls.equals(c)) {
                allSubclass.add((Class<?>) c);
            }
        }
        return allSubclass;
    }
}


}


二、請求分發

  攔截用戶請求並分發給響應的控制器進行處理。

1、自定義DispatcherServlet

  自定義DispatcherServlet類,實現根據一個基本包進行掃描,掃描裏面的子包以及子包下的類,將全類名添加到List集和中的功能。其次要把掃描出來的類進行實例化。以全類名爲keyclass實例對象爲value,放入到hashMap容器中。然後實行依賴注入,把service層的實例注入到controller,即初始化controller層的成員變量。最後建立pathmethod的映射關係,保存在hashMap容器中。由於我們的SpringMVC是基於servlet實現的,所以ChenDispatcherServlet要繼承HttpServlet。源碼如下:

ChenDispatcherServlet.java

package com.njust.springmvc.servlet;

import com.njust.springmvc.annotation.ChenController;
import com.njust.springmvc.annotation.ChenQualifier;
import com.njust.springmvc.annotation.ChenRequestMapping;
import com.njust.springmvc.annotation.ChenService;
import com.njust.springmvc.controller.BaseControllerUse;
import com.njust.springmvc.handlerAdapter.HandlerAdapterService;
import com.sun.xml.internal.ws.util.StringUtils;

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.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:16
 * @description:
 */
public class ChenDispatcherServlet extends HttpServlet {



    private static final long serialVersionUID = 1L;

    List<String> classNames = new ArrayList<String>();

    Map<String, Object> beans = new HashMap<String, Object>();

    Map<String, Object> handlerMap = new HashMap<String, Object>();
    //    將具體的方法和controller關聯
    Map<String, Object> handlerControllerMap = new HashMap<String, Object>();

    Properties prop = null;

    private static String HANDLERADAPTER = "jamesHandlerAdapter";


    public void init(ServletConfig config) {
        // 1、我們要根據一個基本包進行掃描,掃描裏面的子包以及子包下的類
        scanPackage("com.njust");

        System.out.println("scanPackage(\"com.njust\");");
        for (String className : classNames) {
            System.out.println(className);
        }
        // 2、我們肯定是要把掃描出來的類進行實例化
        instance();

        // 3、依賴注入,把service層的實例注入到controller
        ioc();

        // 4、建立一個path與method的映射關係
        HandlerMapping();
        System.out.println("HandlerMapping();");
        for (Map.Entry<String, Object> entry : handlerMap.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        /*
         * InputStream is = this.getClass()
         * .getResourceAsStream("/config/properties/spring.properties"); prop =
         * new Properties(); try { prop.load(is); } catch (IOException e) {
         * e.printStackTrace(); }
         */
    }

    private void scanPackage(String basePackage) {
        //掃描編譯好的類路徑下所有的類
        URL url = this.getClass().getClassLoader().getResource("/" + replaceTo(basePackage));

        String fileStr = url.getFile();

        File file = new File(fileStr);
        //拿到所有類com.njust 下的 com.njust.springmvc 文件夾
//        list()方法是返回某個目錄下的所有文件和目錄的文件名,返回的是String數組
        String[] filesStr = file.list();

        for (String path : filesStr) {
            File filePath = new File(fileStr + path);//掃描com.njust.springmvc下的所有class類

            //遞歸調用掃描,如果是路徑,繼續掃描
            if (filePath.isDirectory()) {
                // com.njust.com.njust.springmvc
                scanPackage(basePackage + "." + path);
            } else {
                classNames.add(basePackage + "." + filePath.getName());//如果是class文件則加入List集合(待生成bean)
            }
        }
    }

    private String replaceTo(String basePackage) {
//        英文點號(.)表示任意字符 所以需要轉義
        return basePackage.replaceAll("\\.", "/");
    }


    public ChenDispatcherServlet() {
    }

    private void instance() {
        if (classNames.size() <= 0) {
            System.out.println("包掃描失敗!");
            return;
        }
        //遍歷掃描到的class文件,將需要實例化的類(加了註解的類)進行反射創建對象(像註解就不需要實例化)
        for (String className : classNames) {
            // com.njust.com.njust.springmvc.service.impl.BaseServiceUseImpl.class
            String cn = className.replace(".class", "");

            try {
                Class<?> clazz = Class.forName(cn);//拿到class類,用來實例化
                // 將掃描到的類,獲取類名,並判斷是否標記了ChenController註解
//                註釋ChenController是否在此clazz上。如果在則返回true;不在則返回false。
                if (clazz.isAnnotationPresent(ChenController.class)) {
                    //調用構造方法
                    Object instance = clazz.newInstance();
                    //獲取對應的請求路徑"/chen"
//                    ChenRequestMapping requestMapping = clazz
//                            .getAnnotation(ChenRequestMapping.class);
//                    String rmvalue = requestMapping.value();//得到"/chen"請求路徑
                    //用路徑做爲key,對應value爲實例化對象
//                    beans.put(rmvalue, instance);

//                    注意:此處改進版本是使用全限定名
                    ChenController chenController = clazz.getAnnotation(ChenController.class);

                    if (chenController.value() == null || chenController.value().equals("")) {
                        beans.put(instance.getClass().getName(), instance);
                    } else {
                        beans.put(chenController.value(), instance);
                    }


                } else if (clazz.isAnnotationPresent(ChenService.class)) {
                    //獲取當前clazz類的註解(通過這個註解可得到當前service的id)  @com.njust.springmvc.annotation.ChenService(value=baseServiceUseImpl)
                    ChenService service = clazz.getAnnotation(ChenService.class);
                    Object instance = clazz.newInstance();
                    //put(baseServiceUseImpl,instance)

//                    默認使用全限定名
                    if (service.value() == null || service.value().equals("")) {
                        beans.put(instance.getClass().getName(), instance);
                    } else {
                        beans.put(service.value(), instance);
                    }


                } else {
                    continue;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
     * response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
    }

    private void errorDeal(HttpServletResponse response, String message) {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            writer.write(new String(message.getBytes("UTF-8"), StandardCharsets.UTF_8));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
     * response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //獲取到請求路徑   /springmvcchen/chen/query
        String uri = request.getRequestURI();

        String context = request.getContextPath();
        //將  "/springmvcchen/chen/query"  去掉"/springmvcchen"
        String path = uri.replace(context, "");
        //根據請求路徑來獲取要執行的方法 
        Method method = (Method) handlerMap.get(path);

        if (method == null) {
            errorDeal(response, "請檢查路徑是否設置正確!");
            return;
        }

        //拿到控制類
//        BaseControllerUse instance = (BaseControllerUse) beans.get("/" + path.split("/")[1]);
//        BaseControllerUse instance = (BaseControllerUse) beans.get(path);
        Object instance = handlerControllerMap.get(path);

        if (instance == null) {
            errorDeal(response, "controller 請檢查參數是否設置正確!");
            return;
        }

        //處理器
        HandlerAdapterService ha = (HandlerAdapterService) beans.get(HANDLERADAPTER);


        /*
         * @RequestMapping("/order")
         * order(@RequestBody String params, @RequestHeader @RequestParam String param1){//參數有多種類型接收方式
         * }
         */


        Object[] args = ha.hand(request, response, method, beans);

        try {
            method.invoke(instance, args);
            // method.invoke(instance, new
            // Object[]{request,response,null});//拿參數
			
			/*如果有多個參數類型,就得這樣寫了(可用策略模式,省去以下代碼)
			 * if(ParamType == HttpServletRequest){
				
			}else if(ParamType == @RquestHeader){
				
			}else
			*用策略模式實現(把粒度控制得更細),新建 ChenHandlerAdapter
			*/


        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    private void HandlerMapping() {
        if (beans.entrySet().size() <= 0) {
            System.out.println("沒有類的實例化!");
            return;
        }

        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            Object instance = entry.getValue();

            Class<?> clazz = instance.getClass();
            // 拿所有Controller的類
            if (clazz.isAnnotationPresent(ChenController.class)) {
                String classPath = "";

                //防止用戶在類上沒有寫 ChenRequestMapping
                if (clazz.isAnnotationPresent(ChenRequestMapping.class)) {
                    //@com.njust.springmvc.annotation.ChenRequestMapping(value=/chen)
                    ChenRequestMapping requestMapping = clazz
                            .getAnnotation(ChenRequestMapping.class);
                    // 獲取Controller類上面的ChenRequestMapping註解裏的請求路徑
                    classPath = requestMapping.value();
                }
                // 獲取控制類裏的所有方法
                Method[] methods = clazz.getMethods();
                // 獲取方法上的ChenRequestMapping設置的路徑,與方法名建立映射關係
                for (Method method : methods) {
                    //判斷哪些方法上使用ChenRequestMapping路徑註解
                    if (method.isAnnotationPresent(ChenRequestMapping.class)) {
                        //@com.njust.springmvc.annotation.ChenRequestMapping(value=/query)
                        ChenRequestMapping methodrm = method
                                .getAnnotation(ChenRequestMapping.class);
                        String methodPath = methodrm.value();
                        // 把方法上與路徑建立映射關係( /Chen/query--->public void com.njust.springmvc.controller.ChenController.query )
                        handlerMap.put(classPath + methodPath, method);
                        //將具體的路徑和controller實例關聯
                        handlerControllerMap.put(classPath + methodPath, instance);
                    } else {
                        continue;
                    }
                }
            }
        }
    }

    // 初始化IOC容器
    private void ioc() {

        if (beans.entrySet().size() <= 0) {
            System.out.println("沒有類的實例化!");
            return;
        }
        //將實例化好的bean遍歷,
        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            Object instance = entry.getValue();//獲取bean實例

            Class<?> clazz = instance.getClass();//獲取類,用來判斷類裏聲明瞭哪些註解(主要是針對控制類裏的判斷,比如使用了@Autowired  @Qualifier,對這些註解進行解析)
            //判斷該類是否使用了ChenController註解 或ChenService註解,註解類允許注入
            if (clazz.isAnnotationPresent(ChenController.class) || clazz.isAnnotationPresent(ChenService.class)) {
                Field[] fields = clazz.getDeclaredFields();// 拿到類裏面的屬性
                // 判斷是否聲明瞭自動裝配(依賴注入)註解,比如@Autrowired @Qualifier
                for (Field field : fields) {
                    if (field.isAnnotationPresent(ChenQualifier.class)) {
                        ChenQualifier qualifier = field.getAnnotation(ChenQualifier.class);
                        //拿到@ChenQualifier("ChenServiceImpl")裏的指定要注入的bean名字"ChenServiceImpl"
                        String value = qualifier.value();
//                        通用化,如果不設置值,則使用默認值
                        if (value == null || value.equals("")) {
//                            使用全類名
                            value = field.getType().getName();

                            try {
//                                根據全類名獲取class文件
                                Class<?> aClass = Class.forName(value);
//                                判斷該類是否是接口
                                if (aClass.isInterface()) {
//                                    查看接口的所有實現類
                                    List<Class<?>> allActionSubClass = CommonUtil.getAllActionSubClass(value);
                                    if (allActionSubClass.size() >= 2 || allActionSubClass.size() <= 0) {
                                        System.exit(0);
                                        throw new RuntimeException("接口沒有實現或接口子類有多個實現!");

                                    }

                                    value = (allActionSubClass.get(0).getName());
                                }
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (NoSuchFieldException e) {
                                e.printStackTrace();
                            }
                        }
                        field.setAccessible(true);
                        try {
                            // 從MAP容器中獲取"ChenServiceImpl"對應的bean,並注入實例控制層bean,解決依賴注入
//                            給instance對象的field字段賦值 beans.get(value)
                            field.set(instance, beans.get(value));
                        } catch (IllegalArgumentException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    } else {
                        continue;
                    }
                }
            } else {
                continue;
            }
        }
    }


}


2、web.xml

  設置啓動容器時刻初始化ChenDispatcherServlet,映射路徑爲/。源碼如下:

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>

  <servlet>
    <servlet-name>ChenDispatcherServlet</servlet-name>
    <servlet-class>com.njust.springmvc.servlet.ChenDispatcherServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>ChenDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>


3、參數解析

  定義ArgumentResolver接口類,解析方法中的參數,獲取數值並返回給調用者。源碼如下:

ArgumentResolver .java

package com.njust.springmvc.argumentResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:52
 * @description:
 */
public interface ArgumentResolver {
    public boolean support(Class<?> type, int paramIndex, Method method);

    //參數解析方法
    public Object argumentResolver(HttpServletRequest request,
                                   HttpServletResponse response, Class<?> type,
                                   int paramIndex,//參數索引下座標,有很多註解,你得知道是哪個參數的註解,每個參數的索引順序不一樣
                                   Method method);
}


4、HttpServletRequest參數解析

  定義HttpServletRequestArgumentResolver 類,實現ArgumentResolver接口。support()主要用來判斷傳進來的類型是否是ServletRequest類或它的子類。因爲這是一個request參數解析類,所以argumentResolver()方法直接返回request參數給調用者。源代碼如下:

HttpServletRequestArgumentResolver .java

package com.njust.springmvc.argumentResolver;

import com.njust.springmvc.annotation.ChenService;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:53
 * @description:
 */

@ChenService("httpServletRequestArgumentResolver")
public class HttpServletRequestArgumentResolver implements ArgumentResolver {
    //判斷傳進來的參數是否爲request
    public boolean support(Class<?> type, int paramIndex, Method method) {
//        isAssignableFrom()方法是判斷是否爲某個類的父類
        /**
         * class1.isAssignableFrom(class2) 判定此 Class1 對象所表示的類或接口與
         * 指定的 Class2 參數所表示的類或接口是否相同,
         * 或是否是其超類或超接口。
         * 如果是則返回 true;否則返回 false。
         */
        return ServletRequest.class.isAssignableFrom(type);
    }
    ////如果返回的參數是request,則直接返回
    public Object argumentResolver(HttpServletRequest request,
                                   HttpServletResponse response, Class<?> type, int paramIndex,
                                   Method method) {
        return request;
    }

}


5、HttpServletResponse參數解析

  定義HttpServletResponseArgumentResolver類,同HttpServletRequestArgumentResolver類似,實現ArgumentResolver接口。support()主要用來判斷傳進來的類型是否是ServletResponse類或它的子類。因爲這是一個response參數解析類,所以argumentResolver()方法直接返回response參數給調用者。源碼如下:

HttpServletResponseArgumentResolver .java

package com.njust.springmvc.argumentResolver;

import com.njust.springmvc.annotation.ChenService;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:54
 * @description:
 */
@ChenService("httpServletResponseArgumentResolver")
public class HttpServletResponseArgumentResolver implements ArgumentResolver {
    //判斷傳進來的參數是否爲response
    public boolean support(Class<?> type, int paramIndex, Method method) {
        return ServletResponse.class.isAssignableFrom(type);
    }
    //如果返回的參數是response,則直接返回
    public Object argumentResolver(HttpServletRequest request,
                                   HttpServletResponse response, Class<?> type, int paramIndex,
                                   Method method) {
        return response;
    }

}


6、RequestParam參數解析

  定義RequestParamArgumentResolver 類,同HttpServletRequestArgumentResolver類似,實現ArgumentResolver接口。support()主要用來判斷傳進來的類型是否加了ChenRequestParam註解或它的子類註解。argumentResolver()方法判斷是否使用了ChenRequestParam 註解,如果沒有使用,直接返回null,如果使用了,首先判斷value是否爲空,如果不爲空,直接通過request獲取參數值並返回,如果爲空,則通過java反射機制獲取參數名稱,然後再通過request獲取參數值並返回。這裏注意要判斷jdk的版本,這個方法只是在JDK1.8以後才支持,同時在maven中一定要配置<compilerArgument>-parameters</compilerArgument>,才能使得該方法生效。源碼如下:

RequestParamArgumentResolver .java

package com.njust.springmvc.argumentResolver;

import com.njust.springmvc.annotation.ChenRequestParam;
import com.njust.springmvc.annotation.ChenService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:54
 * @description:
 */

@ChenService("requestParamArgumentResolver")
//解析聲明註解爲RequestParam, 獲取註解的值
public class RequestParamArgumentResolver implements ArgumentResolver {
    //判斷傳進來的參數是否爲ChenRequestParam
    public boolean support(Class<?> type, int paramIndex, Method method) {

        Annotation[][] an = method.getParameterAnnotations();

        Annotation[] paramAns = an[paramIndex];

        for (Annotation paramAn : paramAns) {
            //判斷傳進的paramAn.getClass()是不是 ChenRequestParam 類型
            if (ChenRequestParam.class.isAssignableFrom(paramAn.getClass())) {
                return true;
            }
        }
        return false;
    }
    //參數解析,並獲取註解的值
    public Object argumentResolver(HttpServletRequest request,
                                   HttpServletResponse response, Class<?> type, int paramIndex,
                                   Method method) {

        Annotation[][] an = method.getParameterAnnotations();

        Annotation[] paramAns = an[paramIndex];

        for (Annotation paramAn : paramAns) {
            if (ChenRequestParam.class.isAssignableFrom(paramAn.getClass())) {
                ChenRequestParam rp = (ChenRequestParam)paramAn;

                String value = rp.value();
                if (value == null || value.equals("")) {
                    //如果是JDK1.8以後,使用反射獲取參數名稱
                    if (System.getProperty("java.version").contains("1.8.")) {
                        Parameter parameter = method.getParameters()[paramIndex];
                        String name = parameter.getName();
                        value = name;
                    }
                }

                return request.getParameter(value);
            }
        }

        return null;
    }

}

7、請求適配

  定義HandlerAdapterService 類,封裝請求處理。源碼如下:

HandlerAdapterService .java

package com.njust.springmvc.handlerAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:56
 * @description:
 */
public interface HandlerAdapterService {

    Object[] hand(HttpServletRequest request,//拿request請求裏的參數
                         HttpServletResponse response,//
                         Method method,
                         Map<String, Object> beans);
}


8、請求適配實現

  定義JamesHandlerAdapter類,實現根據用戶請求的URL和方法,獲取到參數的值並返回。源碼如下:

JamesHandlerAdapter .java

package com.njust.springmvc.handlerAdapter;

import com.njust.springmvc.annotation.ChenService;
import com.njust.springmvc.argumentResolver.ArgumentResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:56
 * @description:
 */

@ChenService("jamesHandlerAdapter")
public class JamesHandlerAdapter implements HandlerAdapterService {
    //對method方法裏的參數進行處理
    public Object[] hand(HttpServletRequest request,//需要傳入request,拿請求的參數
                         HttpServletResponse response, Method method,//執行的方法,可以拿到當前待執行的方法有哪些參數
                         Map<String, Object> beans) {
        //拿到當前待執行的方法有哪些參數類型
        Class<?>[] paramClazzs = method.getParameterTypes();
        //根據參數的個數,new 一個參數的數組,將方法裏的所有參數賦值到args來
        Object[] args = new Object[paramClazzs.length];

        //1、要拿到所有實現了ArgumentResolver這個接口的實現類
        Map<String, Object> argumentResolvers = getBeansOfType(beans,
                ArgumentResolver.class);

        int paramIndex = 0;
        int i = 0;
        //對每一個參數進行循環,每個參數都有特殊處理(比如RequestParam的處理類爲 RequestParamArgumentResolver )
        for (Class<?> paramClazz : paramClazzs) {
            //哪個參數對應了哪個參數解析類,用策略模式來找
            for (Map.Entry<String, Object> entry : argumentResolvers.entrySet()) {
                ArgumentResolver ar = (ArgumentResolver) entry.getValue();

                if (ar.support(paramClazz, paramIndex, method)) {
                    args[i++] = ar.argumentResolver(request,
                            response,
                            paramClazz,
                            paramIndex,
                            method);
                }
            }
            paramIndex++;
        }

        return args;
    }

    //獲取實現了ArgumentResolver接口的所有實例(其實就是每個參數的註解實例)
    private Map<String, Object> getBeansOfType(Map<String, Object> beans,//所有bean
                                               Class<?> intfType) //類型的實例
    {

        Map<String, Object> resultBeans = new HashMap<>();

        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            //拿到實例-->反射對象-->它的接口(接口有多實現,所以爲數組)
            Class<?>[] intfs = entry.getValue().getClass().getInterfaces();

            if (intfs != null && intfs.length > 0) {
                for (Class<?> intf : intfs) {
                    //接口的類型與傳入進來的類型一樣,把實例加到resultBeans裏來
                    if (intf.isAssignableFrom(intfType)) {
                        resultBeans.put(entry.getKey(), entry.getValue());
                    }
                }
            }
        }

        return resultBeans;
    }

}


三、測試結果

輸入url:http://localhost:8080/query?name=1&age=2,結果顯示:{name=1,age=2}
在這裏插入圖片描述

以上結果表明我們的手寫SpringMVC已經可以正常工作了。

總結

步驟

  自定義SpringMVC的主要實現步驟如下:

  • 首先我們自定義ChenController ChenService ChenQualifier ChenRequestMapping ChenRequestParam 註解類,保證註解不僅被保存到class文件中,而且jvm加載class文件之後,仍然存在,以便被spring反射出實例對象。
  • 然後自定義ChenDispatcherServlet,完成請求分發的功能。同時在web.xml中設置啓動容器時刻初始化ChenDispatcherServlet,映射路徑爲/ChenDispatcherServlet初始化工作主要包括:
    • 首先要根據一個基本包進行掃描,掃描裏面的子包以及子包下的類,將全類名添加到List集和中。
    • 其次要把掃描出來的類進行實例化。以全類名爲keyclass實例對象爲value,放入到hashMap容器中。
    • 依賴注入,把service層的實例注入到controller,即初始化controller層的成員變量。
    • 建立pathmethod的映射關係,保存在hashMap容器中。
  • 然後doPost方法監聽用戶請求。具體流程如下:
    • 首先根據請求路徑去容器中找到相應的方法,如果沒有直接返回,並告知用戶沒有該路徑。
    • 然後根據請求路徑去容器中找到對應的控制類,如果沒有直接返回,並告知用戶沒有該路徑。
    • 然後根據策略模式獲取請求參數的實際值。
    • 最後通過反射機制執行該方法,並響應給用戶,完成整個業務處理邏輯。

流程圖

服務啓動流程圖

Created with Raphaël 2.2.0開始首先要根據一個基本包進行掃描,掃描裏面的子包以及子包下的類,將全類名添加到List集和中。其次要把掃描出來的類進行實例化。以全類名爲key,class實例對象爲value,放入到hashMap容器中。依賴注入,把service層的實例注入到controller, 即初始化controller層的成員變量。建立path與method的映射關係,保存在hashMap容器中。結束

請求響應流程圖

Created with Raphaël 2.2.0開始獲取到請求路徑根據請求路徑來獲取要執行的方法method是否爲空?結束根據請求路徑來獲取控制類控制類controller是否爲空?獲取HANDLERADAPTER處理器根據處理器取得參數通過反射機制執行該方法,並響應給用戶yesnoyesno

重點及易錯點

1、參數名稱獲取

               String value = rp.value();
                if (value == null || value.equals("")) {
                    //如果是JDK1.8以後,使用反射獲取參數名稱
                    if (System.getProperty("java.version").contains("1.8.")) {
                        Parameter parameter = method.getParameters()[paramIndex];
                        String name = parameter.getName();
                        value = name;
                    }
                }

  這段代碼處理ChenRequestParam沒有設置value()使用默認值的情況,一定要判斷JDK的版本,同時在maven中添加如下配置參數。

              <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <source>8</source>
                        <target>8</target>
                        <!--反射獲取方法參數名稱-->
                        <compilerArgument>-parameters</compilerArgument>
                    </configuration>
                </plugin>

2、控制反轉(IOC),實例化成員變量


                        //拿到@ChenQualifier("ChenServiceImpl")裏的指定要注入的bean名字"ChenServiceImpl"
                        String value = qualifier.value();

//                        通用化,如果不設置值,則使用默認值
                        if (value == null || value.equals("")) {
//                            使用全類名
                            value = field.getType().getName();

                            try {
//                                根據全類名獲取class文件
                                Class<?> aClass = Class.forName(value);
//                                判斷該類是否是接口
                                if (aClass.isInterface()) {
//                                    查看接口的所有實現類
                                    List<Class<?>> allActionSubClass = CommonUtil.getAllActionSubClass(value);
                                    if (allActionSubClass.size() >= 2 || allActionSubClass.size() <= 0) {
                                        System.exit(0);
                                        throw new RuntimeException("接口沒有實現或接口子類有多個實現!");

                                    }

                                    value = (allActionSubClass.get(0).getName());
                                }
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (NoSuchFieldException e) {
                                e.printStackTrace();
                            }
                        }

  上面的代碼是處理@ChenQualifier沒有設置value()使用默認值的情況。如果是正常的類,則直接使用全類名作爲key去容器中找實例對象。如果是接口,則需要注意判斷接口是否實現,或有多個實現。如果有多個實現可以任意選擇一個實例或拋出異常。spring官方是直接拋出異常,這裏我們也是拋出異常。

3、容器key命名

  我查閱相關資料發現,有一些作者將類的名稱作爲key,而不是全類名,當框架中有多個模塊的時候難免會有類名衝突的問題。所以我建議還是使用全類名,上面的代碼實現也是全類名。

4、小結

  註解默認值確實需要考慮太多的東西,同時框架也需要更多的業務邏輯判斷,所以在編程實踐中能明確的就明確,提高程序的運行效率。
  寫小輪子太燒腦了,有問題歡迎各位讀者批評指正。

點個贊再走唄!歡迎留言哦!

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