手寫SpringMVC,劍指優秀開源框架靈魂

由於Spring官方就是選擇gradle作爲自動化構建工具,所以我們在本次嘗試中就按照spring的選擇也是用gradle
在整個項目中,我們一共包含兩個模塊framework模塊用於首先實現我們springmvc的常見功能,test模塊則是用來測試我們手寫的模塊是否正確
項目鏈接:https://github.com/ZhangJia97/Mini-Spring


下面是項目結構,只保留了我們用到的文件結構

├── build.gradle
├── framework
│   ├── build.gradle
│   └── src
│       ├── main
│           ├── java
│               └── xyz
│                   └── suiwo
│                       └── imooc
│                           ├── beans
│                           │   ├── Autowired.java
│                           │   ├── Bean.java
│                           │   └── BeanFactory.java
│                           ├── core
│                           │   └── ClassScanner.java
│                           ├── starter
│                           │   └── MiniApplication.java
│                           └── web
│                               ├── handler
│                               │   ├── HandlerManager.java
│                               │   └── MappingHandler.java
│                               ├── mvc
│                               │   ├── Controller.java
│                               │   ├── RequestMapping.java
│                               │   └── RequestParam.java
│                               ├── server
│                               │   └── TomcatServer.java
│                               └── servlet
│                                   └── DispatcherServlet.java
└── test
    ├── build.gradle
    └── src
        ├── main
             ├── java
                └── xyz
                    └── suiwo
                        └── imooc
                            ├── Application.java
                            ├── controller
                            │   └── SalaryController.java
                            └── service
                                └── SalaryService.java


首先我們需要在framework的依賴中添加tomcat的依賴,因爲springboot就是通過加入tomcat依賴來實現的

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    // https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
    compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.23'
}

接下來讓我們看看如何去創建一個tomcat服務

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

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

    public void startServer() throws LifecycleException {
        tomcat = new Tomcat();
        tomcat.setPort(8080);

        Context context = new StandardContext();
        context.setPath("");
        context.addLifecycleListener(new Tomcat.FixContextListener());
        DispatcherServlet dispatcherServlet = new DispatcherServlet();

        // servlet註冊到tomcat容器內並開啓異步支持
        Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet).setAsyncSupported(true);

        context.addServletMappingDecoded("/", "dispatcherServlet");

        // 註冊到默認host容器
        tomcat.getHost().addChild(context);
        tomcat.start();

        Thread awaitThread = new Thread("tomcat_await_thread"){
            @Override
            public void run() {
                TomcatServer.this.tomcat.getServer().await();
            }
        };

        //設置成非守護線程
        awaitThread.setDaemon(false);
        awaitThread.start();
    }
}

然後我們可以看到上述代碼向tomcat中set了一個dispatchServlet用於處理請求,我們看看DispatchServlet如何去處理請求

public class DispatcherServlet implements Servlet {
    @Override
    public void init(ServletConfig config) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        for(MappingHandler mappingHandler : HandlerManager.mappingHandlerList){
            try {
                if(mappingHandler.handle(req, res)){
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

我們現在已經成功創建了一個Tomcat的服務類,下面我們就可以在主類中啓動tomcat服務了


然後我們看一些framework的主類

public class MiniApplication {
    public static void run(Class<?> cls, String[] args){
        System.out.println("Hello Mini-Spring!");
        // 創建一個Tomcat服務
        TomcatServer tomcatServer = new TomcatServer(args);
        try {
            // 啓動tomcat
            tomcatServer.startServer();

            // 掃描項目中當前cls目錄下的所有包
            List<Class<?>> classList = ClassScanner.scannerClass(cls.getPackage().getName());

            // 初始化所有bean
            BeanFactory.init(classList);

            // 初始化所有的MappingHandler
            HandlerManager.resolveMappingHandler(classList);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

我們再創建三個mvc相關的註解

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

然後我們看一下ClassScanner類,這個類主要用於掃描包

public class ClassScanner {
    public static List<Class<?>> scannerClass(String packageName) throws IOException, ClassNotFoundException {
        List<Class<?>> classList= new ArrayList<>();
        String path = packageName.replaceAll("\\.", "/");

        // 獲取默認類加載器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // 獲取資源文件的路徑
        Enumeration<URL> resources = classLoader.getResources(path);
        while(resources.hasMoreElements()){
            URL resource = resources.nextElement();

            // 判斷資源類型
            if(resource.getProtocol().contains("jar")){

                // 如果資源類型是jar包,則我們先獲取jar包的絕對路徑
                JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
                String jarFilePath = jarURLConnection.getJarFile().getName();

                // 獲取這個jar包下所有的類
                classList.addAll(getClassesFromJar(jarFilePath, path));
            }else {
                // todo 處理非jar包的情況
            }
        }
        return classList;
    }

    private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
        //初始化一個容器用於存儲類
        List<Class<?>> classes = new ArrayList<>();

        // 通過路徑獲取JarFile實例
        JarFile jarFile = new JarFile(jarFilePath);

        // 遍歷jar包,每個jarEntry都是jar包裏的一個文件
        Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
        while(jarEntryEnumeration.hasMoreElements()){
            JarEntry jarEntry = jarEntryEnumeration.nextElement();
            String entryName = jarEntry.getName();  // xyz/suiwo/imooc/test/Test.class
            if(entryName.startsWith(path) && entryName.endsWith(".class")){
                // 把分隔符換成點,並去除.class後綴
                String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6);
                classes.add(Class.forName(classFullName));
            }
        }
        return classes;
    }
}

作爲spring的經典ioc思想,初始化創建bean是重中之重,下面讓我們看看如何實現吧
對於常見與Bean相關的註解就是@Bean還有@Autowired
所以我們首先創建兩個註解

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

下面我們看看如何去初始化bean吧

public class BeanFactory {

    private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();

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

    public static void init(List<Class<?>> classList) throws Exception {
        List<Class<?>> toCreate = new ArrayList<>(classList);
        while (toCreate.size() > 0){
            int remainSize = toCreate.size();
            for(int i = 0; i < toCreate.size(); i++){
                // 返回true則說明創建成功或者說當前類不是一個bean
                // 返回false則此時可能存存在當前需要創建的bean的依賴還沒有創建所以暫時先跳過
                if(finishCreate(toCreate.get(i))){
                    toCreate.remove(i);
                }
            }
            // 如果數量沒有改變則說明出現了死循環,拋出異常
            if(toCreate.size() == remainSize){
                throw new Exception("死循環");
            }
        }
    }

    private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException {
        // 如果沒有滿足的註解,則直接返回true
        if(!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)){
            return true;
        }
        Object bean = cls.newInstance();
        for(Field field : cls.getDeclaredFields()){
            if(field.isAnnotationPresent(Autowired.class)){
                Class<?> fieldType = field.getType();
                Object reliantBean = BeanFactory.getBean(fieldType);
                
                // 如果爲空,則說明當前類中的字段所依賴的類還沒有注入,所以返回false,先跳過,等到所需要依賴注入之後再創建
                if(reliantBean == null){
                    return false;
                }
                
                field.setAccessible(true);
                field.set(bean, reliantBean);
            }
        }
        
        // 將創建好的bean放入容器中
        classToBean.put(cls, bean);
        return true;
    }
}

然後我們來看一下控制器,每一個MappingHandler都是一個請求映射器

public class MappingHandler {

    // 需要處理的uri
    private String uri;

    // 所對應的方法
    private Method method;

    // 所對應的方法
    private Class<?> controller;

    // 所需要的參數
    private String[] args;

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

    // 若與MappingHandler匹配成功,執行方法
    public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
        String requestUri = ((HttpServletRequest)req).getRequestURI();
        if(!uri.equals(requestUri)){
            return false;
        }
        Object[] parameters = new Object[args.length];
        for(int i = 0; i < args.length; i++){
            parameters[i] = req.getParameter(args[i]);
        }
        Object ctl = BeanFactory.getBean(controller);
        Object response = method.invoke(ctl, parameters);
        res.getWriter().println(response.toString());
        return true;
    }
}

我們在創建一個管理器去管理這些MappingHandler

public class HandlerManager {
    public static List<MappingHandler> mappingHandlerList = new ArrayList<>();

    // 把Controller類挑選出來,並將類中的帶有@RequestMapping方法初始化成MappingHandler
    public static void resolveMappingHandler(List<Class<?>> classList){
        for(Class<?> cls : classList){
            if(cls.isAnnotationPresent(Controller.class)){
                parseHandlerFromController(cls);
            }
        }
    }

    // 解析controller類
    private static void parseHandlerFromController(Class<?> cls) {
        Method[] methods = cls.getDeclaredMethods();
        for(Method method : methods){
            if(!method.isAnnotationPresent(RequestMapping.class)){
                continue;
            }
            String uri = method.getDeclaredAnnotation(RequestMapping.class).value();
            List<String> paramNameList = new ArrayList<>();
            for(Parameter parameter : method.getParameters()){
                if(parameter.isAnnotationPresent(RequestParam.class)){
                    paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
                }
            }
            String[] params = paramNameList.toArray(new String[paramNameList.size()]);
            MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params);
            HandlerManager.mappingHandlerList.add(mappingHandler);
        }
    }
}

至此,我們就已經成功的將整個框架大致完成了,對於test模塊中的代碼,我就不在這裏在書寫了,因爲和我們日常寫springboot業務相同只是爲了測試我們手寫框架的幾個功能。

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