preface

As we all know, SSM is one of the frameworks Java must learn, and Spring has become an indispensable part of every developer’s life.

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 came out, configuration files were greatly reduced, which further liberated 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 you feel empty? You may not even be able to write the most basic interface, especially those who have not been exposed to 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 Maven project and introduce dependencies (omits some attributes) into the POM file:

<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 HelloController class:

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

Create a new SpringBoot 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, you can now enter the test path: http://localhost:8080/hello/demo? Name = Gemini Lone Wolf test, 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.

But suppose there is 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. Pom files introduce dependencies. It should be noted that the package property should be set to WAR.

<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

Create a webapp/WEB-INF folder under SRC /main and create a web. XML file under web-INF.

<? 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 HelloServlet class that inherits HttpServlet:

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. Execute maven package command to confirm successful package into WAR package:

RUN–>Edit Configurations then click the + in the upper left corner to create a New Tomcat Server. If this is your first configuration, there is no Tomcat Server option by default, you need to click xx more items at the bottom… :

6. Click on Deployment on the right, then click on it as shown below, and finally locate the war package file in the popup box:

The war package name will be added to the Application Context by default. For convenience, we need to delete the war package name from the Application Context. Select Apply and click OK to complete the deployment:

8, we finally in the browser http://localhost:8080/hello? input request path Name = Gemini Lone Wolf, then 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. The POM dependencies remain unchanged, but the web.xml changes as follows, which intercepts all interfaces /*, and configures an additional parameter, which is also intended to simulate Spring more graphically:


<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

Create a new application. Properties configuration file under respurces that defines the base path for 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 "";
}
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() 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() 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() 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() default "";
}

Copy the code

The MyDispatcherServlet class is the core logic:

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. This Servlet has an init method compared to the HelloServlet above, which does 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. HandlerMapping class is used to store the mapping between request paths and methods:

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); }}Copy the code
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.

If you think this article is good, you can give me a thumbs-up, comments, favorites, one key three even oh, thank you for reading, we will see you tomorrow.