手寫一個簡單到SpirngMVC框架

  spring對於java程序員來講,無疑就是吃飯到筷子。在每次編程工做到時候,咱們幾乎都離不開它,相信不管過去,仍是如今或是將來到一段時間,它仍會扮演着重要到角色。本身對spring有必定的自我看法,因此參考網上的視頻和文章,整理出一套簡單的SpirngMVC。java

 

  項目地址先貼出來,接下來大概講下流程。git

  手寫簡單的SpringMvc框架。程序員

 

  主要分爲幾個步驟:github

  1. 掃描包下面的文件。web

  2. 根據掃描到到文件,初始化bean工廠。spring

  3. 根據@Controller @RequestMapping 註解,處理映射關係。apache

  4.  啓動tomcat。編程

 

1. 項目基於maven,framework模塊是主要對SpringMVC框架,test只是爲了方便測試,單獨引用framework框架進行測試 。api

 

 2.接下來說下主要framework模塊。圖以下:tomcat

(1)MiniApplication爲框架的入口類。其主要有如下四個功能。

package com.chan.starter;

import com.chan.beans.BeanFactory;
import com.chan.core.ClassScanner;
import com.chan.web.handler.HandlerManage;
import com.chan.web.server.TomcatServer;
import org.apache.catalina.LifecycleException;

import java.io.IOException;
import java.util.List;

/**
 * @description: 項目的入口
 * @author: Chen
 * @create: 2019-06-23 14:22
 **/
public class MiniApplication {

    public static void run(Class<?> clz,String[] agrs){
        try {
            //根據傳進來的clz所在的包取掃描包下的數據
            List<Class<?>> classList = ClassScanner.scanClasses(clz.getPackage().getName());
            try {
                //根據掃描到到文件,初始化bean工廠。
                BeanFactory.init(classList);
                //根據@Controller @RequestMapping 註解,處理映射關係。
                HandlerManage.resolveMappingHandleList(classList);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            //啓動tomcat
            TomcatServer tomcatServer = new TomcatServer(agrs);
            tomcatServer.startServer();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }

}

(2)功能一  掃描包下的文件。

package com.chan.core;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @description:類掃描器
 * @author: Chen
 * @create: 2019-07-02 22:29
 **/
public class ClassScanner {

    /**
     * 掃描包下的文件並返回。  
     * 若是是jar包,則取jar的文件。若是是文件夾,這遞歸取
     * @param packegeName
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static List<Class<?>> scanClasses(String packegeName) throws IOException, ClassNotFoundException {

        List<Class<?>> classList = new ArrayList<>();
        String path = packegeName.replace(".","/");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = classLoader.getResources(path);
        while (resources.hasMoreElements()){

            URL resource = resources.nextElement();
            //若是資源是jar包,那麼遍歷jar裏面到文件
            if (resource.getProtocol().contains("jar")){
                JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
                String jarFilePath = jarURLConnection.getJarFile().getName();
                classList.addAll(getClassesFromJar(path,jarFilePath));
            }
            //是文件的話,遞歸取的文件
            else if (resource.getProtocol().contains("file")){
                File dir = new File(resource.getFile());
                for (File file:dir.listFiles()){
                    if (file.isDirectory()){
                        classList.addAll(scanClasses(packegeName + "." + file.getName()));
                    }else {
                        String className =packegeName +"." +file.getName().replace(".class", "");
                        classList.add(Class.forName(className));
                    }
                }

            }

        }
        return classList;

    }

    private static List<Class<?>> getClassesFromJar(String path, String jarFilePath) throws IOException, ClassNotFoundException {

        List<Class<?>> classList = new ArrayList<>();
        JarFile jarFile = new JarFile(jarFilePath);
        Enumeration<JarEntry> jarEntrys = jarFile.entries();
        while (jarEntrys.hasMoreElements()){
            JarEntry jarEntry = jarEntrys.nextElement();
            String name = jarEntry.getName();
            if (name.startsWith(path)&&name.endsWith(".class")){
                String classFullName = name.replace("/", ".").substring(0, name.length() - 6);
                classList.add(Class.forName(classFullName));
            }
        }

        return classList;

    }

}

 

(3) 根據掃描到到文件,初始化bean工廠。

package com.chan.beans;

import com.chan.web.mvc.Controller;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @description: bean 工廠
 * @author: Chen
 * @create: 2019-07-03 00:03
 **/
public class BeanFactory {

    /**
     * 配置bean容器 存放bean
     */
    private static Map<Class<?>,Object> beanMap = new ConcurrentHashMap<>();

    /**
     * 根據類  獲取bean
     * @param clz
     * @return
     */
    public static Object getBean(Class<?> clz){return beanMap.get(clz);}

    /**
     * 根據掃描到到類  依次按照規則注入到bean容器中
     * @param classList
     * @throws Exception
     */
    public static void init(List<Class<?>> classList) throws Exception {

        List<Class<?>> toCreate = new ArrayList<>(classList);

        /**
         * 將知足注入條件循環注入bean容器
         */
        while (toCreate.size()!=0){

            int remainSize = toCreate.size();

            for (int i=0;i<toCreate.size();i++){
                if (finishCreate(toCreate.get(i))){
                    toCreate.remove(i);
                }
            }

            if (remainSize==toCreate.size()){
                throw new Exception("cycle dependency");
            }

        }

    }


    /**
     * 判斷是不是須要注入到bean
     * 若是不是 直接返回true  而且添加到bean容器
     * 若是是,可是當時不知足注入條件  返回false  等到下次循環再調用
     * 直至知足條件,添加到bean容器中
     * @param clz
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private static boolean finishCreate(Class<?> clz) throws IllegalAccessException, InstantiationException {

        if (!clz.isAnnotationPresent(Controller.class)&&!clz.isAnnotationPresent(Bean.class)){
            return true;
        }

        Object bean = clz.newInstance();
        for (Field field : clz.getDeclaredFields()){
            if (field.isAnnotationPresent(AutoWired.class)){
                Class<?> fieldType = field.getType();
                Object filedTypeObj = BeanFactory.getBean(fieldType);
                if (filedTypeObj==null){
                    return false;
                }

                field.setAccessible(true);
                field.set(bean,filedTypeObj);
            }
        }

        beanMap.put(clz,bean);
        return true;
    }

}

 

(4)根據@Controller @RequestMapping 註解,處理映射關係。 

package com.chan.web.handler;

import com.chan.web.mvc.Controller;
import com.chan.web.mvc.RequestMapping;
import com.chan.web.mvc.RequestParam;
import com.sun.glass.events.mac.NpapiEvent;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

/**
 * @description: 映射處理管理中心
 * @author: Chen
 * @create: 2019-07-03 00:34
 **/
public class HandlerManage {

    /**
     * 保存須要映射的列表
     */
    public static List<MappingHandle> mappingHandleList = new ArrayList<>();

    public static void resolveMappingHandleList(List<Class<?>> classList){

        for (Class<?> clz :classList){

            if (clz.isAnnotationPresent(Controller.class)){
                parseHandlerFromController(clz);
            }

        }

    }

    private static void parseHandlerFromController(Class<?> clz) {

        Method[] methods = clz.getMethods();

        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[] args = paramNameList.toArray(new String[paramNameList.size()]);
            MappingHandle mappingHandle = new MappingHandle(uri,method,clz,args);
            HandlerManage.mappingHandleList.add(mappingHandle);
        }

    }

}

 

(5)配置DispatcherServlet啓動tomcat。

package com.chan.web.server;

import com.chan.util.YmlUtil;
import com.chan.web.servlet.DispatcherServlet;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;

import javax.servlet.Servlet;

/**
 * @description:根據 tomcat 的包進行處理
 * @author: Chen
 * @create: 2019-06-23 14:58
 **/
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(YmlUtil.get("server.port"));
        tomcat.start();

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

        DispatcherServlet servlet = new DispatcherServlet();
        Tomcat.addServlet(context, "dispatcherServlet", servlet).setAsyncSupported(true);
        context.addServletMappingDecoded("/", "dispatcherServlet");
        tomcat.getHost().addChild(context);

        Thread awaitThread = new Thread("tomcar-await-thread"){
            @Override
            public void run() {
                TomcatServer.this.tomcat.getServer().await();
            }
        };
        awaitThread.setDaemon(false);
        awaitThread.start();
    }

}
package com.chan.web.servlet;

import com.chan.web.handler.HandlerManage;
import com.chan.web.handler.MappingHandle;

import javax.servlet.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

/**
 * @description: serlet適配器,全部的url都會進入到此處,
 * 在此處根據@RequestMaping所映射到方法進行操做。
 * @author: Chen
 * @create: 2019-06-23 15:15
 **/
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 {

        System.out.println("HandlerManage.mappingHandleList:"+HandlerManage.mappingHandleList.size());

        for (MappingHandle mappingHandle : HandlerManage.mappingHandleList){
            try {
                if (mappingHandle.handle(req,res)){
                    return;
                }else {
                    System.out.println("false");
                }
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }

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

    @Override
    public void destroy() {

    }
}
package com.chan.web.handler;

import com.chan.beans.BeanFactory;
import org.apache.catalina.servlet4preview.http.HttpServletRequest;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @description: 映射處理類
 * @author: Chen
 * @create: 2019-07-03 00:35
 **/
public class MappingHandle {

    String uri;

    Method method;

    Class<?> controller;

    String[] agrs;

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

    /**
     * 將掃描到到RequestMapping的路徑  進行處理
     * 經過反射調用 調用該方法
     * @param req
     * @param res
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IOException
     */
    public boolean handle(ServletRequest req, ServletResponse res) throws InvocationTargetException, IllegalAccessException, IOException {

        String requestURI = ((HttpServletRequest)req).getRequestURI();

        System.out.println("uri:"+uri);
        System.out.println("requestURI:"+requestURI.substring(1));
        if (!uri.equals(requestURI.substring(1))){
            return false;
        }

        String[] params = new String[agrs.length];
        for (int i =0;i< params.length;i++){
            params[i] = req.getParameter(agrs[i]);
        }

        Object obj = BeanFactory.getBean(controller);
        System.out.println(obj);
        if (obj==null){
            return false;
        }

        Object ret = method.invoke(obj,params);
        System.out.println("ret:"+ret.toString());
        res.getWriter().println(ret.toString());
        return true;
    }

}

 

 

3.測試framework模塊。

 

(1) application.yml 配置項目啓動的端口號。

(2)Application項目入口。

(3)controller 控制層

(4)service 業務層,在這裏注入類bean。   

這裏和springboot相似,這只是簡單Spirng框架的大概思路流程。

在尾巴處再次貼上項目手寫簡單的SpringMvc框架。

相關文章
相關標籤/搜索