手写一个简单到SpirngMVC框架

  spring对于java程序员来说,无疑就是吃饭到筷子。在每次编程工作到时候,我们几乎都离不开它,相信无论过去,还是现在或是未来到一段时间,它仍会扮演着重要到角色。自己对spring有一定的自我见解,所以参考网上的视频和文章,整理出一套简单的SpirngMVC。

 

  项目地址先贴出来,接下来大概讲下流程。

  手写简单的SpringMvc框架。

 

  主要分为几个步骤:

  1. 扫描包下面的文件。

  2. 根据扫描到到文件,初始化bean工厂。

  3. 根据@Controller @RequestMapping 注解,处理映射关系。

  4.  启动tomcat。

 

1. 项目基于maven,framework模块是主要对SpringMVC框架,test只是为了方便测试,单独引用framework框架进行测试 。

 

 2.接下来讲下主要framework模块。图如下:

(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框架。

 

posted @ 2019-07-05 00:15  那个男孩很坏  阅读(372)  评论(0编辑  收藏  举报