手寫一個簡單版本的spring MVC

pom.xml


 <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.12</version>
            <scope>test</scope>
        </dependency>
        <!--servlet-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <encoding>UTF-8</encoding>
                    <!--告訴編譯器,保存形參的值-->
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <!--這裏可以修改端口-->
                    <port>8089</port>
                    <path>/</path>
                    <server>tomcat7</server>
                </configuration>
            </plugin>
        </plugins>
    </build>

配置文件

scanPackage=com.liu.mvc.demo

在這裏插入圖片描述

自定義註解


@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutoWired {
    String  value() default "";
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {

    String value() default "";
}

@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    String value() default "";
}

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {

    String value() default "";
}

Handler類用記url與method的綁定

package com.liu.mvc.framework.handler;

import java.io.Serializable;
import java.lang.reflect.Method;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @Description
 * @ClassName Handler
 * @Author 劉楠
 * @date 2020.07.02
 */
public class Handler implements Serializable {
    private static final long serialVersionUID = 1060649943862035180L;
    /**
     * 控制器
     */
    private Object controller;
    /**
     * 方法
     */
    private Method method;
    /**
     * 匹配
     */
    private Pattern pattern;
    /**
     * 請求url
     */
    private String requestUrl;
    /**
     * 參數的順序 key是參數名  value是第幾個參數
     */
    private Map<String, Integer> paramIndexMapping ;

    public Handler() {
    }

    public Handler(Object controller, Method method, Pattern pattern, String requestUrl) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        this.requestUrl = requestUrl;
        this.paramIndexMapping = new HashMap<>();
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }

    public String getRequestUrl() {
        return requestUrl;
    }

    public void setRequestUrl(String requestUrl) {
        this.requestUrl = requestUrl;
    }

    public Map<String, Integer> getParamIndexMapping() {
        return paramIndexMapping;
    }

    public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
        this.paramIndexMapping = paramIndexMapping;
    }

    public void add(String paramName,Integer index){
        paramIndexMapping.put(paramName,index);
    }
}

自定義MyDispatcherServlet

package com.liu.mvc.framework.servlet;

import com.liu.mvc.framework.annotation.MyAutoWired;
import com.liu.mvc.framework.annotation.MyController;
import com.liu.mvc.framework.annotation.MyRequestMapping;
import com.liu.mvc.framework.annotation.MyService;
import com.liu.mvc.framework.handler.Handler;
import org.apache.commons.lang3.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.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @Description
 * @ClassName MyDispatcherServlet
 * @Author 劉楠
 * @date 2020.07.02
 */
public class MyDispatcherServlet extends HttpServlet {

    private Properties props = new Properties();
    //緩存類的全類名
    private List<String> classNames = new ArrayList<>();
    //實現化Bean對象
    private Map<String, Object> iocMap = new ConcurrentHashMap<>();

    //HandlerMapping
//    private Map<String, Handler> handlerMapping = new HashMap<>();;
    private List<Handler> handlerMapping = new ArrayList<>();;


    @Override
    public void init(ServletConfig config) throws ServletException {
        //0.從web.xml Servelt中init-param獲取
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        //1.加載配置文件 properties
        doLoadConfig(contextConfigLocation);
        //2.掃描有相關注解的類
        doScan(props.getProperty("scanPackage"));
        //3.初始化Bean(IOC容器)

        doInstance();

        //4. 實現對象之間依賴注入
        doAutoWire();
        //5.初始SpringMVC相關組件如HandlerMapping處理器映射器 url method 綁定關係
        initHandlerMapping();
        System.out.println("MyMVC初始化完批");
        //6.處理請求

    }

    private void doLoadConfig(String contextConfigLocation) {

        //讀成注
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        //加載
        try {
            props.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 掃描包
     *
     * @param basePackage
     */
    public void doScan(String basePackage) {
        String classPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String basePackagePath = basePackage.replaceAll("\\.", "/");
        String scanPath = classPath + basePackagePath;

        File pack = new File(scanPath);
        File[] files = pack.listFiles();

        for (File file : files) {
            if (file.isDirectory()) {
                //遞歸
                doScan(basePackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                //文件class文件 全類名
                String className = basePackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }

        }
    }

    /**
     * 實例化Bean對象
     * 基於類的全類名和反射創建對象
     */
    private void doInstance() {
        if (classNames.size() == 0) {
            return;
        }

        try {
            for (String className : classNames) {
                //com.liu.mvc.demo.controller.DemoController
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(MyController.class)) {
                    //是個Controller 類的首字母小寫保存
                    String simpleName = clazz.getSimpleName();
                    //首字母小寫
                    String lowerFirstSimpleName = lowerFirst(simpleName);
                    //實現化對象
                    Object instance = clazz.newInstance();

                    iocMap.put(lowerFirstSimpleName, instance);

                } else if (clazz.isAnnotationPresent(MyService.class)) {

                    //是Service

                    MyService annotation = clazz.getAnnotation(MyService.class);
                    Object newInstance = clazz.newInstance();
                    String beanName = annotation.value();
                    if (!"".equals(beanName)) {
                        iocMap.put(beanName, newInstance);
                    } else {

                        beanName = lowerFirst(clazz.getSimpleName());

                        iocMap.put(beanName, newInstance);

                    }
                    /**
                     * 將接口也放一份
                     */
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        //以接口的全類名
                        iocMap.put(anInterface.getName(), newInstance);
                    }

                } else {
                    continue;
                }

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

    }

    public String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    /**
     * 實現Bean之間的依賴注入
     */
    private void doAutoWire() {
        if (iocMap.isEmpty()) {
            return;
        }
        try {
            /**
             *   遍歷容器中的所有對象 判斷屬性有沒有MyAutoWire註解
             */

            for (Map.Entry<String, Object> entry : iocMap.entrySet()) {

                /**
                 * 對象實例
                 */
                Object entryValue = entry.getValue();
                //獲取所有字段
                Field[] fields = entryValue.getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (!field.isAnnotationPresent(MyAutoWired.class)) {
                        continue;
                    }
                    //有註解
                    MyAutoWired annotation = field.getAnnotation(MyAutoWired.class);
                    String beanName = annotation.value();
                    if ("".equals(beanName.trim())) {
                        //獲取類型再獲取名稱
                        beanName = field.getType().getName();
                    }

                    //開啓賦值
                    field.setAccessible(true);

                    field.set(entryValue, iocMap.get(beanName));
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }


    }

    /**
     * 實現key-value url-method映射關係
     */
    private void initHandlerMapping() {
        if(iocMap.isEmpty()){
            return;
        }

        try {
            for (Map.Entry<String, Object> entry : iocMap.entrySet()) {

                Object value = entry.getValue();
                Class<?> clazz = value.getClass();
                //判斷是不是MyController繼續
                if(!clazz.isAnnotationPresent(MyController.class)){
                    continue;
                }

                String baseUrl="";
                if(clazz.isAnnotationPresent(MyRequestMapping.class)){
                    MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
                    baseUrl=annotation.value();
                }
                //獲取所有方法
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if(!method.isAnnotationPresent(MyRequestMapping.class)){
                        //沒有註解不處理
                        continue;
                    }
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                    String methodUrl =annotation.value();

                    String url = baseUrl+methodUrl;
                    Pattern pattern = Pattern.compile(url);

                    Handler handler =new Handler(value, method,pattern , url);
                    Parameter[] parameters = method.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        Parameter parameter = parameters[i];
                        if(parameter.getType() ==HttpServletRequest.class || parameter.getType()==HttpServletResponse.class) {
                            handler.add(parameter.getType().getSimpleName(),i);
                        }else{
                            handler.add(parameter.getName(), i);
                        }
                    }

                    handlerMapping.add(handler);
                }


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


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //請求的uri

       Handler handler=  getHandler(req);
       if(handler==null){
           resp.getWriter().write("404 not found");
       }


       //參數類型數組
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();

        //創建數組 並與方法中的參數順序保持一致
        Object[] args = new Object[parameterTypes.length];


        //
        Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();
        /**
         * 獲取請求的參數 填充普通參數
         */
        Map<String, String[]> parameterMap = req.getParameterMap();
        for(Map.Entry<String,String[]> entry:parameterMap.entrySet()){
            String entryKey = entry.getKey();
            String[] entryValue = entry.getValue();
            //遍歷所有參數
            String join = StringUtils.join(entryValue, ",");
            //如果也方法中的參數匹配上就填充
          if(!  paramIndexMapping.containsKey(entryKey)){
              continue;
          }
          //有這個參數 找到這個索引
            Integer index = paramIndexMapping.get(entryKey);
          //賦值
          args[index]=join;


        }

        //填充request與response參數
        Integer requestIndex = paramIndexMapping.get(HttpServletRequest.class.getSimpleName());
        args[requestIndex]=req;
        Integer responseIndex = paramIndexMapping.get(HttpServletResponse.class.getSimpleName());
        args[responseIndex]=resp;

        try {
            handler.getMethod().invoke(handler.getController(),args );
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    private Handler getHandler(HttpServletRequest req) {
        if(handlerMapping.isEmpty()){
            return null;
        }
        String requestURI = req.getRequestURI();
        Optional<Handler> handler = handlerMapping.stream().filter(h -> {
            Matcher matcher = h.getPattern().matcher(requestURI);
            return matcher.matches();
        }).findFirst();

        if(handler.isPresent()){
            return handler.get();
        }
        return null;

    }
}

web.xml

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                   http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1"
         metadata-complete="false">


    <servlet>
        <servlet-name>mvcServelt</servlet-name>
        <servlet-class>com.liu.mvc.framework.servlet.MyDispatcherServlet</servlet-class>
      <!--初始參數-->

      <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>springmvc.properties</param-value>
      </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvcServelt</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.js</url-pattern>
        <url-pattern>*.css</url-pattern>
        <url-pattern>*.html</url-pattern>
        <url-pattern>*.htm</url-pattern>
        <url-pattern>*.png</url-pattern>
        <url-pattern>*.jpg</url-pattern>
        <url-pattern>*.gif</url-pattern>
        <url-pattern>*.ico</url-pattern>
    </servlet-mapping>

</web-app>

Controller

@MyController
@MyRequestMapping("/demo")
public class DemoController {

    @MyAutoWired
    private IDemoServcie demoServcie;


    @MyRequestMapping("/query")
    public String query(HttpServletRequest request, HttpServletResponse response, String name){
        String s = demoServcie.get(name);
        return s;
    }

}

service

public interface IDemoServcie {

    String get(String name);
}
@MyService("demoService")
public class DemoServcieImpl implements IDemoServcie {
    @Override
    public String get(String name) {
        System.err.println("DemoServcieImpl: "+name);
        return name;
    }
}



完成
https://gitee.com/null_631_9084/myhomework/tree/master/stage01-springmvc/springmvc-task02

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