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