Simple implementation of SpringMvc handwriting - MVC conclusion

After the above two articles, you should have a general understanding of the principle of spring (the code is not important, but the focus is on the idea).
spring is implemented, so we have to mention MVC architecture.

MVC architecture

How to design the structure of a program is a special knowledge, called "architectural pattern", which belongs to the programming methodology. MVC mode is a kind of architecture mode:

Model (business model): data and business rules. One M can provide data to multiple V, reuse M and reduce code repeatability.
View: the interface that users see and interact with.
Controller (service controller): receives the user's input and calls M and V to complete the user's requirements.

These three should not be strange. Let's write it today.

Simple implementation of MVC handwriting

1. Preparation in advance

1.1 code structure change (Continued)

1.2 you need to configure tomcat startup by yourself. You need to rely on servlet s. Post the pom file here

<groupId>com.deno</groupId>
<artifactId>springmvc-demo</artifactId>
<packaging>war</packaging>
<version>1.0</version>

<dependencies>
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>servlet-api</artifactId>
		<version>2.4</version>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.16.10</version>
	</dependency>
</dependencies>

1.3 anyone who knows tomcat must know the web xml

<?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">
	<display-name>test web spring</display-name>
	<servlet>
		//servlet name
		<servlet-name>TestSpringMvc</servlet-name>
		//servlet implementation class, here we implement it ourselves
		<servlet-class>com.xxx.framework.springmvc.TDispatcherServlet</servlet-class>
		//init methods and parameters
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:application.properties</param-value>
		</init-param>
		//Start load times
		<load-on-startup>1</load-on-startup>
	</servlet>
	//Intercept path configuration
	<servlet-mapping>
		<servlet-name>TestSpringMvc</servlet-name>
		//Intercept request path
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

1.4 TRequestMapping and TRequestParam annotations

copy directly, not posted here

1.5 configuration files and layouts directory

  • New configuration file

#MVC front end page directory
htmlRootTemplate=layouts

  • 404 and 500 pages
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>I went to Mars</title>
</head>
<body>
    <font size='30' color='red'>404 Not Found</font>
</body>
</html>

//500 page, using #{} instead of ${} value
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Server error</title>
</head>
<body>
    <font style="font-size: 18px" color='blue'>500 The server seems a little tired and needs a rest<br/>
    msg: #{msg}<br/>
    error: #{error}<br/>
    </font>
</body>
</html>
  • index.html uses #{} instead of ${} value
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<font color="red" style="font-size: 30px">
    welcome to #{name}<br>
    current time : #{date}<br>
    token: #{token}<br>
</font>
</body>
</html>

2. servlet initialization phase TDispatcherServlet

Now start the step-by-step analysis. Since it inherits HttpServlet, it must override init() by this class.

2.1 init() configuration resolution

public class TDispatcherServlet extends HttpServlet {
	//context
    TestApplicationContext context;
    //The Controller spring corresponding to the request path is actually a list. Here, map is simplified
    Map<String, Handler> handlerMapping = new HashMap<String, Handler>();
    TView view;

    @Override
    public void init(ServletConfig config) throws ServletException {
        //The context IOC DI phase has been written in the previous article, and it is called directly here
        this.context = new TestApplicationContext(
                config.getInitParameter("contextConfigLocation"));
				
		//Spring MVC actually initializes nine major components, which will be revealed later
				
        //Initialize handlermapping & parameter resolution
        initHandlerMapping();

        //Initializes the htmlRootPath that the view converter passes in the configuration
        initViewResolvers(context.getContextConfig().getProperty("htmlRootTemplate"));

        System.out.println("spring mvc is init");
    }
}

2.2 initializing mapping initHandlerMapping

private void initHandlerMapping() {
    //Load Controller layer
    for (Map.Entry<String, Object> entry : this.context.getIoc().entrySet()) {
        Object instance = entry.getValue();
        Class<?> clazz = instance.getClass();
        if (!clazz.isAnnotationPresent(TController.class)) continue;

        //Get root path
        String baseUrl = "";
        if (clazz.isAnnotationPresent(TRequestMapping.class)) {
            TRequestMapping annotation = clazz.getAnnotation(TRequestMapping.class);
            baseUrl = annotation.value();
        }
        //Get paths on all methods
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (!method.isAnnotationPresent(TRequestMapping.class)) continue;

            TRequestMapping annotation = method.getAnnotation(TRequestMapping.class);
            String reqUrl = (baseUrl + "/" + annotation.value()).replaceAll("/+", "/");
            if (this.handlerMapping.containsKey(reqUrl)) {
                throw new RuntimeException("[" + reqUrl + "] the handlerMapping is exists");
            }
			//Save to map
            this.handlerMapping.put(reqUrl, new Handler(instance, method, reqUrl));
            System.out.println("mapping add to: " + reqUrl);
        }
    }
}
  • Handler records class information and parameter information
@Data
public class Handler {

    Object target;  //Instance class
    Method method;  //method
    String url;     //Request path
    Class<?>[] parameterTypes; //Parameter type
    Map<String,Integer> mapIndex;   //Parameter location


    public Handler(Object target, Method method, String reqUrl) {
        this.target = target;
        this.method = method;
        this.url = reqUrl;
        this.parameterTypes = method.getParameterTypes();
        doHanlerParam(method);
    }

    //Initialize the parameter map and record the parameter key and position
    private void doHanlerParam(Method method) {
        this.mapIndex = new HashMap<String, Integer>();
        //Deal with the location of req and resp first
        for (int i = 0; i < this.parameterTypes.length; i++) {
            Class<?> type = this.parameterTypes[i];
            if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
                this.mapIndex.put(type.getName(),i);
            }
        }

        //Location of processing parameters (according to comments)
        Annotation[][] pas = method.getParameterAnnotations();
        for (int k = 0; k < pas.length; k++) {  //If you use forEach, you need to use Map to store parameter locations
            Annotation[] pa = pas[k];
            for (int i = 0; i < pa.length; i++) {
                Annotation annotation = pa[i];
                if (annotation instanceof TRequestParam) {
                    String key = ((TRequestParam) annotation).name();
                    this. mapIndex.put(key,k);
                }
            }
        }
    }
}

2.3 initialize view converter

private void initViewResolvers(String htmlRootTemplate) {
   //Load html page directory
   URL url = this.getClass().getClassLoader().getResource(htmlRootTemplate);

   //Encapsulate into view and provide rendering method in view
   this.view = new TView(url.getFile());
}
  • View saves htmlRootPath and provides rendering method render()
@Data
public class TView {

    String htmlPath;

    public TView(String htmlPath) {
        this.htmlPath = htmlPath;
    }


    public void render(Map<String, Object> modelMap, String htmlName, HttpServletResponse resp) throws Exception {
        StringBuilder builder = new StringBuilder();
        //Get the path of html
        File viewFile = new File(this.htmlPath + "/" + htmlName
                + (htmlName.endsWith(".html") ? "" : ".html"));
        //Read line
        RandomAccessFile accessFile = new RandomAccessFile(viewFile, "r");
        String line;
        while (null != (line = accessFile.readLine())) {
            line = new String(line.getBytes("ISO-8859-1"), "utf-8");
            //Regular matching #{} of fields
            Pattern pattern = Pattern.compile("#\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(line);
            while (matcher.find()) {
                String paramName = matcher.group();
                //#{name}
                paramName = paramName.replaceAll("#\\{|\\}", "");
                //Remove and replace from map
                Object paramValue = modelMap.get(paramName);
                if (paramValue != null) {
                    line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
                } else {
                    line = matcher.replaceFirst("null");
                }
                matcher = pattern.matcher(line);
            }
            builder.append(line);
        }
        resp.setCharacterEncoding("utf-8");
        resp.getWriter().write(builder.toString());
    }

    //Handling special characters
    public static String makeStringForRegExp(String str) {
        return str.replace("\\", "\\\\").replace("*", "\\*")
                .replace("+", "\\+").replace("|", "\\|")
                .replace("{", "\\{").replace("}", "\\}")
                .replace("(", "\\(").replace(")", "\\)")
                .replace("^", "\\^").replace("$", "\\$")
                .replace("[", "\\[").replace("]", "\\]")
                .replace("?", "\\?").replace(",", "\\,")
                .replace(".", "\\.").replace("&", "\\&");
    }
}

3.servlet call phase TDispatcherServlet

When using servlet s, you must be familiar with doGet and doPost

3.1 rewrite methods doGet and doPost

@Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     this.doPost(req, resp);
 }

 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     try {
         //call
         doDispatcher(req, resp);
     } catch (Exception e) {
         try {
             if ("404".equals(e.getMessage())) {
                 this.view.render(null, "404", resp);
             } else {
                 Map<String, Object> modelMap = new HashMap<String, Object>();
                 modelMap.put("msg", e.getMessage());
                 modelMap.put("error", Arrays.toString(e.getStackTrace()));
                 this.view.render(modelMap, "500", resp);
             }
         } catch (Exception ex) {
             ex.printStackTrace();
         }

     }
 }

3.2 calling and processing doDispatcher

private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    //Get the handler that stores the Controller class according to the request path
    Handler handler = getHandler(req, resp);
    //Analytical parameters
    Object[] objects = getParam(req, resp, handler);

    //Reflection call
    Object result = handler.getMethod().invoke(handler.getTarget(), objects);

    if (null == result || result instanceof Void) return;
    //Return to page
    if (result instanceof TModelAndView) {
        TModelAndView mv = (TModelAndView) result;
        this.view.render(mv.getModel(), mv.getHtmlName(), resp);
    } else {
        //Solve the Chinese garbled code displayed on the front page
        resp.setHeader("Content-Type", "text/html; charset=utf-8");
        resp.getWriter().write(result.toString());
    }
}
  • getHandler get Handler
private Handler getHandler(HttpServletRequest req, HttpServletResponse resp) {
    String uri = req.getRequestURI();
	//Get the nonexistence from the mapping map and return 404
    if (this.handlerMapping.containsKey(uri)) {
        return this.handlerMapping.get(uri);
    }
    throw new RuntimeException("404");
}

3.3 parameter processing

private Object[] getParam(HttpServletRequest req, HttpServletResponse resp, Handler handler) {
     //Parameter type and subscript position of parameter
     Class<?>[] parameterTypes = handler.getParameterTypes();
     Map<String, Integer> mapIndex = handler.getMapIndex();

     //Incoming parameters
     Map<String, String[]> parameterMap = req.getParameterMap();

     Object[] params = new Object[mapIndex.size()];
     //Get parameters according to subscript position
     for (Map.Entry<String, Integer> entry : mapIndex.entrySet()) {
         String key = entry.getKey();
         Integer index = entry.getValue();
         if (key.equals(HttpServletRequest.class.getName())) {
             params[index] = req;
         }
         if (key.equals(HttpServletResponse.class.getName())) {
             params[index] = resp;
         }
         //Only one parameter with the same name is considered in this version
         if (parameterMap.containsKey(key)) {
             String value = Arrays.toString(parameterMap.get(key)).replaceAll("\\[|\\]", "");
             //Convert parameters according to an array of parameter types
             params[index] = converter(value, parameterTypes[index]);
         }
     }
     return params;
 }

 private Object converter(String value, Class<?> type) {
     if (type == Integer.class) {
         return Integer.valueOf(value);
     } else if (type == Double.class) {
         return Double.valueOf(value);
     }
	//Don't write anything else
     return value;
 }

4. Call test

Package, configure tomcat, add @ TRequestMapping("/mvc") to HelloAction
Start log output

application is init
mapping add to: /mvc/index.html
mapping add to: /mvc/hello1
mapping add to: /mvc/hello2
spring mvc is init

4.1 normal response write return

@TRequestMapping("/hello1")
public void hello1(@TRequestParam(name = "name") String name, HttpServletResponse response) {
    try {
        response.setHeader("Content-Type","text/html; charset=utf-8");
        response.getWriter().write(helloService.sayHello(name));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Request http://localhost:8080//mvc/hello1?name= Wang Er Ma Zi

my name is Wang Er Ma Zi

4.2 direct return string

@TRequestMapping("/hello2")
public String hello2(@TRequestParam(name = "a") Integer a, @TRequestParam(name = "b") Integer b) {
    return "a + b = " + (a + b);
}

request http://localhost:8080/mvc/hello2?a=28&b=232

a + b = 260

4.3 return to html page

@TRequestMapping("/index.html")
public TModelAndView index(@TRequestParam(name = "name") String name) {
    Map<String, Object> modelMap;
    try {
        String time = helloService.getDataTime(name);
        modelMap = new HashMap<String, Object>();
        modelMap.put("name", name);
        modelMap.put("date", time);
        modelMap.put("token", UUID.randomUUID());
        return new TModelAndView("index", modelMap);
    } catch (Exception e) {
        modelMap = new HashMap<String, Object>();
        modelMap.put("msg", "Server request exception");
        modelMap.put("error", Arrays.toString(e.getStackTrace()));
        return new TModelAndView("500", modelMap);
    }
}

request http://localhost:8080/mvc/index.html?name= Beijing

Title welcome to Beijing
Current time: 11:30:33, December 14, 2021
token: af3efd56-7a13-4dd4-8ce1-a1327ab80fc4

4.4 service exception section log and 500 pages

Add an error code to the service

public String getDataTime(String name) {
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String date = format.format(new Date());
    System.out.println("Get current time:" + date + ",name=" + name);
 	int i =1/0;     //Test section exception and return exception model
    return date;
}

request http://localhost:8080/mvc/index.html?name= Beijing

  • First look at the html page and return
  • Java console log printing
application is init
mapping add to: /mvc/index.html
mapping add to: /mvc/hello2
mapping add to: /mvc/hello1
spring mvc is init
[2021-12-14 11:34:17,868] Artifact springmvc-demo:war: Artifact is deployed successfully
[2021-12-14 11:34:17,868] Artifact springmvc-demo:war: Deploy took 698 milliseconds
 Call before method:JoinPoint(target=null, method=null, args=[Beijing], result=null, throwName=null)
Get current time:2021-12-14 11:34:22,name=Beijing
 An unexpected call occurred:JoinPoint(target=null, method=null, args=[Beijing], result=null, throwName=/ by zero)

The handwritten content of SpringMvc has been completed here, which basically realizes the main function. Of course, it is only for reference. The next article starts with the analysis of the source code.

This article is a learning record and cannot be reproduced

Finally Spring MVC simple handwritten Github address

Previous: Simple implementation of SpringMvc handwriting - AOP switching programming
Next: Spring source code analysis first bullet - IOC control inversion analysis

Black hair doesn't know how to study early, white hair regrets reading late

Keywords: Java Spring Design Pattern mvc

Added by silverglade on Thu, 16 Dec 2021 11:11:55 +0200