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> <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 > < the dependency > Mons < groupId > org.apache.com < / 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:
<? The 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/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, */ 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() default "";
}
package com.lonely.wolf.mini.spring.annotation;

import java.lang.annotation.*;

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

import java.lang.annotation.*;

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

import java.lang.annotation.*;

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

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfService {
    String value() 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;
        }

        //获取方法中的参数类型
        Class<?>[] paramTypeArr = handlerMapping.getMethod().getParameterTypes();
        Object[] paramArr = new Object[paramTypeArr.length];

        for (int i=0;i<paramTypeArr.length;i++){
            Class<?> clazz = paramTypeArr[i];
            //参数只考虑三种类型,其他不考虑
            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("暂不支持的参数类型");
            }
        }
        //反射调用controller方法
        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.加载配置文件
        try {
            doLoadConfig(config.getInitParameter("defaultConfig"));
        } catch (Exception e) {
            System.out.println("加载配置文件失败");
            return;
        }

        //2.根据获取到的扫描路径进行扫描
        doScanPacakge(myConfig.getBasePackages());

        //3.将扫描到的类进行初始化,并存放到IOC容器
        doInitializedClass();

        //4.依赖注入
        doDependencyInjection();

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


    private void doDependencyInjection() {
        if (iocContainerMap.size() == 0){
            return;
        }
        //循环IOC容器中的类
        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();

            //属性注入
            for (Field field : fields){
                //如果属性有WolfAutowired注解则注入值(暂时不考虑其他注解)
                if (field.isAnnotationPresent(WolfAutowired.class)){
                    String value = toLowerFirstLetterCase(field.getType().getSimpleName());//默认bean的value为类名首字母小写
                    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));//初始化对象,后面注入
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }

            //初始化HanderMapping
            String requestUrl = "";
            //获取Controller类上的请求路径
            if (clazz.isAnnotationPresent(WolfController.class)){
                requestUrl = clazz.getAnnotation(WolfController.class).value();
            }

            //循环类中的方法,获取方法上的路径
            Method[] methods = clazz.getMethods();
            for (Method method : methods){
                //假设只有WolfGetMapping这一种注解
                if(!method.isAnnotationPresent(WolfGetMapping.class)){
                    continue;
                }
                WolfGetMapping wolfGetMapping = method.getDeclaredAnnotation(WolfGetMapping.class);
                requestUrl = requestUrl + "/" + wolfGetMapping.value();//拼成完成的请求路径

                //不考虑正则匹配路径/xx/* 的情况,只考虑完全匹配的情况
                if (handlerMappingMap.containsKey(requestUrl)){
                    System.out.println("重复路径");
                    continue;
                }

                Annotation[][] annotationArr = method.getParameterAnnotations();//获取方法中参数的注解

                Map<Integer,String> methodParam = new HashMap<>();//存储参数的顺序和参数名
                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());//存储参数的位置和注解中定义的参数名
                            continue retryParam;
                        }
                    }
                }

                requestUrl = this.formatUrl(requestUrl);//主要是防止路径多了/导致路径匹配不上
                HandlerMapping handlerMapping = new HandlerMapping();
                handlerMapping.setRequestUrl(requestUrl);//请求路径
                handlerMapping.setMethod(method);//请求方法
                handlerMapping.setTarget(entry.getValue());//请求方法所在controller对象
                handlerMapping.setMethodParams(methodParam);//请求方法的参数信息
                handlerMappingMap.put(requestUrl,handlerMapping);//存入hashmap
            }
        }
    }


    /**
     * 初始化类,并放入容器iocContainerMap内
     */
    private void doInitializedClass() {
        if (classNameList.isEmpty()){
            return;
        }
        for (String className : classNameList){
            if (StringUtils.isEmpty(className)){
                continue;
            }
            Class clazz;
            try {
                clazz = Class.forName(className);//反射获取对象
                if (clazz.isAnnotationPresent(WolfController.class)){
                    String value = ((WolfController)clazz.getAnnotation(WolfController.class)).value();
                    //如果直接指定了value则取value,否则取首字母小写类名作为key值存储类的实例对象
                    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("不考虑其他注解的情况");
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("初始化类失败,className为" + className);
            }
        }

    }

    /**
     * 将首字母转换为小写
     * @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);
    }


    /**
     * 扫描包下所有文件获取全限定类名
     * @param basePackages
     */
    private void doScanPacakge(String basePackages) {
        if (StringUtils.isBlank(basePackages)){
            return;
        }
        //把包名的.替换为/
        String scanPath = "/" + basePackages.replaceAll("\\.","/");
        URL url = this.getClass().getClassLoader().getResource(scanPath);//获取到当前包所在磁盘的全路径
        File files = new File(url.getFile());//获取当前路径下所有文件
        for (File file : files.listFiles()){//开始扫描路径下的所有文件
            if (file.isDirectory()){//如果是文件夹则递归
                doScanPacakge(basePackages + "." + file.getName());
            }else{//如果是文件则添加到集合。因为上面是通过类加载器获取到的文件路径,所以实际上是class文件所在路径
                classNameList.add(basePackages + "." + file.getName().replace(".class",""));
            }
        }

    }


    /**
     * 加载配置文件
     * @param configPath - 配置文件所在路径
     */
    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("加载配置文件失败");
        }

        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("初始化配置类失败");
                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; Public class HandlerMapping {private String requestUrl; private Object target; Private Method Method; Private Map<Integer,String> methodParams; // record method parameters}Copy the code
  • 7. After initialization, any call to the interface will go to MyDispatcherServlet because /* is intercepted, and will eventually execute the method doDispatch, which will get the requested path. The global variable handlerMappingMap is then matched, 404 is returned if it does not match, necessary parameters are fetched and assigned, and the relevant method in the Controller is called through reflection.

  • Create a new HelloController and HelloService to 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); } } package com.lonely.wolf.mini.spring.service; import com.lonely.wolf.mini.spring.annotation.WolfService; @wolfService (value = "hello_service") 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.

Author: Gemini Lone Wolf

www.cnblogs.com/lonely-wolf…


Recommend 3 original Springboot +Vue projects, with complete video explanation and documentation and source code:

Build a complete project from Springboot+ ElasticSearch + Canal

  • Video tutorial: www.bilibili.com/video/BV1Jq…
  • A complete development documents: www.zhuawaba.com/post/124
  • Online demos: www.zhuawaba.com/dailyhub

【VueAdmin】 hand to hand teach you to develop SpringBoot+Jwt+Vue back-end separation management system

  • Full 800 – minute video tutorial: www.bilibili.com/video/BV1af…
  • Complete development document front end: www.zhuawaba.com/post/18
  • Full development documentation backend: www.zhuawaba.com/post/19

【VueBlog】 Based on SpringBoot+Vue development of the front and back end separation blog project complete teaching

  • Full 200 – minute video tutorial: www.bilibili.com/video/BV1af…
  • Full development documentation: www.zhuawaba.com/post/17

If you have any questions, please come to my official account [Java Q&A Society] and ask me