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 BeijingCurrent 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