zoukankan      html  css  js  c++  java
  • 手写一个简单到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框架。

  • 相关阅读:
    网络
    oracle 操作
    oracle可重复执行脚本(添加字段)
    c#命名规范(转载)
    Oracle修改表结构字段名和字段长度
    C#Winform的DEV下拉下拉控件介绍
    TPL DataFlow初探(二)
    TPL DataFlow初探(一)
    C# Winform 跨线程更新UI控件常用方法汇总(多线程访问UI控件)
    C# 关于委托
  • 原文地址:https://www.cnblogs.com/boychen/p/11135916.html
Copyright © 2011-2022 走看看