preface

With the rise of Spring and the improvement of its functions, it is now possible that most projects are developed using Spring (Family bucket). Spring is indeed the name of the developer’s Spring, Spring has freed the hands of programmers. After SpringBoot comes out, configuration files are greatly reduced, which further liberates the hands of programmers. However, it is precisely because of the power of Spring family products that we are used to developing for Spring. So if there is no Spring one day, will we feel empty? You may not be able to write even the most basic interfaces, especially if you are not familiar with Servlet programming. Without frameworks like Spring, we need to use native servlets to map interface paths and manage objects ourselves.

What can Spring do for us

Spring is a framework designed to address the complexity of enterprise application development. Spring is designed to simplify development.

In the Spring framework, all objects are beans, so through bean-oriented programming (BOP), combined with its core ideas of dependency injection (DI) and section-oriented (AOP) programming, Spring implements its great design philosophy of simplified development.

Inversion of Control (IOC)

IOC: Inversion of Control. The basic concept of inversion of control is that you don’t need to create an object, but you need to describe how the object is created.

To put it simply, we used to create an object in code with the new keyword. With Spring, we no longer need to create an object ourselves. Instead, we need to fetch it directly from the container and automatically inject it into the object we need: dependency injection.

In other words, the control of object creation is no longer in the hands of the programmers, and all the control is in the hands of Spring.

Dependency Injection (DI)

Dependency Injection (DI) is one of Spring’s ways of implementing inversion of control, so inversion of control is sometimes called Dependency Injection.

Aspect Oriented Programming (AOP)

AOP full name: Aspect Oriented Programming. AOP is a programming idea whose core construct is aspects (facets), which encapsulate common behavior that affects multiple classes into reusable modules, leaving the original module to focus only on its own personalized behavior.

Common scenarios for AOP programming are: Authentication, Auto Caching, Error Handling, Debugging, Logging, and Transactions.

Use Spring to complete Hello World

The most native Spring requires more configuration files, and SpringBoot omitted a lot of configuration, compared with the original Spring and simplified a lot, here we take SpringBoot as an example to complete a simple interface development.

  • 1. Create a new onemavenThe project,pomDependencies introduced in the file (omitting a few attributes) :
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
Copy the code
  • Create a new oneHelloControllerClass:
package com.lonely.wolf.note.springboot.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class HelloController {
    @GetMapping("/demo")
    public String helloWorld(String name){
        return "Hello:"+ name; }}Copy the code
  • 3. Finally create a new oneSpringBootStart the class:
package com.lonely.wolf.note.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.lonely.wolf.note.springboot")
class MySpringBootApplication {
    public static void main(String[] args) { SpringApplication.run(MySpringBootApplication.class, args); }}Copy the code
  • 4. Now enter the test path:http://localhost:8080/hello/demo?name= Gemini lone WolfTest, normal output:Hello, Gemini lone Wolf.

As you can see, it is very easy to develop a simple application using SpringBoot. You can complete a simple application without any configuration. This is because SpringBoot already has conventions (conventions are better than configuration ideas), including container Tomcat, which is integrated by default. So we don’t need any configuration files to complete a simple demo application.

Suppose there were no Spring

As we can see from the example above, it is very easy to implement a Hello World using Spring, but how would we implement such a Hello World interface without Spring?

Servlet-based development

Before there was a framework, programming was developed based on raw servlets. Let’s make a simple interface call based on native servlets.

  • 1.pomFiles introduce dependencies, and it’s important to note thatpackageProperty to be set towarPackages, not listed here to save spacepomFull information:
<packaging>war</packaging> 
<dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.4</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
    </dependencies>
Copy the code
  • 2, insrc/mainLet’s go ahead and create a new folderwebapp/WEB-INFAnd then inWEB-INFLet’s create a new oneweb.xmlFile:

      
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="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/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">
	<display-name>Lonely Wolf Web Application</display-name>
	<servlet>
		<servlet-name>helloServlet</servlet-name>
		<servlet-class>com.lonely.wolf.mini.spring.servlet.HelloServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>helloServlet</servlet-name>
		<url-pattern>/hello/*</url-pattern>
	</servlet-mapping>
</web-app>
Copy the code

Selvlet and servlet-mapping tags are defined in this file. These two tags must correspond to each other. The top tag defines the location of the servlet, while the bottom servlet-mapping file defines the path mapping. These two labels correspond via the servlet-name tag.

  • Create a new oneHelloServletClass inheritanceHttpServlet:
package com.lonely.wolf.mini.spring.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/** * The original Servlet interface is written, generally need to implement GET and POST methods, other methods can optionally inherit */ depending on the situation
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html; charset=utf-8");
        response.getWriter().write("Hello:" + request.getParameter("name")); }}Copy the code
  • 4, implementmavenPackage command to confirm successful packagingwarPackage:

  • 5,RUN-->Edit ConfigurationsAnd then click on the top left+Create a new oneTomcat ServerIf it is the first configuration, it is not configured by defaultTomcat ServerOptions, you need to click on the bottomxx more items...:

  • 6. Click on the rightDeployment, and then click in turn according to the picture below, and finally find the package above in the boxwarPackage files:

  • 7. After the selection, note the followingApplication ContextBy defaultwarPackage name, which we need to delete for convenience, i.e. no context path, just a root path/(Of course, the context can also be preserved, but this part should be included on each request), and then selectApply, click on theOKTo complete the deployment:

  • 8. Finally we enter the request path in the browserhttp://localhost:8080/hello?name= Gemini lone Wolf, you can get the return:Hello, Gemini lone Wolf.

We have completed a simple Servlet based interface development, as you can see, the configuration is very troublesome, each additional Servlet needs to increase the corresponding configuration, so there are many frameworks to help simplify development, such as the original popular Struts2 framework, Of course, except for some older projects, we rarely use the Spring framework for development.

Imitation of the Spring

Spring’s source code is so large that most people stay away from it. It’s true that Spring, after all, has been iterated over the years, is feature-rich, and projects are huge. It’s not easy to understand. While Spring is difficult to understand, its core ideas are still the same as those we covered above, and it’s time to emulate the core parts of Spring and implement a super mini version of it yourself (this version doesn’t include AOP functionality).

  • 1.pomThe dependency and the top remain the same, and thenweb.xmlWith the following change, all interfaces are blocked here/ *, and then configure a parameter, this parameter is also for more image simulationSpring:
<servlet>
    <servlet-name>myDispatcherServlet</servlet-name>
    <servlet-class>com.lonely.wolf.mini.spring.v1.MyDispatcherServlet</servlet-class>
    <init-param>
        <param-name>defaultConfig</param-name>
        <param-value>application.properties</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>myDispatcherServlet</servlet-name>
    <url-pattern>/ *</url-pattern>
</servlet-mapping>
Copy the code
  • 2, inrespurcesLet’s create a new configuration fileapplication.propertiesIs used to define the basic path of scanning:
basePackages=com.lonely.wolf.mini.spring
Copy the code
  • 3. Create some related annotation classes:
package com.lonely.wolf.mini.spring.annotation;

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfAutowired {
    String value(a) default "";
}
Copy the code
package com.lonely.wolf.mini.spring.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfController {
    String value(a) default "";
}
Copy the code
package com.lonely.wolf.mini.spring.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfGetMapping {
    String value(a) default "";
}
Copy the code
package com.lonely.wolf.mini.spring.annotation;

import java.lang.annotation.*;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfRequestParam {
    String value(a) default "";
}
Copy the code
package com.lonely.wolf.mini.spring.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfService {
    String value(a) default "";
}
Copy the code
  • 4. The core logic at this time isMyDispatcherServletClass a:
package com.lonely.wolf.mini.spring.v1;

import com.lonely.wolf.mini.spring.annotation.*;
import com.lonely.wolf.mini.spring.v1.config.MyConfig;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class MyDispatcherServlet extends HttpServlet {
    private MyConfig myConfig = new MyConfig();
    private List<String> classNameList = new ArrayList<String>();

    private Map<String,Object> iocContainerMap = new HashMap<>();
    private Map<String,HandlerMapping> handlerMappingMap = new HashMap<>();

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

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            this.doDispatch(request, response);
        } catch(Exception e) { e.printStackTrace(); }}private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception{
        String requestUrl = this.formatUrl(request.getRequestURI());
        HandlerMapping handlerMapping = handlerMappingMap.get(requestUrl);
        if (null == handlerMapping){
            response.getWriter().write("404 Not Found");
            return;
        }

        // Get the parameter type in the methodClass<? >[] paramTypeArr = handlerMapping.getMethod().getParameterTypes(); Object[] paramArr =new Object[paramTypeArr.length];

        for (int i=0; i<paramTypeArr.length; i++){ Class<? > clazz = paramTypeArr[i];// Only three types of parameters are considered
            if (clazz == HttpServletRequest.class){
                paramArr[i] = request;
            }else if (clazz == HttpServletResponse.class){
                paramArr[i] = response;
            } else if (clazz == String.class){
                Map<Integer,String> methodParam = handlerMapping.getMethodParams();
                paramArr[i] = request.getParameter(methodParam.get(i));
            }else{
                System.out.println("Currently unsupported parameter types"); }}// reflection calls the controller method
        handlerMapping.getMethod().invoke(handlerMapping.getTarget(), paramArr);
    }

    private String formatUrl(String requestUrl) {
        requestUrl = requestUrl.replaceAll("/ +"."/");
        if (requestUrl.lastIndexOf("/") == requestUrl.length() -1){
            requestUrl = requestUrl.substring(0,requestUrl.length() -1);
        }
        return requestUrl;
    }


    @Override
    public void init(ServletConfig config) throws ServletException {
        //1. Load the configuration file
        try {
            doLoadConfig(config.getInitParameter("defaultConfig"));
        } catch (Exception e) {
            System.out.println("Failed to load configuration file");
            return;
        }

        //2. Scan based on the obtained scan path
        doScanPacakge(myConfig.getBasePackages());

        //3. Initialize the scanned classes and put them in the IOC container
        doInitializedClass();

        //4. Dependency injection
        doDependencyInjection();

        System.out.println("DispatchServlet Init End..." );
    }


    private void doDependencyInjection(a) {
        if (iocContainerMap.size() == 0) {return;
        }
        // Loop classes in IOC containers
        Iterator<Map.Entry<String,Object>> iterator = iocContainerMap.entrySet().iterator();

        while(iterator.hasNext()){ Map.Entry<String,Object> entry = iterator.next(); Class<? > clazz = entry.getValue().getClass(); Field[] fields = clazz.getDeclaredFields();// Attribute injection
            for (Field field : fields){
                // If the attribute has a WolfAutowired annotation, inject the value (leaving other annotations aside for now)
                if (field.isAnnotationPresent(WolfAutowired.class)){
                    String value = toLowerFirstLetterCase(field.getType().getSimpleName());// The value of the default bean is lowercase
                    if (field.getType().isAnnotationPresent(WolfService.class)){
                        WolfService wolfService = field.getType().getAnnotation(WolfService.class);
                        value = wolfService.value();
                    }
                    field.setAccessible(true);
                    try {
                        Object target = iocContainerMap.get(beanName);
                        if (null == target){
                            System.out.println(clazz.getName() + "required bean:" + beanName + ",but we not found it");
                        }
                        field.set(entry.getValue(),iocContainerMap.get(beanName));// Initialize the object and inject it later
                    } catch(IllegalAccessException e) { e.printStackTrace(); }}}// Initialize HanderMapping
            String requestUrl = "";
            // Get the request path on the Controller class
            if (clazz.isAnnotationPresent(WolfController.class)){
                requestUrl = clazz.getAnnotation(WolfController.class).value();
            }

            // Loop through the method in the class to get the path on the method
            Method[] methods = clazz.getMethods();
            for (Method method : methods){
                // Assume WolfGetMapping is the only annotation
                if(! method.isAnnotationPresent(WolfGetMapping.class)){continue;
                }
                WolfGetMapping wolfGetMapping = method.getDeclaredAnnotation(WolfGetMapping.class);
                requestUrl = requestUrl + "/" + wolfGetMapping.value();// Compose the completed request path

                // Not consider the regular match path /xx/* case, only consider the full match case
                if (handlerMappingMap.containsKey(requestUrl)){
                    System.out.println("Repeated path");
                    continue;
                }

                Annotation[][] annotationArr = method.getParameterAnnotations();// Get an annotation of the parameters in the method

                Map<Integer,String> methodParam = new HashMap<>();// Store the order and names of parameters
                retryParam:
                for (int i=0; i<annotationArr.length; i++){for (Annotation annotation : annotationArr[i]){
                        if (annotation instanceof WolfRequestParam){
                            WolfRequestParam wolfRequestParam = (WolfRequestParam) annotation;
                            methodParam.put(i,wolfRequestParam.value());// Store the location of the parameter and the parameter name defined in the annotation
                            continue retryParam;
                        }
                    }
                }

                requestUrl = this.formatUrl(requestUrl);// To prevent path mismatch caused by too many paths
                HandlerMapping handlerMapping = new HandlerMapping();
                handlerMapping.setRequestUrl(requestUrl);// Request path
                handlerMapping.setMethod(method);// Request method
                handlerMapping.setTarget(entry.getValue());// Request the controller object of the method
                handlerMapping.setMethodParams(methodParam);// Request method parameter information
                handlerMappingMap.put(requestUrl,handlerMapping);/ / in the hashmap}}}/** * Initializes the class and places it in the container iocContainerMap */
    private void doInitializedClass(a) {
        if (classNameList.isEmpty()){
            return;
        }
        for (String className : classNameList){
            if (StringUtils.isEmpty(className)){
                continue;
            }
            Class clazz;
            try {
                clazz = Class.forName(className);// reflection gets objects
                if (clazz.isAnnotationPresent(WolfController.class)){
                    String value = ((WolfController)clazz.getAnnotation(WolfController.class)).value();
                    // If value is specified directly, value is used; otherwise, lowercase class name is used as the key value to store the instance object of the class
                    iocContainerMap.put(StringUtils.isBlank(value) ? toLowerFirstLetterCase(clazz.getSimpleName()) : value,clazz.newInstance());
                }else if(clazz.isAnnotationPresent(WolfService.class)){
                    String value = ((WolfService)clazz.getAnnotation(WolfService.class)).value();
                    iocContainerMap.put(StringUtils.isBlank(value) ? toLowerFirstLetterCase(clazz.getSimpleName()) : value,clazz.newInstance());
                }else{
                    System.out.println("Regardless of other notes."); }}catch (Exception e) {
                e.printStackTrace();
                System.out.println("Failed to initialize class, className is"+ className); }}}/** * converts the first letter to lowercase *@param className
     * @return* /
    private String toLowerFirstLetterCase(String className) {
        if (StringUtils.isBlank(className)){
            return "";
        }
        String firstLetter = className.substring(0.1);
        return firstLetter.toLowerCase() + className.substring(1);
    }


    /** * Scan all files under the package to obtain the fully qualified class name *@param basePackages
     */
    private void doScanPacakge(String basePackages) {
        if (StringUtils.isBlank(basePackages)){
            return;
        }
        // Replace the package name with. /
        String scanPath = "/" + basePackages.replaceAll("\ \."."/");
        URL url = this.getClass().getClassLoader().getResource(scanPath);// Obtain the full path of the disk where the current package resides
        File files = new File(url.getFile());// Get all files in the current path
        for (File file : files.listFiles()){// Start scanning for all files in the path
            if (file.isDirectory()){// If it is a folder, recurse
                doScanPacakge(basePackages + "." + file.getName());
            }else{// If it is a file, add it to the collection. Because this is the file path obtained through the class loader, it is actually the path where the class file is located
                classNameList.add(basePackages + "." + file.getName().replace(".class"."")); }}}/** * Load the configuration file *@paramConfigPath - The path of the configuration file */
    private void doLoadConfig(String configPath) {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configPath);
        Properties properties = new Properties();
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Failed to load configuration file");
        }

        properties.forEach((k, v) -> {
            try {
                Field field = myConfig.getClass().getDeclaredField((String)k);
                field.setAccessible(true);
                field.set(myConfig,v);
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("Failed to initialize configuration class");
                return; }}); }}Copy the code
  • 5, theServletCompared to the one aboveHelloServletOne moreinitMethod, this method mainly do the following things:

(1) Initialize the configuration file and obtain the parameter information in the configuration file (corresponding method: doLoadConfig).

(2) Get the configuration file loaded in step 1, obtain the package path to be scanned, then convert the package path to the actual disk path, and start traversing all the class files under the disk path, and finally obtain the fully qualified types of all classes under the scanned path after conversion. Store it in the global variable classNameList (doScanPacakge).

(3) According to the global variable obtained in step 2, the classes in classNameList are initialized by reflection (note that only classes with specified annotations are initialized) and the corresponding relationship is stored in the iocContainerMap (the IOC container). The key value is the value attribute in the annotation. If the value attribute is empty, the class name is saved in lower case by default (doInitializedClass).

(4) This step is more critical. It is necessary to assign values to the attributes of all classes in the IOC container and map and store the request paths in the Controller. In order to ensure that the methods in the Controller can be called successfully, the parameters of the method need to be stored. Attributes are mapped only to annotated attributes, and the instance object initialized in step 3 is assigned from the IOC container. Finally, the mapping between the request path and the method in the Controller is stored in the variable handlerMappingMap. The key value is the request path, and the value is the related information of the method (corresponding method: doDependencyInjection).

  • 6. Store the mapping between request paths and methodsHandlerMappingClass to store:
package com.lonely.wolf.mini.spring.v1;

import java.lang.reflect.Method;
import java.util.Map;

// Omits getter/setter methods
public class HandlerMapping {
    private String requestUrl;
    private Object target;// Save the corresponding instance of the method
    private Method method;// The method to save the mapping
    private Map<Integer,String> methodParams;// Record method parameters
}
Copy the code
  • 7, after initialization is complete, because interception/ *, so calling any interface will enterMyDispatcherServletAnd will eventually execute methodsdoDispatchThis method takes the path of the request and then the global variablehandlerMappingMapMatches, and returns if no match is found404, the necessary parameters are taken out and assigned, and finally called by reflectionControllerRelated methods in.
  • 8. Create a new oneHelloControllerHelloServiceTo test:
package com.lonely.wolf.mini.spring.controller;

import com.lonely.wolf.mini.spring.annotation.WolfAutowired;
import com.lonely.wolf.mini.spring.annotation.WolfController;
import com.lonely.wolf.mini.spring.annotation.WolfGetMapping;
import com.lonely.wolf.mini.spring.annotation.WolfRequestParam;
import com.lonely.wolf.mini.spring.service.HelloService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WolfController
public class HelloController {
    @WolfAutowired
    private HelloService helloService;

    @WolfGetMapping("/hello")
    public void query(HttpServletRequest request,HttpServletResponse response, @WolfRequestParam("name") String name) throws IOException {
        response.setContentType("text/html; charset=utf-8");
        response.getWriter().write("Hello:"+ name); }}Copy the code
package com.lonely.wolf.mini.spring.service;

import com.lonely.wolf.mini.spring.annotation.WolfService;

@WolfService(value = "hello_service")// To demonstrate whether the value attribute can be correctly fetched
public class HelloService {}Copy the code
  • 9. Enter the test path:http://localhost:8080////hello?name= Gemini lone Wolf, the test found that the output can be normal:Hello, Gemini lone Wolf.

The above example is just a simple demonstration of how to do a simple application development without any framework. Many of the details in this example are not addressed, just to experience the core ideas of Spring and understand what Spring actually helps us to do. In fact, Spring can help us do much more than this example. Spring architecture is large, elegant, and has been iterated and optimized for many years. Is a framework worth studying.

conclusion

This article starts with the introduction of the core functions of Spring, from how to use Spring to complete an application development, to how to develop based on servlets without Spring, and finally through a simple example to experience the core ideas of Spring.