Handwritten spring MVC framework

Posted by SoccerGloves on Fri, 17 Dec 2021 15:02:46 +0100

1.Spring

The operation process of MVC is as follows.

 

Let's assume this scenario:

DispatcherServlet is the boss in the MVC scene, and he does it himself. He has to review and approve everything. That day, he received a user request asking him to give a web page.

He immediately gave his deputy handler mapping and said, "Xiao Liu, look at this job. Who is suitable for it?" When Xiao Liu looked at the employee roster, a Controller named Xiao Zhang was competent. Xiao Liu said to the leader, "Xiao Zhang can do it.".

At this time, the leader DispatcherServlet cannot directly find Xiao Zhang, because Xiao Zhang is only responsible for realizing specific business, and the user's requirements are too abstract. Xiao Zhang can't understand them. Someone needs to help him sort out what to do in the first step and what to do in the second step. At this time, the project manager HandlerAdaper went online. The leader found the project manager and said, "help Xiao Zhang manage this job. What should I do specifically?".

After the project manager finished dividing two from three, the leader took the handled task and handed it to Li Xiaozhang. Our Xiaozhang was also very competitive and finished it. Moreover, His project not only has business models, but also beautiful components (View), but Xiao Zhang's aesthetic is not very good, so he can't combine them together. Therefore, the leader DispatcherServlet went to the art student viewRsolver and asked her to render it. viewRsolver has excellent painting skills, and only a few strokes render a page with both business information and good-looking.

So far, a project has been completed, and the dispatcher servlet shows the results (JSP and other front-end pages) to the users. The users are satisfied and paid generously, so everyone has money to take

After reading the MVC process of Rod Johnson's spring MVC, we found that the components have a clear division of labor, perform their respective duties, and can complete many complex businesses. However, we just started, we certainly can't complete so many. Therefore, today we build a simple version, Only the leader (dispatcher servlet) and various salesmen, etc. the salesmen are still only responsible for specific business, and the leaders do all other work.

2. The process of our framework

In our process, DispatcherServlet leader = front-end controller + mapping processor

 3. Start building

1. New maven project

2. In POM Import dependency in XML

 

 <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.reflections</groupId>
      <artifactId>reflections</artifactId>
      <version>0.9.11</version>
    </dependency>

3. Project structure

 

4. Prepare configuration file

Write the configuration file in the resource directory:
applicationContext.properties: specify the scanning path package. Here we specify the package where the controller is located

package=com.yun.controller

5. Update web XML file

The skeleton is still version 2.0. We update it to version 4.0 here.

And register our leader mydispatcher servlet and specify the contextConfigLocation where the configuration file is located. Our leader does everything himself and asks him to intercept all requests here.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- Configure our own front-end controller, MyDispatcherServlet Just one servlet,Intercept requests sent by the front end-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>com.yun.servlet.MyDispatcherServlet</servlet-class>
<!--        Configure scan package-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>application.properties</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!-- Block all requests-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

6. User defined annotation

The function of annotation here is equivalent to adding a small tail to the class / Method. We identify different controllers and methods through different tails

We define two annotations

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ fileName:MyController
 * @ description:Imitate the @ controller in spring and act on the class to identify that the class is a controller
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {

    /**
     * It's useless, but in order to imitate the @ Controller in spring, we add it
     * Our simple version uses the default id: a lowercase class name
     */
    String value() default "";
}



import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ fileName:MyRequestMapping
 * @ description:Imitate @ RequestMapping, act on classes and methods, and specify the corresponding Controller and Method through url
 * @ createTime:2021/12/17 10:31
 * @ version:1.0.0
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    /**
     * @Author: zyk
     * @Description: The domain name can only have one segment, which can only be / controllerName/methodName
     * @Date: 2021/12/17 15:15
     */
    String value() default "";
}

The above is some preparatory work. If copying spring MVC is regarded as forming a team, the above work is equivalent to finding a workplace for the team. The following is the characterization of the characters. First, let's welcome our leader mydispatcher servlet

Write front-end controller

Write the front-end controller (a Servlet) and rewrite the init and service methods

MyDispatcherServlet

Overview

The whole process revolves around two rewritten methods, in which init() is the focus.

What MyDispatcherServlet wants to do is to look at the access address of the front end in a sentence: then call the matching processor (Controller) corresponding method (method).

To complete this, we need to bind a certain string to the Controller and method through annotation, and then analyze the string in the Url passed from the front end to find the same one, so as to complete the matching. Reflection plays a great role in this process. Many actions, such as finding the annotation on the class header or finding the value in the annotation, need reflection.

Specific process

code

Create a dispatcher servlet that inherits the HTTP servlet and overrides the two methods. (init() and service())

import com.yun.annotation.MyController;
import com.yun.annotation.MyRequestMapping;
import javafx.scene.effect.Reflection;
import org.reflections.Reflections;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @ fileName:MyDispatcherServlet
 * @ description:Front end controller, equivalent manager, handles distribution requests
 * Look at the access address of the front end, then call the matching processor (Controller) corresponding method (method).
 * @ author:zyk
 * @ createTime:2021/12/17 10:43
 * @ version:1.0.0
 */
public class MyDispatcherServlet extends HttpServlet {
    /**
     * 1.Configure scanned packages and put them into one In the properties file, it is read during initialization
     */
    private Properties properties = new Properties();
    /**
     * 2.You need a set to save all that can respond to the controller
     */
    private Set<Class<?>> classSet = new HashSet<>();
    /**
     * 3.springMVC container
     */
    private Map<String, Object> springMVCContext = new HashMap<>();
    /**
     * 4.The mapping processor stores all the methods
     */
    private Map<String, Method> handlerMapping = new HashMap<>();
    /**
     * 4.The mapping relationship of the back-end processor stores all controller s
     */
    private Map<String, Object> controllerMap = new HashMap<>();

    /**
     * @Author: zyk
     * 1.Load profile
     * 2.Scan controller package
     * 3.Initialize controller
     * 4.Initialize the Handler mapper (Handler = controller + method)
     * @Date: 2021/12/17 15:20
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1. Load the configuration file on the web Initialization parameter contextConfigLocation configured in XML
        String initParameter = config.getInitParameter("contextConfigLocation");
        try {
            loadConfigFile(initParameter);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //2. Scan controller package
        scanPackage(properties.getProperty("package"));
        //3. Initialize the controller
        initController();
        //4. Initialize the processor mapper
        initHandlerMapping();
    }

    /**
     * @Author: zyk
     * @Description: Methods of performing business
     * @Date: 2021/12/17 15:46
     * @Param: [req, resp]
     * @return: void
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //Processing requests
        if (handlerMapping.isEmpty()) {
            return;
        }
        //Get url
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        String url = uri.replace(contextPath, "");
        if (!handlerMapping.containsKey(url)) {
            resp.getWriter().println("404");
        } else {
            Method method = handlerMapping.get(url);
            Object controller = controllerMap.get(url);
            try {
                method.invoke(controller);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @ author: zyk
     * @ description:Load profile
     * @ date: 2021/12/17 10:51
     * Tool class
     */
    private void loadConfigFile(String fileName) {
        //Get resources by stream
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileName);
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @ author: zyk
     * @ description:Scan all classes with MyController annotations and encapsulate them into the set collection
     * @ date: 2021/12/17 10:52
     * Tool class
     */
    private void scanPackage(String packageName) {
        Reflections retentions = new Reflections(packageName);
        classSet = retentions.getTypesAnnotatedWith(MyController.class);
    }

    /**
     * @ author: zyk
     * @ description:Initialize controller
     * @ date: 2021/12/17 10:53
     * Tool class
     */
    private void initController() {
        if (classSet.isEmpty()) {
            return;
        }
        for (Class<?> controller : classSet) {
            try {
                springMVCContext.put(lowerFirstWord(controller.getSimpleName()), controller.newInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @ author: zyk
     * @ description:Initial to lowercase
     * @ date: 2021/12/17
     * Tool class
     */
    private String lowerFirstWord(String simpleName) {
        char[] array = simpleName.toCharArray();
        array[0] += 32;
        return String.valueOf(array);
    }

    /**
     * @ author: zyk
     * @ description:Initialize the mapping processor, (Handler = controller + method)
     * @ date: 2021/12/17 10:58
     */
    private void initHandlerMapping() {
        if (springMVCContext.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : springMVCContext.entrySet()) {
            //Get class object
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(MyController.class)) {
                continue;
            } else {
                String baseUrl = "";
                if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                    //If the class contains the annotation MyRequestMapping, get the annotation value
                    MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class);
                    baseUrl = annotation.value();
                }
                //Get all methods
                Method[] methods = aClass.getMethods();
                for (Method method : methods) {
                    // The judgment method contains the MyRequestMapping annotation
                    if (method.isAnnotationPresent(MyRequestMapping.class)) {
                        MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                        String url = annotation.value();
                        url = baseUrl + url;
                        //Put the method into the method set
                        handlerMapping.put(url, method);
                        try {
                            //Put into controllerMap
                            controllerMap.put(url, aClass.newInstance());
                        } catch (InstantiationException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

Finally, write a Controller to test

@MyRequestMapping(value = "/test")
@MyController
public class TestController {
    @MyRequestMapping(value = "/test1")
    public void test1() {
        System.out.println("test1 Called");
    }

    @MyRequestMapping(value = "/test2")
    public void test2() {
        System.out.println("test2 Called");
    }

    @MyRequestMapping(value = "/test3")
    public void test3() {
        System.out.println("test3 Called");
    }
}

Test screenshot:

Wrong address entered:

Enter the correct address:

 

The above is to completely rewrite the spring MVC framework.

Topics: Java Maven intellij-idea