Handwritten SpringMVC Framework

Posted by gmccague on Sat, 09 Nov 2019 19:41:16 +0100

Handwritten SpringMVC Framework

 

Smell the rose and have a tiger in the heart

Background: Spring must have been heard of by everyone, and Spring Boot and Spring Cloud frameworks may be more popular now; however, as a web (presentation) tier framework that implements the MVC design pattern, Spring MVC's high development efficiency and high performance are still the frameworks many companies are using; in addition, Spring source master level code specifications and design ideas are very strongIt's worth learning; to go one step further, there's a lot of Spring stuff at the bottom of the Spring Boot framework, and you'll often be asked Spring MVC principles at interviews. Ordinary people may just recite how Spring MVC works. If you ask whether you know its underlying implementation (code level), you'll probably quit, but if you can write the Spring MVC framework by hand.Interviewers will definitely be impressed, so handwriting SpringMVC is worth trying.

Before designing your own SpringMVC framework, you need to understand how it works.

1. SpringMVC Running Process

Figure 1. SpringMVC process

1. Users send requests to the server, which are captured by the Spring front-end controller Dispatcher Servlet;

2. Dispatcher Servlet calls HandlerMapping processor mapper after receiving the request;

3. Processor mapper parses the request URL to get the request resource identifier (URI); then, based on the URI, calls HandlerMapping to get all related objects (including handler objects and interceptors corresponding to handler objects) of the Handler configuration, and returns them to Dispatcher Servlet as HandlerExecutionChain objects;

4. Dispatcher Servlet selects an appropriate Handler Adapter through the Handler Adapter Processor Adapter based on the acquired Handler; (Note: If the Handler Adapter is successfully acquired, the preHandler(...) method of the interceptor will be executed at this time;

5. Extract the model data from Request, fill in Handler input, and start executing Handler (that is, Controller). [In the process of filling in Handler input, Spring will help you do some additional work, depending on your configuration, such as: HttpMessageConveter: convert request messages (such as Json, xml, etc.) into an object, and convert objects into specified response information; data conversion:Data conversion of request message, such as String to Integer, Double, etc; Data formatting: Data formatting of request message, such as string to formatted number or date; Data validation: Validation of data (length, format, etc.), Validation results stored in BindingResult or Error

6. The Controller execution finishes returning the ModelAndView object;

7. HandlerAdapter returns the controller execution result ModelAndView object to Dispatcher Servlet;

8. Dispatcher Servlet passes ModelAndView object to ViewReslover view parser;

9. ViewReslover chooses a suitable ViewResolver (which must be a ViewResolver registered in the Spring container) to return to Dispatcher Servlet based on the returned ModelAndView;

10. Dispatcher Servlet renders the View (i.e. fills the View with model data);

11. Dispatcher Servlet will render the result in response to the user (client).

2. Design Thought of SpringMVC Framework

1. Read Configuration Phase

Figure 2. SpringMVC inheritance relationship

The first step is to configure web.xml and load the custom ispatcher Servlet.As you can see from the diagram, SpringMVC is essentially a Servlet, which inherits from the HttpServlet. In addition, the FrameworkServlet is responsible for the container of the initial Spring MVC and sets the Spring container as the parent container. In order to read the configuration in web.xml, you need to use the ServletConfig class, which represents the configuration information of the current Servlet in web.xml and then passes through web.xmlLoad our own MyDispatcher Servlet and read the configuration file.

2. Initialization phase

The initialization phase implements the following steps in sequence in the Dispatcher Servlet class:

1. Load the configuration file;

2. Scan all files under the current project;

3. Get the scanned class, instantiate it through the reflection mechanism, and place it in the ioc container (Map key-value pair beanName-bean) beanName is lowercase by default;

4. Initialize the mapping of path and method;

5. Get the parameter passed in by the request and process the parameter. Reflect the call by taking out the method name corresponding to the url in the initialized handler Mapping.

3. Running Stage

In the run phase, each request will call the doGet or doPost Method, which will match the corresponding Method in the HandlerMapping according to the url request, then use the reflection mechanism to call the url corresponding Method in the Controller and get the result back.

3. Implementing the Spring MVC Framework

First, the younger brother SpringMVC framework only implements his own @Controller and @RequestMapping annotations. Other annotation functions are implemented in a similar way, with fewer annotations, so the project is relatively simple. You can see the following project files and catalog screenshots.

Figure 3. Project files and directories

1. Create a Java Web Project

Create a Java Web project, check the Web Application option under JavaEE, Next.

Figure 4. Creating a Java Web Project

2. Add the following configuration to web.xml under project WEB-INF

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
 5          version="4.0">
 6 
 7     <servlet>
 8         <servlet-name>DispatcherServlet</servlet-name>
 9         <servlet-class>com.tjt.springmvc.DispatcherServlet</servlet-class>
10     </servlet>
11     <servlet-mapping>
12         <servlet-name>DispatcherServlet</servlet-name>
13         <url-pattern>/</url-pattern>
14     </servlet-mapping>
15 
16 </web-app>

3. Create custom Controller annotations

 1 package com.tjt.springmvc;
 2 
 3 
 4 import java.lang.annotation.*;
 5 
 6 
 7 /**
 8  * @MyController Custom Annotation Class
 9  *
10  * @@Target(ElementType.TYPE)
11  * Indicates that the comment can be used on a class;
12  *
13  * @Retention(RetentionPolicy.RUNTIME)
14  * Indicates that the comment will exist in the class byte code file and can be retrieved at run time by reflection
15  *
16  * @Documented
17  * Mark a comment to indicate that a document can be generated
18  */
19 @Target(ElementType.TYPE)
20 @Retention(RetentionPolicy.RUNTIME)
21 @Documented
22 public @interface MyController {
23 
24     /**
25      * public class MyController
26      * Replace class with @interface This class becomes an annotation class
27      */
28 
29     /**
30      * Register Alias for Controller
31      * @return
32      */
33     String value() default "";
34     
35 }

4. Create custom RequestMapping annotations

 1 package com.tjt.springmvc;
 2 
 3 
 4 import java.lang.annotation.*;
 5 
 6 
 7 /**
 8  * @MyRequestMapping Custom Annotation Class
 9  *
10  * @Target({ElementType.METHOD,ElementType.TYPE})
11  * Indicates that the annotation can be used on methods and classes;
12  *
13  * @Retention(RetentionPolicy.RUNTIME)
14  * Indicates that the comment will exist in the class byte code file and can be retrieved at run time by reflection
15  *
16  * @Documented
17  * Mark a comment to indicate that a document can be generated
18  */
19 @Target({ElementType.METHOD, ElementType.TYPE})
20 @Retention(RetentionPolicy.RUNTIME)
21 @Documented
22 public @interface MyRequestMapping {
23 
24     /**
25      * public @interface MyRequestMapping
26      * Replace class with @interface This class becomes an annotation class
27      */
28 
29     /**
30      * Represents the url to access the method
31      * @return
32      */
33     String value() default "";
34 
35 }

5. Design an encapsulation tool class to get all the class files under the project project

  1 package com.tjt.springmvc;
  2 
  3 
  4 import java.io.File;
  5 import java.io.FileFilter;
  6 import java.net.JarURLConnection;
  7 import java.net.URL;
  8 import java.net.URLDecoder;
  9 import java.util.ArrayList;
 10 import java.util.Enumeration;
 11 import java.util.List;
 12 import java.util.jar.JarEntry;
 13 import java.util.jar.JarFile;
 14 
 15 /**
 16  * Get all Class tool classes from the project package
 17  */
 18 public class ClassUtils {
 19 
 20     /**
 21      * static const
 22      */
 23     private static String FILE_CONSTANT = "file";
 24     private static String UTF8_CONSTANT = "UTF-8";
 25     private static String JAR_CONSTANT = "jar";
 26     private static String POINT_CLASS_CONSTANT = ".class";
 27     private static char POINT_CONSTANT = '.';
 28     private static char LEFT_LINE_CONSTANT = '/';
 29 
 30 
 31     /**
 32      * Define private constructors to block implicit public constructors
 33      */
 34     private ClassUtils() {
 35     }
 36 
 37 
 38     /**
 39      * Get all Class es from the project package
 40      * getClasses
 41      *
 42      * @param packageName
 43      * @return
 44      */
 45     public static List<Class<?>> getClasses(String packageName) throws Exception {
 46 
 47 
 48         List<Class<?>> classes = new ArrayList<Class<?>>();  // Define a class Generic set of classes
 49         boolean recursive = true;  // recursive Whether to iterate in a loop
 50         String packageDirName = packageName.replace(POINT_CONSTANT, LEFT_LINE_CONSTANT);  // Get the name of the package and replace it
 51         Enumeration<URL> dirs;  // Define a collection of enumerations to hold all under that directory separately java Class files and Jar Contents such as packages
 52         dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
 53         /**
 54          * Iterate through items in this directory
 55          */
 56         while (dirs.hasMoreElements()) {
 57             URL url = dirs.nextElement();  // Get the next element
 58             String protocol = url.getProtocol();  // Get the name of the protocol protocol
 59             // If it is
 60             /**
 61              * If protocol is a file
 62              */
 63             if (FILE_CONSTANT.equals(protocol)) {
 64                 String filePath = URLDecoder.decode(url.getFile(), UTF8_CONSTANT); // Get the physical path of the package
 65                 findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes); // Scan entire package of files as files and add them to the collection
 66                 /**
 67                  * If protocol is a jar package file
 68                  */
 69             } else if (JAR_CONSTANT.equals(protocol)) {
 70                 JarFile jar;  // Define a JarFile
 71                 jar = ((JarURLConnection) url.openConnection()).getJarFile();  // Obtain jar
 72                 Enumeration<JarEntry> entries = jar.entries();  // from jar Get Enumeration Class in Package
 73                 /**
 74                  * Iterate through the enumerated classes obtained from the Jar package
 75                  */
 76                 while (entries.hasMoreElements()) {
 77                     JarEntry entry = entries.nextElement();  // Obtain jar An entity in, such as a directory, META-INF Files such as
 78                     String name = entry.getName();
 79                     /**
 80                      * If the entity name begins with/
 81                      */
 82                     if (name.charAt(0) == LEFT_LINE_CONSTANT) {
 83                         name = name.substring(1);  // Get the following string
 84                     }
 85                     // If
 86                     /**
 87                      * If the first half of the entity name is the same as the defined package name
 88                      */
 89                     if (name.startsWith(packageDirName)) {
 90                         int idx = name.lastIndexOf(LEFT_LINE_CONSTANT);
 91                         /**
 92                          * And the entity name ends with'/'
 93                          * If it ends with'/'it is a package
 94                          */
 95                         if (idx != -1) {
 96                             packageName = name.substring(0, idx).replace(LEFT_LINE_CONSTANT, POINT_CONSTANT);  // Get the package name and'/' replace with'.'
 97                         }
 98                         /**
 99                          * If the entity is a package and can continue iteration
100                          */
101                         if ((idx != -1) || recursive) {
102                             if (name.endsWith(POINT_CLASS_CONSTANT) && !entry.isDirectory()) {  // if it is.class File and not directory
103                                 String className = name.substring(packageName.length() + 1, name.length() - 6);  // Remove.class Suffix and get the real class name
104                                 classes.add(Class.forName(packageName + '.' + className)); // Add the obtained class name to the classes
105                             }
106                         }
107                     }
108                 }
109             }
110         }
111 
112         return classes;
113     }
114 
115 
116     /**
117      * Get all Class es under the package as files
118      * findAndAddClassesInPackageByFile
119      *
120      * @param packageName
121      * @param packagePath
122      * @param recursive
123      * @param classes
124      */
125     public static void findAndAddClassesInPackageByFile(
126             String packageName, String packagePath,
127             final boolean recursive,
128             List<Class<?>> classes) throws Exception {
129 
130 
131         File dir = new File(packagePath);  // Get a directory for this package and set up a File
132 
133         if (!dir.exists() || !dir.isDirectory()) {  // if dir Return directly if it does not exist or is not a directory
134             return;
135         }
136 
137         File[] dirfiles = dir.listFiles(new FileFilter() {  // if dir Get all the files, directories under the package if they exist
138 
139             /**
140              * Custom filter rules if they can be looped (with subdirectories) or a file ending with.class (compiled java bytecode file)
141              * @param file
142              * @return
143              */
144             @Override
145             public boolean accept(File file) {
146                 return (recursive && file.isDirectory()) || (file.getName().endsWith(POINT_CLASS_CONSTANT));
147             }
148         });
149 
150         /**
151          * Loop through all files to get java class files and add them to the collection
152          */
153         for (File file : dirfiles) {
154             if (file.isDirectory()) {  // if file Continue scanning for directories
155                 findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
156                         classes);
157             } else {  // if file by java Class file removes the following.class Leave only the class name
158                 String className = file.getName().substring(0, file.getName().length() - 6);
159                 classes.add(Class.forName(packageName + '.' + className));  // hold className Add to Collection
160 
161             }
162         }
163     }
164 }

6. Access jump page index.jsp

 1 <%--
 2   Created by IntelliJ IDEA.
 3   User: apple
 4   Date: 2019-11-07
 5   Time: 13:28
 6   To change this template use File | Settings | File Templates.
 7 --%>
 8 <%--
 9 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
10 --%>
11 <html>
12   <head>
13     <title>My Fucking SpringMVC</title>
14   </head>
15   <body>
16   <h2>The Lie We Live!</h2>
17   <H2>My Fucking SpringMVC</H2>
18   </body>
19 </html>

7. Customize the Dispatcher Servlet design, inherit HttpServlet, override init methods, doGet, doPost, etc., and customize annotations to achieve the functions.

  1 package com.tjt.springmvc;
  2 
  3 
  4 import javax.servlet.ServletConfig;
  5 import javax.servlet.ServletException;
  6 import javax.servlet.http.HttpServlet;
  7 import javax.servlet.http.HttpServletRequest;
  8 import javax.servlet.http.HttpServletResponse;
  9 import java.io.IOException;
 10 import java.lang.reflect.InvocationTargetException;
 11 import java.lang.reflect.Method;
 12 import java.util.List;
 13 import java.util.Map;
 14 import java.util.Objects;
 15 import java.util.concurrent.ConcurrentHashMap;
 16 
 17 
 18 
 19 /**
 20  * DispatcherServlet Processing SpringMVC Framework Process
 21  * Main process:
 22  * 1,Package Scan Gets All Classes Below Package
 23  * 2,Initialize all classes under the package
 24  * 3,Initialize the HandlerMapping method, which corresponds to the url and method
 25  * 4,Implement HttpServlet override doPost method
 26  *
 27  */
 28 public class DispatcherServlet extends HttpServlet {
 29 
 30     /**
 31      * Partial static constants
 32      */
 33     private static String PACKAGE_CLASS_NULL_EX = "Packet scanned classes by null";
 34     private static String HTTP_NOT_EXIST = "sorry http is not exit 404";
 35     private static String METHOD_NOT_EXIST = "sorry method is not exit 404";
 36     private static String POINT_JSP = ".jsp";
 37     private static String LEFT_LINE = "/";
 38 
 39     /**
 40      * Container for storing SpringMVC bean s
 41      */
 42     private ConcurrentHashMap<String, Object> mvcBeans = new ConcurrentHashMap<>();
 43     private ConcurrentHashMap<String, Object> mvcBeanUrl = new ConcurrentHashMap<>();
 44     private ConcurrentHashMap<String, String> mvcMethodUrl = new ConcurrentHashMap<>();
 45     private static String PROJECT_PACKAGE_PATH = "com.tjt.springmvc";
 46 
 47 
 48     /**
 49      * Initialize components sequentially
 50      * @param config
 51      */
 52     @Override
 53     public void init(ServletConfig config) {
 54         String packagePath = PROJECT_PACKAGE_PATH;
 55         try {
 56             //1.Scan for all classes under the current package
 57             List<Class<?>> classes = comscanPackage(packagePath);
 58             //2.Initialization springmvcbean
 59             initSpringMvcBean(classes);
 60         } catch (Exception e) {
 61             e.printStackTrace();
 62         }
 63         //3.Map request addresses and methods
 64         initHandMapping(mvcBeans);
 65     }
 66 
 67 
 68     /**
 69      * Call the ClassUtils tool class to get all the classes in the project
 70      * @param packagePath
 71      * @return
 72      * @throws Exception
 73      */
 74     public List<Class<?>> comscanPackage(String packagePath) throws Exception {
 75         List<Class<?>> classes = ClassUtils.getClasses(packagePath);
 76         return classes;
 77     }
 78 
 79     /**
 80      * Initialize SpringMVC bean s
 81      *
 82      * @param classes
 83      * @throws Exception
 84      */
 85     public void initSpringMvcBean(List<Class<?>> classes) throws Exception {
 86         /**
 87          * Throw an exception if the classes scanned by the package are empty
 88          */
 89         if (classes.isEmpty()) {
 90             throw new Exception(PACKAGE_CLASS_NULL_EX);
 91         }
 92 
 93         /**
 94          * Traverse all classes to get the @MyController annotation
 95          */
 96         for (Class<?> aClass : classes) {
 97             //Get custom annotated controller Initialize it to custom springmvc In container
 98             MyController declaredAnnotation = aClass.getDeclaredAnnotation(MyController.class);
 99             if (declaredAnnotation != null) {
100                 //Get the name of the class
101                 String beanid = lowerFirstCapse(aClass.getSimpleName());
102                 //Get Object
103                 Object beanObj = aClass.newInstance();
104                 //Put in spring container
105                 mvcBeans.put(beanid, beanObj);
106             }
107         }
108 
109     }
110 
111     /**
112      * Initialize the HandlerMapping method
113      *
114      * @param mvcBeans
115      */
116     public void initHandMapping(ConcurrentHashMap<String, Object> mvcBeans) {
117         /**
118          * Traversing springmvc to get injected object values
119          */
120         for (Map.Entry<String, Object> entry : mvcBeans.entrySet()) {
121             Object objValue = entry.getValue();
122             Class<?> aClass = objValue.getClass();
123             //Get the current class to determine if it has a custom requestMapping annotation
124             String mappingUrl = null;
125             MyRequestMapping anRequestMapping = aClass.getDeclaredAnnotation(MyRequestMapping.class);
126             if (anRequestMapping != null) {
127                 mappingUrl = anRequestMapping.value();
128             }
129             //Get all methods of the current class,Is there any comment on the judgement method
130             Method[] declaredMethods = aClass.getDeclaredMethods();
131             /**
132              * Traversal Notes
133              */
134             for (Method method : declaredMethods) {
135                 MyRequestMapping methodDeclaredAnnotation = method.getDeclaredAnnotation(MyRequestMapping.class);
136                 if (methodDeclaredAnnotation != null) {
137                     String methodUrl = methodDeclaredAnnotation.value();
138                     mvcBeanUrl.put(mappingUrl + methodUrl, objValue);
139                     mvcMethodUrl.put(mappingUrl + methodUrl, method.getName());
140                 }
141             }
142 
143         }
144 
145     }
146 
147     /**
148      * @param str
149      * @return Class name initial lowercase
150      */
151     public static String lowerFirstCapse(String str) {
152         char[] chars = str.toCharArray();
153         chars[0] += 32;
154         return String.valueOf(chars);
155 
156     }
157 
158     /**
159      * doPost request
160      * @param req
161      * @param resp
162      * @throws ServletException
163      * @throws IOException
164      */
165     @Override
166     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
167         try {
168             /**
169              * Processing Request
170              */
171             doServelt(req, resp);
172         } catch (NoSuchMethodException e) {
173             e.printStackTrace();
174         } catch (InvocationTargetException e) {
175             e.printStackTrace();
176         } catch (IllegalAccessException e) {
177             e.printStackTrace();
178         }
179     }
180 
181     /**
182      * doServelt Processing Request
183      * @param req
184      * @param resp
185      * @throws IOException
186      * @throws NoSuchMethodException
187      * @throws InvocationTargetException
188      * @throws IllegalAccessException
189      * @throws ServletException
190      */
191     private void doServelt(HttpServletRequest req, HttpServletResponse resp) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ServletException {
192         //Get Request Address
193         String requestUrl = req.getRequestURI();
194         //Find Address Correspondence bean
195         Object object = mvcBeanUrl.get(requestUrl);
196         if (Objects.isNull(object)) {
197             resp.getWriter().println(HTTP_NOT_EXIST);
198             return;
199         }
200         //Method of getting the request
201         String methodName = mvcMethodUrl.get(requestUrl);
202         if (methodName == null) {
203             resp.getWriter().println(METHOD_NOT_EXIST);
204             return;
205         }
206 
207 
208         //Execution method by conformal reflection
209         Class<?> aClass = object.getClass();
210         Method method = aClass.getMethod(methodName);
211 
212         String invoke = (String) method.invoke(object);
213         // Get suffix information
214         String suffix = POINT_JSP;
215         // Page Directory Address
216         String prefix = LEFT_LINE;
217         req.getRequestDispatcher(prefix + invoke + suffix).forward(req, resp);
218 
219 
220 
221 
222     }
223 
224     /**
225      * doGet request
226      * @param req
227      * @param resp
228      * @throws ServletException
229      * @throws IOException
230      */
231     @Override
232     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
233         this.doPost(req, resp);
234     }
235 
236 
237 }

8. Test the handwritten Spring MVC framework effect class TestMySpring MVC.

 1 package com.tjt.springmvc;
 2 
 3 
 4 /**
 5  * Handwritten SpringMVC Test Class
 6  * TestMySpringMVC
 7  */
 8 @MyController
 9 @MyRequestMapping(value = "/tjt")
10 public class TestMySpringMVC {
11 
12 
13     /**
14      * Testing handwritten Spring MVC framework effects testMyMVC1
15      * @return
16      */
17     @MyRequestMapping("/mvc")
18     public String testMyMVC1() {
19         System.out.println("he Lie We Live!");
20         return "index";
21     }
22 
23 
24 }

9. Configure Tomcat to run a Web project.

Figure 5. Configuring tomcat

10. Run the project and access the tests.

1. Enter the normal path http://localhost:8080/tjt/mvc to access the test results as follows:

Figure 6. Normal path test results

2. Input illegal (non-existent) path http://localhost:8080/tjt/mvc8 access test results as follows:

Figure 7. Illegal Path Test Effect

3. The console prints "The Lie We Live" as follows:

Figure 8. Console printing

The test results show that the SpringMVC framework has been successfully handwritten. Congratulations.

 

 

Smell the rose and have a tiger in the heart

 

 

 

Topics: Java Spring xml JavaEE