Pt1 手写IoC/DI
Pt1.1 流程设计
IoC + DI是在启动的时候初始化的,负责管理Bean的生命周期和依赖关系。

IoC和DI主要涉及核心类:

DispatcherServlet:在web.xml定义的启动类Servlet。负责Web容器初始化,以及拦截客户端请求并完成调度和分发;

ApplicationContext:Spring运行上下文。负责读取Spring配置,扫描Bean,保存IoC容器。

BeanDefinition:保存Spring Bean的定义信息。

BeanWrapper:Spring对BeanDefinition的代理,包含了Bean定义和实例化对象信息。

BeanDefinitionReader:负责加载Spring配置,读取Bean定义。

 

IoC和DI是在Spring启动的过程中完成的,其中DispatcherServlet是入口,初始化整个流程是在ApplicationContext进行控制的。 过程大体分为以下几个步骤:

加载/解析Spring配置文件,扫描Bean;

将读取的Bean定义封装成BeanDefinition;

将Bean注册到IoC容器(未实例化);

完成依赖注入(自动);

Pt1.2 基础配置
application.properties
Spring IoC的自动扫描需要配置scanPackage,即扫描根路径,Spring会扫描根路径下所有Bean定义。当然在Spring中,该配置是在Spring的xml中配置的,这里我们直接以properties文件的形式进行配置,简化代码读取的逻辑,本质上是一样的。

 # 配置类扫描包路径
 scanPackage=com.demo.spring.simulation.v5.test

pom.xml
从依赖上可以看出,核心只有Servlet,引入日志、Lombok和Junit是用来简化开发和输出一些验证信息。没有任何的Spring组件依赖,我们要自己手写Spring的核心流程,实现上还是尽量干净一些。

 <?xml version="1.0" encoding="UTF-8"?>
 ​
 <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>org.spring5</groupId>
     <artifactId>myspring5-action</artifactId>
     <version>1.0-SNAPSHOT</version>
     <packaging>war</packaging>
 ​
     <name>myspring5-action Maven Webapp</name>
     <!-- FIXME change it to the project's website -->
     <url>http://www.example.com</url>
 ​
     <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>
     </properties>
 ​
     <dependencies>
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
             <version>3.1.0</version>
         </dependency>
 ​
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.18.12</version>
         </dependency>
 ​
         <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <version>1.7.25</version>
         </dependency>
 ​
         <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
             <version>1.2.3</version>
         </dependency>
 ​
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter</artifactId>
             <version>RELEASE</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
 ​
     <build>
         <finalName>myspring5-action</finalName>
         <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
             <plugins>
                 <plugin>
                     <artifactId>maven-clean-plugin</artifactId>
                     <version>3.1.0</version>
                 </plugin>
                 <plugin>
                     <artifactId>maven-resources-plugin</artifactId>
                     <version>3.0.2</version>
                 </plugin>
                 <plugin>
                     <artifactId>maven-compiler-plugin</artifactId>
                     <version>3.8.0</version>
                 </plugin>
                 <plugin>
                     <artifactId>maven-surefire-plugin</artifactId>
                     <version>2.22.1</version>
                 </plugin>
                 <plugin>
                     <artifactId>maven-war-plugin</artifactId>
                     <version>3.2.2</version>
                 </plugin>
                 <plugin>
                     <artifactId>maven-install-plugin</artifactId>
                     <version>2.5.2</version>
                 </plugin>
                 <plugin>
                     <artifactId>maven-deploy-plugin</artifactId>
                     <version>2.8.2</version>
                 </plugin>
             </plugins>
         </pluginManagement>
     </build>
 </project>

 

web.xml
配置非常简单,定义了Web容器启动Servlet(MyDispatcherServlet)和配置路径。

 <?xml version="1.0" encoding="UTF-8"?>
 <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
          version="3.1">
 ​
     <display-name>myspring5-action</display-name>
     <welcome-file-list>
         <welcome-file>index.jsp</welcome-file>
     </welcome-file-list>
     
     <servlet>
         <servlet-name>springMVC</servlet-name>
         <servlet-class>com.demo.spring.simulation.v5.servlet.MyDispatcherServlet</servlet-class>
         <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>classpath:application.properties</param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
         <async-supported>true</async-supported>
     </servlet>
     <servlet-mapping>
         <servlet-name>springMVC</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 </web-app>

 


Pt1.3 注解定义
Spring注解比较多,根据模拟过程中的需要选择性的实现部分。

 

@MyController

 package com.demo.spring.simulation.v5.annotation;
 ​
 import java.lang.annotation.*;
 ​
 /**
  * 自定义Controller注解
  */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MyController {
     String value() default "";
 }

@MyService

 package com.demo.spring.simulation.v5.annotation;
 ​
 import java.lang.annotation.*;
 ​
 /**
  * 自定义Service注解
  */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MyService {
     String value() default "";
 }

@MyAutowired

 package com.demo.spring.simulation.v5.annotation;
 ​
 import java.lang.annotation.*;
 ​
 /**
  * 自定义Autowired注解
  */
 @Target({ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MyAutowired {
     String value() default "";
 }
@MyRequestMapping
 
package com.demo.spring.simulation.v5.annotation;
 ​
 import java.lang.annotation.*;
 ​
 /**
  * 自定义RequestMapping注解
  */
 @Target({ElementType.TYPE, ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MyRequestMapping {
     String value() default "";
 }

 

 
 
@MyRequestParam
 
package com.demo.spring.simulation.v5.annotation;
 ​
 import java.lang.annotation.*;
 ​
 /**
  * 自定义RequestParam注解
  */
 @Target({ElementType.PARAMETER})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MyRequestParam {
     String value() default "";
 }

 

 
Pt1.4 核心代码
DispatcherServlet
DispatcherServlet作为启动的整个入口,init()负责初始化IoC、DI、MVC和AOP的环境。这里先介绍IoC和DI的加载过程,从代码可以看出,初始化过程是在ApplicationContext中完成的。
 
DispatherServlet#init是入口,我们从这里开始看整个流程的处理。
 
/**
 * DispatcherServlet负责请求调度和分发。
 */
public class MyDispatcherServlet extends HttpServlet {

    // Spring配置文件路径
    private static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";

    // Spring上下文,Spring IoC容器
    private MyApplicationContext applicationContext;

    @Override
    public void init(ServletConfig config) throws ServletException {
        log.info("DispatcherServlet -> Create Web Server Starting.");

        // 1、初始化ApplicationContext。ApplicationContext包含了Spring核心IoC容器,完成Bean扫描、初始化和DI。
        log.info("DispatcherServlet -> Init Spring IoC/DI Starting.");
        applicationContext = new MyApplicationContext(config.getInitParameter(CONTEXT_CONFIG_LOCATION));
        log.info("DispatcherServlet -> Init Spring IoC/DI Finished.");

// TODO
        
        log.info("DispatcherServlet -> Create Web Server Finished.");
    }

 

    @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 {
// TODO
    }
}