preface

The Spring framework is all too familiar to Java backend programmers, having previously been implemented with reflection but with a lot of clever design behind it.

If you don’t read Spring’s source code, you will miss a chance to learn from the masters: its code specification and design ideas are well worth learning.

Most of us programmers are amateurs who don’t understand code specifications. After writing the code for a month, other old drivers had to spend 3 days to reconstruct it. I believe that most old drivers are very headache to see the new code.

Without further ado, let’s move on to today’s topic, the MVC pattern has been widely used in Web application design. SpringMVC, with DispatcherServlet as the core, is responsible for coordinating and organizing different components to complete the request processing and return the response, realizing the MVC pattern.

To implement your own SpringMVC framework, you need to start with the following points:

  • Understand SpringMVC workflow and nine components
  • Sort out the design ideas of SpringMVC
  • Implement your own SpringMVC framework

Understand SpringMVC workflow and nine components

The running process of SpringMVC

  • The user sends the request to the DispatcherServlet of the front-end controller
  • The DispatcherServlet invokes the HandlerMapping processor mapper upon receiving the request.
  • The processor mapper finds the specific processor based on the request URL, generates the processor object and a processor interceptor (if any) back to the DispatcherServlet.
  • The DispatcherServlet invokes the handler through the HandlerAdapter processor adapter
  • The execution processor (Controller, also known as back-end Controller).
  • Controller completes execution and returns ModelAndView
  • The HandlerAdapter returns the Controller execution result, ModelAndView, to the DispatcherServlet
  • The DispatcherServlet passes the ModelAndView to the ViewReslover view parser
  • ViewReslover parses and returns the specific View
  • The DispatcherServlet renders the View (i.e. populates the View with model data).
  • DispatcherServlet responds to the user.

As can be seen from the above, DispatcherServlet has the functions of receiving requests, responding to results, forwarding and so on. With dispatcherServlets, you can reduce coupling between components.

The nine components of SpringMVC

protected void initStrategies(ApplicationContext context) {
 // Used to process upload requests. The processing method is common request packaging into MultipartHttpServletRequest, the latter can be directly call getFile method to obtain the File.
 initMultipartResolver(context);
 //SpringMVC uses Locale in two main places: ViewResolver view parsing; The second is when using internationalized resources or topics.
 initLocaleResolver(context); 
 // Used to parse topics. Each theme in SpringMVC corresponds to a properties file that holds all the resources associated with the current theme,
 // Images, CSS styles, etc. The SpringMVC theme also supports internationalization,
 initThemeResolver(context);
 // To find the Handler.
 initHandlerMappings(context);
 // By its name, it is an adapter. The structure of the processing methods required by servlets is fixed, taking request and response methods as parameters.
 // How can fixed Servlet handling methods call flexible handlers for processing? That's what HandlerAdapter does
 initHandlerAdapters(context);
 // All other components are used for work. In the process of working will inevitably appear problems, problems after how to do?
 // This requires a special role to handle exceptions, which in SpringMVC is HandlerExceptionResolver.
 initHandlerExceptionResolvers(context);
 // This Handler does not set the View or the ViewName, so we need to get the ViewName from the request.
 / / how to get from the request ViewName is RequestToViewNameTranslator thing to do.
 initRequestToViewNameTranslator(context);
 //ViewResolver is used to resolve String View names and Locale views into View views.
 //View is used to render the page, that is, to fill the template with the parameters returned by the program, to generate AN HTML (or other type) file.
 initViewResolvers(context);
 // Manage the FlashMap, which is used to pass parameters in the redirect.
 initFlashMapManager(context); 
}
Copy the code

Sort out the design ideas of SpringMVC

This article only implements its own @Controller, @requestMapping, and @RequestParam annotations. Readers can try to implement the rest of the SpringMVC functionality themselves.

1. Read the configuration

As you can see from the figure, SpringMVC is essentially a Servlet that inherits from HttpServlet.

The FrameworkServlet is responsible for initializing the SpringMVC container and setting the Spring container as the parent. Since this article is just implementing SpringMVC, I won’t go into much detail about the Spring container

To read the configuration in web.xml, we use the ServletConfig class, which represents the configuration information of the current Servlet in web.xml. Load our own MyDispatcherServlet and read the configuration file from web.xml.

2. Initialization phase

We mentioned earlier that the initStrategies method of DispatcherServlet initializes 9 components, but here we will implement some of the most basic components of SpringMVC, not all of them, including in order:

  • Loading a Configuration File
  • Scan all classes under the user configuration package
  • Take the scanned class, instantiate it through reflection. And put it into an IOC container (Map’s key-value pair beanname-bean). BeanName is lowercase by default
  • Initialize HandlerMapping, which maps url and method into a K-V Map, and fetch it at run time

3. Operation stage

Each request will call the doGet or doPost methods, so the unified run phase will be handled in the doDispatch Method, which will match the corresponding Method in HandlerMapping according to the URL request.

The reflection mechanism is then used to call the method corresponding to the URL in the Controller and get the result back. The following features are included in order:

  • Exception interception
  • Gets the parameters passed in by the request and processes the parameters
  • By initializing handlerMapping, retrieve the method name corresponding to the URL and reflect the call

Implement your own SpringMVC framework

Project Documents and Contents:

First, create a new Maven project and import the following dependencies in POM.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0. 0</modelVersion>
  <groupId>com.liugh</groupId>
  <artifactId>liughMVC</artifactId>
  <version>0.01.-SNAPSHOT</version>
  <packaging>war</packaging>
  
 <properties>
  <project.build.sourceEncoding>UTF- 8 -</project.build.sourceEncoding>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
  <java.version>1.8</java.version>
 </properties>
 
 <dependencies>
      <dependency>
       <groupId>javax.servlet</groupId> 
     <artifactId>javax.servlet-api</artifactId> 
     <version>3.01.</version> 
     <scope>provided</scope>
  </dependency>
    </dependencies>
</project>
Copy the code

Next, we create a web.xml in web-INF with the following configuration:

<? xml version="1.0" encoding="UTF-8"? > <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
 version="3.0">
 <servlet>
  <servlet-name>MySpringMVC</servlet-name>
  <servlet-class>com.liugh.servlet.MyDispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>application.properties</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>MySpringMVC</servlet-name>
  <url-pattern>/*  Copy the code

The application.properties file simply configures the packages to be scanned into the SpringMVC container.

scanPackage=com.liugh.core
Copy the code

Create your own Controller annotation, which can only be annotated on a class:

package com.liugh.annotation;

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
 /** * register an alias for the controller * @return */
    String value() default "";

}
Copy the code

RequestMapping annotations can be used on classes and methods:

package com.liugh.annotation;

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

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
    /** * indicates the url to access the method */
    String value() default "";
}
Copy the code

RequestParam annotations, annotated only on parameters

package com.liugh.annotation;

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

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
    /** * indicates the alias of the parameter. * @return */ is mandatory
    String value();
}
Copy the code

Then create a MyDispatcherServlet class that inherits HttpServlet and overwrites the init, doGet, doPost methods, plus the functionality we implemented in step 2:

package com.liugh.servlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.liugh.annotation.MyController;
import com.liugh.annotation.MyRequestMapping;

public class MyDispatcherServlet extends HttpServlet{
 
 private Properties properties = new Properties();
 
 private List<String> classNames = new ArrayList<>();
 
 private Map<String, Object> ioc = new HashMap<>();
 
 private Map<String, Method> handlerMapping = new  HashMap<>();
 
 private Map<String, Object> controllerMap  =new HashMap<>();
 
 @Override
 public void init(ServletConfig config) throws ServletException {
  
  //1. Load the configuration file
  doLoadConfig(config.getInitParameter("contextConfigLocation"));
  
  //2. Initialize all associated classes and scan all classes under the user specified package
  doScanner(properties.getProperty("scanPackage"));
  
  //3. Take the scanned class, use reflection mechanism, instantiate it, and put it in ioc container (k-v beanname-bean) beanName default lowercase
  doInstance();
  
  //4. Initialize HandlerMapping(url = method)
  initHandlerMapping();
 }
  
 @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 {
   // Process the request
   doDispatch(req,resp);
  } catch (Exception e) {
   resp.getWriter().write("500!!!!! Server Exception");
  }

 }
 
 private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
  if(handlerMapping.isEmpty()){
   return;
  }
  
  String url =req.getRequestURI();
  String contextPath = req.getContextPath();
  
  url=url.replace(contextPath, "").replaceAll("/ +"."/");
  
  if(! this.handlerMapping.containsKey(url)){ resp.getWriter().write("404 NOT FOUND!");
   return;
  }
  
  Method method =this.handlerMapping.get(url);
  
  // Get the argument list for the methodClass<? >[] parameterTypes = method.getParameterTypes();// Get the request parameters
  Map<String, String[]> parameterMap = req.getParameterMap();
  
  // Save parameter values
  Object [] paramValues= new Object[parameterTypes.length];
  
  // The argument list for the method
        for (int i = 0; i<parameterTypes.length; i++){  
            // Do something based on the parameter name
            String requestParam = parameterTypes[i].getSimpleName();  
            
            if (requestParam.equals("HttpServletRequest")) {// The parameter type is already specified, the edge is strongly typed
             paramValues[i]=req;
                continue;  
            }  
            if (requestParam.equals("HttpServletResponse")){  
             paramValues[i]=resp;
                continue;  
            }
            if(requestParam.equals("String")) {for (Entry<String, String[]> param : parameterMap.entrySet()) {
            String value =Arrays.toString(param.getValue()).replaceAll("\ \ [| \ \]"."").replaceAll(",\\s".","); paramValues[i]=value; }}}// use reflection mechanism to call
  try {
   method.invoke(this.controllerMap.get(url), paramValues);// The first argument is method's instance in the ioc container
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 private void  doLoadConfig(String location){
  // Load the file corresponding to the value of contextConfigLocation from web. XML into the stream
  InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
  try {
   // Load the contents of the file with the Properties file
   properties.load(resourceAsStream);
  } catch (IOException e) {
   e.printStackTrace();
  }finally {
   / / off the flow
   if(null! =resourceAsStream){ try { resourceAsStream.close(a); } catch (IOException e) { e.printStackTrace(); } } } } private void doScanner(String packageName) {// Replace all of the. With /
  URL url  =this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\ \."."/"));
  File dir = new File(url.getFile());
  for (File file : dir.listFiles()) {
   if(file.isDirectory()){
    // Read the package recursively
    doScanner(packageName+"."+file.getName());
   }else{
    String className =packageName +"." +file.getName().replace(".class"."");
    classNames.add(className);
   }
  }
 }
 
 private void doInstance() {
  if (classNames.isEmpty()) {
   return;
  } 
  for (String className : classNames) {
   try {
    // Get the class out and reflect it (only @myController needs to be instantiated)Class<? > clazz =Class.forName(className);if(clazz.isAnnotationPresent(MyController.class)){
     ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
    }else{
     continue;
    }
   } catch (Exception e) {
    e.printStackTrace();
    continue;
   }
  }
 }

 private void initHandlerMapping(){
  if(ioc.isEmpty()){
   return;
  }
  try {
   for (Entry<String, Object> entry: ioc.entrySet()) {
    Class<? extends Object> clazz = entry.getValue().getClass();
    if(! clazz.isAnnotationPresent(MyController.class)){continue;
    }
    
    // The controller header's URL is spelled with the method's URL
    String baseUrl ="";
    if(clazz.isAnnotationPresent(MyRequestMapping.class)){
     MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
     baseUrl=annotation.value();
    }
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
     if(! method.isAnnotationPresent(MyRequestMapping.class)){continue;
     }
     MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
     String url = annotation.value();
     
     url =(baseUrl+"/"+url).replaceAll("/ +"."/");
     handlerMapping.put(url,method);
     controllerMap.put(url,clazz.newInstance());
     System.out.println(url+","+method); } } } catch (Exception e) { e.printStackTrace(); }}/** * lowercase the first letter of the string */
 private String toLowerFirstWord(String name){
  char[] charArray = name.toCharArray();
  charArray[0] + =32;
  returnString.valueOf(charArray); }}Copy the code

Now that we’ve developed our own SpringMVC, let’s test it out:

package com.liugh.core.controller;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.liugh.annotation.MyController;
import com.liugh.annotation.MyRequestMapping;
import com.liugh.annotation.MyRequestParam;

@MyController
@MyRequestMapping("/test")
public class TestController {
 
  @MyRequestMapping("/doTest")
    public void test1(HttpServletRequest request, HttpServletResponse response,
      @MyRequestParam("param") String param){
   System.out.println(param);
     try {
            response.getWriter().write( "doTest method success! param:"+param);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
  
  
  @MyRequestMapping("/doTest2")
    public void test2(HttpServletRequest request, HttpServletResponse response){
        try {
            response.getWriter().println("doTest2 method success!"); } catch (IOException e) { e.printStackTrace(); }}}Copy the code

The results

Go to http://localhost:8080/liughMVC/test/doTest? Param = liugh is as follows:

Try accessing a non-existent file:

This is where we’re done!

From: my.oschina.net/liughDevelo…

Give a [look], is the biggest support for IT elder brotherCopy the code