1. 程式人生 > >手寫SpringMVC (一) 簡要版,去除冗餘複雜程式碼,手寫Spring核心功能

手寫SpringMVC (一) 簡要版,去除冗餘複雜程式碼,手寫Spring核心功能

github 地址 :https://github.com/yjy91913/jerry-mvcframework
只是閒來無事寫的簡化版,僅供大家理解SpringMvc的運作原理)
瞭解了springMVC的原始碼,寫一個功能簡單可以實現的springMVC,只是為了深入瞭解spring

先看一下專案結構
這裡寫圖片描述
demo是測試類

第一步:pom檔案

加入依賴 -servlet

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

第二步:配置web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="true" version="3.0">
<display-name> Jerry web application </display-name> <!--配置一個servlet,這個配置一個servlet就是springMVC中的DispatcherServlet--> <servlet> <servlet-name>jmvc</servlet-name> <servlet-class
>
com.jerry.mvcframework.servlet.JDispatcherServlet</servlet-class> <init-param> <!--讀取檔案的地址--> <param-name>contextConfigLocation</param-name> <param-value>application.properties</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jmvc</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>

第三步:配置掃描包的位置

這一步簡化了,只有讀取掃描包的位置
resources 目錄下建立一個配置檔案application.properties
scanPackage=com.jerry.demo

第四步:配置所有的常用註解

這個只寫一下常用的註解

@JAutowired

@Target({ElementType.FIELD})     //欄位、列舉的常量
@Retention(RetentionPolicy.RUNTIME)     //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented     //說明該註解將被包含在javadoc中
public @interface JAutowired {
    String value() default "";
}

@JController

@Target({ElementType.TYPE})     //介面、類、列舉、註解
@Retention(RetentionPolicy.RUNTIME)     //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented     //說明該註解將被包含在javadoc中
public @interface JController {
    String value() default "";
}

@JRequestMapping

@Target({ElementType.METHOD,ElementType.TYPE})     //欄位、列舉的常量    方法
@Retention(RetentionPolicy.RUNTIME)     //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented     //說明該註解將被包含在javadoc中
public @interface JRequestMapping {
    String value() default "";
}

@JRequestParam

@Target({ElementType.PARAMETER})     //方法引數
@Retention(RetentionPolicy.RUNTIME)     //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented     //說明該註解將被包含在javadoc中
public @interface JRequestParam {
    String value() default "";
}

@JService

@Target({ElementType.TYPE})     //介面、類、列舉、註解
@Retention(RetentionPolicy.RUNTIME)     //註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
@Documented     //說明該註解將被包含在javadoc中
public @interface JService {
    String value() default "";
}

第五步:建立JDispatcherServlet

我們所要的做的事情,就是實現這個類
原聲Servlet主要方法我們要處理就兩個 一個:init doPost doGet 後面兩個其實是一個方法

第六步:編寫init()方法

主要需要實現的功能都在註釋裡

    @Override
    public void init(ServletConfig config) throws ServletException {

        //1.載入配置檔案
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2.掃描所有的相關的類
        doScanner(properties.getProperty("scanPackage"));

        //3.初始化所有相關的類Class的例項,並且將其儲存到IOC容器
        doInstance();

        //4.自動化的依賴注入
        doAutowired();

        //5.初始化HandlerMapping
        initHandlerMapping();

        System.out.println("Jerry MVC Framework is init");

    }

第六步:編寫doLoadConfig()方法

    private void doLoadConfig(String configLocation){
        //獲取一個inputStream
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);

        //載入配置檔案
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(null != inputStream) {

                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

第六步:編寫doScanner()方法

這部是掃描我們指定的目錄

    //初始化一個List,用於存放.class的地址
    private List<String> classNames = new ArrayList<String>();

    private void doScanner(String packageName){
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        //獲取所有的位元組碼檔案
        File classDir = new File(url.getFile());

        for (File file : classDir.listFiles()) {
            //如果是資料夾遞迴執行
            if(file.isDirectory()){
                doScanner(packageName + "." + file.getName());
            } else {
                //輸入是.class,加入list
                classNames.add(packageName + "." + file.getName().replace(".class",""));
            }
        }
    }

第七步:編寫doInstance()方法

初始化所有相關的類Class的例項,並且將其儲存到IOC容器

    //初始化ioc容器
    private Map<String,Object> ioc = new HashMap<String, Object>();

    //寫一個講第一個字母變成小寫的方法
    private String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    //編寫doInstance()
    private void doInstance(){
        if(classNames.isEmpty()){
            return;
        }

        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                //進行例項化
                //判斷,是否有Controller等註解
                if(clazz.isAnnotationPresent(JController.class)){

                    String beanName = lowerFirst(clazz.getSimpleName());
                    ioc.put(beanName,clazz.newInstance());


                }else if (clazz.isAnnotationPresent(JService.class)){
                    JService jService = clazz.getAnnotation(JService.class);
                    String beanName = jService.value();
                    //1.預設採用類名首字母小寫 beanId
                    //2.如果自定義名字,優先使用自己定義的名字
                    if("".equals(beanName.trim())){
                        beanName = lowerFirst(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();
                    ioc.put(beanName,instance);
                    //3.根據型別匹配,利用實現類的介面名字作為key,實現類的類做為value
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces) {
                        ioc.put(lowerFirst(i.getSimpleName()),instance);
                    }
                }else {
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

第七步:編寫doAutowired()方法

自動化的依賴注入,掃描所有帶autowired註解

private void doAutowired(){
        if(ioc.isEmpty()){
            return;
        }
        for (Map.Entry<String,Object> entry : ioc.entrySet()) {

            //在Spring中,沒有隱私
            Field[] fields = entry.getValue().getClass().getDeclaredFields();


            for (Field field : fields) {
                //找autowired
                if(!field.isAnnotationPresent(JAutowired.class)){
                    continue;
                }

                JAutowired jAutowired = field.getAnnotation(JAutowired.class);
                String beanName = jAutowired.value().trim();

                if("".equals(beanName)){
                    beanName = lowerFirst(field.getType().getSimpleName());
                }
                //暴力反射
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                    System.out.println(entry.getValue()+" is autowired ,object is "+ioc.get(beanName));
                } catch (Exception e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

第七步:初始化HandlerMapping

初始化一個HandlerMapping

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

寫一個handler類

    @Data
    @ToString
    private class Handler {
        protected Object controller;
        protected Method method;
        protected Pattern pattern;
        protected Map<String,Integer> paramIndexMapping;
    }

編寫initHandlerMapping()方法

    private void initHandlerMapping() {

        if(ioc.isEmpty()){
            return;
        }

        for (Map.Entry<String,Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            //HandlerMapping 只認JController
            if(!clazz.isAnnotationPresent(JController.class)){
                continue;
            }
            String url = "";
            if(clazz.isAnnotationPresent(JRequestMapping.class)){
                JRequestMapping jRequestMapping = clazz.getAnnotation(JRequestMapping.class);
                url = jRequestMapping.value().trim();
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                //如果沒有JRequestMapping ,直接跳過
                if(!method.isAnnotationPresent(JRequestMapping.class)){
                    continue;
                }
                JRequestMapping jRequestMapping = method.getAnnotation(JRequestMapping.class);
                String murl = url + jRequestMapping.value().trim();

                Handler handler = new Handler();
                handler.setController(entry.getValue());
                handler.setMethod(method);
                handlerMapping.put(murl,handler);

                System.out.println("Mapping : "+ murl + "  " +handler);
            }
        }
    }

到這裡springMVC初始化方法已經寫完了,下面就是
doDispatch()方法了 ,下次在寫