天天看點

手寫一個簡單到SpirngMVC架構

前提:

spring對于java程式員來說,無疑就是吃飯到筷子。在每次程式設計工作到時候,我們幾乎都離不開它,相信無論過去,還是現在或是未來到一段時間,它仍會扮演着重要到角色。自己對spring有一定的自我見解,是以參考網上的視訊和文章,整理出一套簡單的SpirngMVC。

内容較多,項目位址先貼出來,接下來大概講下流程。

手寫簡單的SpringMvc架構。

主要分為幾個步驟:
  1. 掃描包下面的檔案。
  2. 根據掃描到到檔案,初始化bean工廠。
  3. 根據@Controller @RequestMapping 注解,處理映射關系。
  4. 啟動tomcat。
1. 項目基于maven,framework子產品是主要對SpringMVC架構,test隻是為了友善測試,單獨引用framework架構進行測試 。
手寫一個簡單到SpirngMVC架構
2.接下來講下主要framework子產品。圖如下:
手寫一個簡單到SpirngMVC架構
  • (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子產品。

  • application.yml 配置項目啟動的端口号。
  • Application項目入口。
  • controller 控制層
  • service 業務層,在這裡注入類bean。
    手寫一個簡單到SpirngMVC架構

這裡和springboot類似,這隻是簡單Spirng架構的大概思路流程。

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

如果文章對您有幫助,github給個start吧。