Handwritten Spring mvc framework

Posted by wazo00 on Thu, 20 Jun 2019 18:14:48 +0200

Handwritten Spring mvc framework

1. Dependency Management Tools

Maven,Gradle

Advantage:

  • Automated Management Dependency (jar package)
  • Resolve Dependency Conflicts
  • Packaging (mvn clean package)

Characteristic:

  • Contract greater than configuration

    For example, src is the agreed code directory

  • The same project can have many modules, each built separately
  • Plug-in mechanism

Gradle has advantages over Maven:

  • Use json configuration
  • No installation required
2. Integrating tomcat with code
  1. Introducing jar packages ( Tomcat Embed Core)

  2. Code:

    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(6699);
            tomcat.start();
    
            Context context = new StandardContext();
            context.setPath("");
            context.addLifecycleListener(new Tomcat.FixContextListener());
    
            DispatcherServlet servlet = new DispatcherServlet();
            Tomcat.addServlet(context, "dispatcherServlet", servlet).setAsyncSupported(true);
            // '/': map all URLs
            context.addServletMappingDecoded("/", "dispatcherServlet");
            tomcat.getHost().addChild(context);
    
            Thread awaitThread = new Thread("tomcat_await_thread"){
                @Override
                public void run() {
                    //Internal class calls instance member variables of external class
                    //This line of code is blocking, is it supposed to keep tomcat off?
                    TomcatServer.this.tomcat.getServer().await();
                }
            };
            awaitThread.setDaemon(false);
            awaitThread.start();
        }
    }
3. Process of one request
  1. Front-end requests are sent to the port tomcat listens on via the http protocol, tomcat encapsulates the request into a ServletRequest object via decoding, deserialization, and so on, and then calls the service() of Dispatcher Servlet

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

    You can see that there is only one operation in the code: traversing the mappingHandlerList

  2. Match the request url to find the corresponding function to process

    The mappingHandler List holds the mappingHandler, which stores information such as the request path and the corresponding execution function.

    (ps: Because there are very few path s, the code does not use a map, but traverses directly)

    Let's look at the process of building a mappingHandler List, which first iterates through the classes annotated @controller, then the corresponding methods for @RequestMapping, and finally establishes the MappingHandler object and adds it to the list

    public class HandlerManager {
        public static List<MappingHandler> mappingHandlerList = new ArrayList<>();
     //This static method is called when the project is initialized
        public static void resolveMappingHandler(List<Class<?>> classList) {
            for (Class<?> cls : classList) {
                if (cls.isAnnotationPresent(Controller.class)) {
                    parseHandlerFromController(cls);
                }
            }
        }
    
        private static void parseHandlerFromController(Class<?> cls) {
            Method[] methods = cls.getDeclaredMethods();
            for (Method method : methods) {
                //Find functions with @RequestMapping added
                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);
            }
        }
    }

    Code defined by the @Controller,@RequestMapping,@RequestParam annotation is temporarily omitted.

    Okay, the request comes to the mappingHandler:

    public class MappingHandler {
        private String uri;
        private Method method;
        private Class<?> controller;
        private String[] args;   //This is the list of parameters with @RequestParam added
    
        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);
            //Write the returned results to the ServletResponse object
            res.getWriter().println(response.toString());
            return true;
        }
    
    
        MappingHandler(String uri, Method method, Class<?> cls, String[] args) {
            this.uri = uri;
            this.method = method;
            this.controller = cls;
            this.args = args;
        }
    }

The process ends when the ServletResponse object that has written the data is Byted to the front end

IV. Initialization of Projects

Why should I end up with initialization, which seems better understood?

Recall that when we use spring, we all have an Application class, which is the familiar thing below:

public class Application {
    public static void main(String[] args) {
        MiniApplication.run(Application.class, args);
    }
}

Let's take a look at MiniApplication.run(Application.class, args)

    public static void run(Class<?> cls, String[] args) {
        System.out.println("Hello mini-spring!");
        TomcatServer tomcatServer = new TomcatServer(args);
        try {
            //1. Start the server
            tomcatServer.startServer();
            //2. Scan class files in src
            List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
            //3. Initialize BeanFactory
            BeanFactory.initBean(classList);
            //4. Set up a mappingHandlerList
            HandlerManager.resolveMappingHandler(classList);
            //5. Print Class Information
            classList.forEach(it-> System.out.println(it.getName()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

The second step needs to be elaborated:

  • First of all, let's note that the parameter we passed in is cls.getPackage().getName()

    That is, only the class files under the package will be scanned, that is, why do we keep the Application.java file outermost

public class ClassScanner {
    public static List<Class<?>> scanClasses(String packageName) throws IOException, ClassNotFoundException {
        List<Class<?>> classList = new ArrayList<>();
        String path = packageName.replace(".", "/");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = classLoader.getResources(path);
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            if (resource.getProtocol().contains("jar")) {
                JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
                String jarFilePath = jarURLConnection.getJarFile().getName();
                classList.addAll(getClassesFromJar(jarFilePath, path));
            }else {
                // todo
            }
        }
        return classList;
    }

    private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();
        JarFile jarFile = new JarFile(jarFilePath);
        Enumeration<JarEntry> jarEntries = jarFile.entries();
        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            String entryName = jarEntry.getName();// com/mooc/zbs/test/Test.class
            if (entryName.startsWith(path) && entryName.endsWith(".class")) {
                String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6);
                classes.add(Class.forName(classFullName));
            }
        }
        return classes;
    }
}

The ability to access file resources with the help of classLoader

The specific code has not been read carefully, let's not say.

Topics: Javascript Tomcat Spring Maven Gradle