從0開始仿寫一個SpringMvc框架(1)

本文章只是仿寫SpringMvc框架的第一篇,後續會增加Aop、HandlerInterceptor、Valid、掃描xml文件等功能。

首先說下仿寫SpringMvc(mini)需要實現的幾個核心功能:實現Ioc容器(Beanfactory),掃描註解解析出需要處理的類(controller、bean、RequestMapping、AutoWired註解等),實現SpringMvc核心的DispatcherServlet以及Handler等。

本項目基於idea開發

1、建立項目,新建一個maven工程導入相關依賴,主要是tomcat的依賴和配置打成jar包後程序的入口

 
    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.5.34</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.2</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.dy.minispringMvc.MiniSpringMvcApp</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

注意:mainClass是你自己的main方法所在類,這裏主要是爲了配置jar包的程序入口。

打包時候點擊左上角的Maven然後點擊install即可,打完後的包可以直接運行。

2、整合tomcat服務器,這裏**注意 TomcatServer.this.tomcat.getServer().await();**開啓tomcat服務等待。不然運行的時候刷一下就過去了。

package com.dy.minispringMvc.core;

import com.dy.minispringMvc.Servlet.CustomDispatcherServlet;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;

public class TomcatServer {
    private Tomcat tomcat;
    private String[] args;

    public TomcatServer(String[] args) {
        this.args = args;
    }

    //把自定義servlet註冊到tomcat容器當中
    public void startServer() throws LifecycleException {
        tomcat = new Tomcat();
        tomcat.setPort(7788);
        tomcat.start();
        //創建一個Context對象
        Context context = new StandardContext();
        context.setPath("");
        context.addLifecycleListener(new Tomcat.FixContextListener());
        
        //實例化我們自己的CustomDispatcherServlet
        CustomDispatcherServlet servlet = new CustomDispatcherServlet();
        
        //註冊我們自己的CustomDispatcherServlet到tomcat
        tomcat.addServlet(context, "servlet", servlet).setAsyncSupported(true);
        
        //設置映射,這裏是7788端口下所有路徑都會到我們自己的CustomDispatcherServlet 
        context.addServletMappingDecoded("/", "servlet");
        tomcat.getHost().addChild(context);

        TomcatServer.this.tomcat.getServer().await();

    }


}

下面是自己實現的CustomDispatcherServlet類,實現Servlet的service方法即可


public class CustomDispatcherServlet implements Servlet {
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        servletResponse.getWriter().println("hello SpringMc");
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}

直接運行,然後可以看到tomcat打印信息,證明tomcat整合完成
在這裏插入圖片描述在這裏插入圖片描述
瀏覽器訪問7788端口下任意路徑看到如上圖,證明第一步整合tomcat容器成功。

2、創建核心類CustomClassLoader,這個類的目的很簡單,就是拿到基於當前main方法所在類的所有Class。


/**
 * 自定義類加載 目的就是拿到當前jar包下所有java.lang.Class
 * <p>
 * 類的加載3個階段
 * <p>
 * 通過一個類的全限定名來獲取定義此類的二進制字節流
 * 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構
 * 在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口
 * <p>
 */
public class CustomClassLoader {
    public static List<Class<?>> loadeClass(String packageName) throws IOException, ClassNotFoundException {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        String path = packageName.replace(".", "/");
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = contextClassLoader.getResources(path);
        while (resources.hasMoreElements()) {
            URL url = resources.nextElement();//file:/C:/Users/root/Desktop/study/mini_springMvc/target/classes/com/dy/minispringMvc
            //只對jar包進行處理
            if (url.getProtocol().contains("jar")) {
                JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                String jarPath = jarURLConnection.getJarFile().getName();//拿到jar包絕對路徑  C:\Users\root\Desktop\study\mini_springMvc\target\mini_springMvc-1.0-SNAPSHOT-shaded.jar
                classList.addAll(getClassFromJar(jarPath, path));
            } else {
                //其他暫時不做處理
            }
        }
        return classList;
    }

    private static List<Class<?>> getClassFromJar(String jarPath, String path) throws IOException, ClassNotFoundException {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        JarFile jarFile = new JarFile(jarPath);
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            String name = jarEntry.getName();//com/dy/minispringMvc/core/CustomClassLoader.class  這個jar包下的資源路徑
            //如果是class文件,並且是我們建的class,非依賴
            if (name.startsWith(path) && name.endsWith(".class")) {
                //去掉.class後綴
                String className = name.replace("/", ".").substring(0, name.length() - 6);//com.dy.minispringMvc.core.CustomClassLoader
                classList.add(Class.forName(className));
            }
        }
        return classList;
    }
}

代碼很簡單就是通過類的全限定名稱,拿到當前jar包下所有java.lang.Class對象,只要知道java.lang.Class是什麼就行,代碼上方註解已經有說明。

我們拿到所有java.lang.Class對象就可以爲所欲爲了。

3、創建框架基礎容器,創建CustomBeanfactory,這裏需要說一下控制反轉(IOC)和依賴注入(DI)的區別,控制反轉通俗來說就是對Bean的控制交給框架去管理,是一種思想。而依賴注入則是一種手段,讓Bean的創建去依賴容器,從容器當中拿Bean。好處就是只需要new一次,如果需要就去容器當中拿即可。

首先來看幾個自定義註解:
注意:controller也是一個特殊的Bean

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CustomAutoWired {

}

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

}

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

}

定義好了註解我們才能知道哪些類需要交給容器管理
CustomBeanfactory:


public class CustomBeanfactory {
    private static Map<Class<?>, Object> beanMap = new ConcurrentHashMap<Class<?>, Object>();

    public static Object getBean(Class<?> cla) {
        return beanMap.get(cla);
    }

    public static void ininBean(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
        List<Class<?>> classList = new ArrayList<Class<?>>(classes);
        while (!classList.isEmpty() && classList.size() > 0) {
            //記錄一下需要掃描的總數
            int size = classList.size();

            for (int i = 0; i < classList.size(); i++) {
                if (crateBean(classList.get(i))) {
                    classList.remove(i);
                }
            }
            //如果出現相互依賴的情況,則拋出一個異常
            if (size == classList.size()) {
                throw new RuntimeException("創建失敗,暫未解決相互依賴問題");
            }
        }
    }

    private static boolean crateBean(Class<?> cla) throws IllegalAccessException, InstantiationException {
        //如果是不需要處理的類,則直接刪除
        if (!cla.isAnnotationPresent(CustomBean.class) && !cla.isAnnotationPresent(CustomController.class)) {
            return true;
        }
        Object obj = cla.newInstance();
        Field[] declaredFields = cla.getDeclaredFields();
        //設置需要創建的對象的屬性
        for (Field field : declaredFields) {
            //如果是CustomAutoWired註解修飾的屬性,則從我們的BeanFactory取,取不到則返回false,因爲while循環,會在後面循環創建成功
            //找當前的依賴,當依賴創建好以後,直接從beanMap取即可。
            if (field.isAnnotationPresent(CustomAutoWired.class)) {
                Class<?> fieldType = field.getType();
                Object fieldObj = beanMap.get(fieldType);
                if (fieldObj == null) {
                    return false;
                }
                //將取到的依賴set到需要創建的bean中
                //大坑 這裏必須設置爲true,不然不能進行set
                field.setAccessible(true);
                field.set(obj, fieldObj);
            }
        }
        beanMap.put(cla, obj);
        return true;
    }
}

4、創建Mvc框架的核心Handle和HandlerManager管理類

同樣的先看幾個簡單註解
注意:這兩個註解和上面註解不同的是CustomRequestMapping用於方法上的註解,CustomResqusetParam註解是用於屬性上的註解。其中CustomResqusetParam的value屬性是參數的key值,目的是爲了方便用req.getParameter(“key”)取到請求參數,CustomRequestMapping的value則是映射的UrL

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomRequestMapping {
    String value();
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CustomResqusetParam {
    String value();
}

繼續使用我們CustomClassLoader.loadeClass(packName)方法拿到的所有Class對象爲所欲爲。

HandlerManager類,這個類主要是掃描出所有CustomController註解的類,把他變成一個Handler

**
 * 創建所有Url和handler的映射
 */
public class HandlerManager {
    private static Map<String, HandlerMapping> handlerMappingMap = new ConcurrentHashMap<String, HandlerMapping>();

    public static HandlerMapping getHandler(String url) {
        return handlerMappingMap.get(url);
    }

    public static void resolveMappingHandler(List<Class<?>> classList) {
        for (Class<?> cls : classList) {
            if (cls.isAnnotationPresent(CustomController.class)) {
                parseHandlerFromController(cls);
            }
        }
    }

    private static void parseHandlerFromController(Class<?> cla) {
        Method[] methods = cla.getMethods();
        for (Method method : methods) {
            if (!method.isAnnotationPresent(CustomRequestMapping.class)) {
                continue;
            }
            String url = method.getDeclaredAnnotation(CustomRequestMapping.class).value();
            List<String> parmasKey = new ArrayList<>();
            for (Parameter parameter : method.getParameters()) {
                if (!parameter.isAnnotationPresent(CustomResqusetParam.class)) {
                    continue;
                }
                parmasKey.add(parameter.getDeclaredAnnotation(CustomResqusetParam.class).value());
            }
            parmasKey.toArray();
            String[] args = parmasKey.toArray(new String[parmasKey.size()]);
            HandlerMapping handlerMapping = new HandlerMapping(url, method, cla, args);
            handlerMappingMap.put(url, handlerMapping);
        }

    }

}

HandlerMapping 類

public class HandlerMapping {
    private String url;
    private Method method;
    private Class<?> controller;
    //請求參數的key
    private String[] paramKey;

    private String[] paramValue;

    public HandlerMapping(String url, Method method, Class<?> controller, String[] args) {
        this.url = url;
        this.method = method;
        this.controller = controller;
        this.paramKey = args;
    }

    public boolean handler(HttpServletRequest req, HttpServletResponse res) throws InvocationTargetException, IllegalAccessException, IOException {
        paramValue=new String[paramKey.length];
        for (int i = 0; i < paramKey.length; i++) {
            paramValue[i] = req.getParameter(paramKey[i]);
        }
        Object bean = CustomBeanfactory.getBean(controller);
        Object response = method.invoke(bean, paramValue);
        res.getWriter().println(response.toString());
        return true;
    }
}

最後,改造一下我們的CustomDispatcherServlet類的service方法,讓這個類完成他的目的,分發

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        HttpServletRequest httpServletRequest= (HttpServletRequest) servletRequest;
        HandlerMapping handler = HandlerManager.getHandler(httpServletRequest.getRequestURI());
       if (handler!=null){
           HttpServletResponse httpServletResponse= (HttpServletResponse) servletResponse;
           try {
               handler.handler(httpServletRequest,httpServletResponse);
           } catch (InvocationTargetException e) {
               e.printStackTrace();
           } catch (IllegalAccessException e) {
               e.printStackTrace();
           }
       }
    }

下面是main方法代碼:


public class MiniSpringMvcApp {
    public static void main(String[] args) throws LifecycleException, IOException {
        System.out.println("打包");
        System.out.println(MiniSpringMvcApp.class.getPackage().getName());
        try {
            List<Class<?>> classList = CustomClassLoader.loadeClass(MiniSpringMvcApp.class.getPackage().getName());
            CustomBeanfactory.ininBean(classList);
            HandlerManager.resolveMappingHandler(classList);
        } catch (Exception e) {
            e.printStackTrace();
        }

        TomcatServer tomCatServer=new TomcatServer(args);
        tomCatServer.startServer();
    }
}

注意:測試時一定要打成jar包後測試,註解使用方法和SpringMvc框架一樣,請自行測試

本文章只是仿寫SpringMvc框架的第一篇,後續會增加Aop、HandlerInterceptor、Valid、掃描xml文件等功能。

gitHup地址:https://github.com/dengyu123456/mini_springMvc.git

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